Skip to content

Commit

Permalink
Pactbreaker fixes
Browse files Browse the repository at this point in the history
- Fix issue when multiple kills would take place in a house causing an
  error instead of playing the "empty-handed" message to the players
  whose kills were pre-empted
- Voting the same player two nights in a row is no longer effective.
- Fix issue where the regular vampire drained logic was incorrectly
  happening, causing a duplicated message sent to drained targets and
  incorrectly marking them as absent from the village for the next day.
  • Loading branch information
skizzerz committed Dec 19, 2023
1 parent d957367 commit 334344b
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 4 deletions.
3 changes: 3 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,9 @@
"pactbreaker_vote": [
"The village has decided to lock {0:@} into the stocks until tomorrow."
],
"pactbreaker_stocks_escape": [
"The village has decided to lock {0:@} into the stocks until tomorrow. However, it seems that shortly after the village left for the night, they managed to free themselves..."
],
"roles_players": "There {=is,are:plural({0})} {0:bold} playing.",
"roles_gamemode": "Using the {0} game mode.",
"roles_need_gamemode": "Please specify a game mode by using \"{=roles!command:!} <mode>\".",
Expand Down
3 changes: 3 additions & 0 deletions src/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ def __call__(self, *args, **kwargs):
else:
return self.func(*args, **kwargs)

def install(self):
self.listener.install(self.event)

def remove(self):
self.listener.remove(self.event)

Expand Down
29 changes: 25 additions & 4 deletions src/gamemodes/pactbreaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
from src.containers import UserSet, UserDict, DefaultUserDict
from src.decorators import command
from src.dispatcher import MessageDispatcher
from src.events import Event
from src.events import EventListener
from src.events import Event, EventListener
from src.functions import get_players, get_main_role, get_target, change_role
from src.gamemodes import game_mode, GameMode
from src.gamestate import GameState
from src.messages import messages, LocalRole
from src.locations import move_player, get_home, Location, VillageSquare, Forest
from src.cats import Wolf, Vampire
from src.status import add_protection
from src.status import add_protection, add_lynch_immunity
from src.roles.helper.wolves import send_wolfchat_message, wolf_kill, wolf_retract
from src.roles.vampire import send_vampire_chat_message, vampire_bite, vampire_retract
from src.roles.vampire import on_player_protected as vampire_drained
from src.roles.vigilante import vigilante_kill, vigilante_retract, vigilante_pass

@game_mode("pactbreaker", minp=6, maxp=24, likelihood=0)
Expand Down Expand Up @@ -57,6 +57,8 @@ def __init__(self, arg=""):
"begin_day": EventListener(self.on_begin_day),
"transition_night_begin": EventListener(self.on_transition_night_begin),
"lynch": EventListener(self.on_lynch),
"abstain": EventListener(self.on_abstain),
"lynch_immunity": EventListener(self.on_lynch_immunity),
"del_player": EventListener(self.on_del_player),
}

Expand All @@ -74,6 +76,7 @@ def __init__(self, arg=""):
self.visiting: UserDict[User, Location] = UserDict()
self.drained = UserSet()
self.voted: DefaultUserDict[User, int] = DefaultUserDict(int)
self.last_voted: Optional[User] = None
self.active_players = UserSet()
self.collected_evidence: DefaultUserDict[User, UserSet] = DefaultUserDict(UserSet)
dfd = lambda: DefaultUserDict(int)
Expand All @@ -95,10 +98,12 @@ def startup(self):
# register !visit and !pass, remove all role commands
self.visit_command.register()
self.pass_command.register()
self.last_voted = None
wolf_kill.remove()
wolf_retract.remove()
vampire_bite.remove()
vampire_retract.remove()
vampire_drained.remove()
vigilante_kill.remove()
vigilante_retract.remove()
vigilante_pass.remove()
Expand All @@ -111,6 +116,7 @@ def teardown(self):
wolf_retract.register()
vampire_bite.register()
vampire_retract.register()
vampire_drained.install()
vigilante_kill.register()
vigilante_retract.register()
vigilante_pass.register()
Expand All @@ -129,6 +135,8 @@ def on_del_player(self, evt: Event, var: GameState, player, all_roles, death_tri
others.discard(player)
for _, others in self.visit_count.items():
del others[:player:]
if self.last_voted is player:
self.last_voted = None

def on_start_game(self, evt: Event, var: GameState, mode_name: str, mode: GameMode):
# mark every player as active at start of game
Expand Down Expand Up @@ -464,7 +472,8 @@ def on_transition_day_resolve(self, evt: Event, var: GameState, dead: set[User],
else:
# we can no longer tell whether or not the owner stayed home; they've already been
# cleared from visitor lists. So, always give the message as if house was unoccupied
player.send(messages["pactbreaker_house_empty_1"])
owner = [x for x in get_players(var) if get_home(var, x) is location][0]
player.send(messages["pactbreaker_house_empty_1"].format(owner))

self.night_kill_messages.clear()

Expand All @@ -473,15 +482,27 @@ def on_begin_day(self, evt: Event, var: GameState):
self.active_players.clear()
self.active_players.update(get_players(var))
self.visiting.clear()
# if someone was locked up last night, ensure they can't be locked up again tonight
if self.last_voted is not None:
add_lynch_immunity(var, self.last_voted, "pactbreaker")

def on_lynch(self, evt: Event, var: GameState, votee: User, voters: Iterable[User]):
self.last_voted = votee
self.voted[votee] += 1
if self.voted[votee] < 3:
channels.Main.send(messages["pactbreaker_vote"].format(votee))
self.active_players.discard(votee)
# don't kill the votee
evt.prevent_default = True

def on_abstain(self, evt: Event, var: GameState):
self.last_voted = None

def on_lynch_immunity(self, evt: Event, var: GameState, player: User, reason: str):
if reason == "pactbreaker":
channels.Main.send(messages["pactbreaker_stocks_escape"].format(player))
evt.data["immune"] = True

def on_wolf_numkills(self, evt: Event, var: GameState, wolf):
evt.data["numkills"] = 0

Expand Down

0 comments on commit 334344b

Please sign in to comment.