Skip to content

Commit

Permalink
Track night kills better in pactbreaker
Browse files Browse the repository at this point in the history
We now key kill locations by both attacker and victim since it's now
possible for a single attacker to kill multiple victims in a single
night. When this happened, an error occurred, now it won't.
  • Loading branch information
skizzerz committed Dec 27, 2023
1 parent 8c6a94e commit e16a8de
Showing 1 changed file with 27 additions and 19 deletions.
46 changes: 27 additions & 19 deletions src/gamemodes/pactbreaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def __init__(self, arg=""):
}

# messages to send on night kills; only populated during transition_day so user containers are unnecessary
self.night_kill_messages: dict[User, Location] = {}
self.night_kill_messages: dict[tuple[User, User], Optional[Location]] = {}
self.visiting: UserDict[User, Location] = UserDict()
self.drained = UserSet()
self.voted: DefaultUserDict[User, int] = DefaultUserDict(int)
Expand Down Expand Up @@ -223,7 +223,7 @@ def on_night_kills(self, evt: Event, var: GameState):
elif card == "hunted":
evt.data["victims"].add(visitor)
evt.data["killers"][visitor].append(wolf)
self.night_kill_messages[wolf] = location
self.night_kill_messages[(wolf, visitor)] = location
else:
visitor.send(messages["pactbreaker_forest_empty"])
elif location is VillageSquare:
Expand Down Expand Up @@ -265,7 +265,7 @@ def on_night_kills(self, evt: Event, var: GameState):
else:
evt.data["victims"].add(target)
evt.data["killers"][target].append(visitor)
self.night_kill_messages[visitor] = location
self.night_kill_messages[(visitor, target)] = location
elif role == "vampire" and (card == "drained" or evidence_special):
# vampires fully drain the player in the stocks
if not target or target_role == "vampire":
Expand All @@ -275,7 +275,7 @@ def on_night_kills(self, evt: Event, var: GameState):
evt.data["victims"].add(target)
evt.data["killers"][target].append(visitor)
evt.data["kill_priorities"][actor] = 10
self.night_kill_messages[visitor] = location
self.night_kill_messages[(visitor, target)] = location
elif role == "vigilante" and (card == "exposed" or evidence_special):
# vigilantes kill the player in the stocks if they have hard evidence on them,
# otherwise they gain hard evidence
Expand All @@ -285,7 +285,7 @@ def on_night_kills(self, evt: Event, var: GameState):
elif target in self.collected_evidence[visitor]:
evt.data["victims"].add(target)
evt.data["killers"][target].append(visitor)
self.night_kill_messages[visitor] = location
self.night_kill_messages[(visitor, target)] = location
else:
# vigilante is the only role capable of gaining evidence from people in the stocks
self.collected_evidence[visitor].add(target)
Expand All @@ -298,13 +298,13 @@ def on_night_kills(self, evt: Event, var: GameState):
# vigilantes and villagers get killed by the wolf
evt.data["victims"].add(visitor)
evt.data["killers"][visitor].append(actor)
self.night_kill_messages[actor] = location
self.night_kill_messages[(actor, visitor)] = location
elif card == "drained":
# non-vampires get drained by the vampire
evt.data["victims"].add(visitor)
evt.data["killers"][visitor].append(actor)
evt.data["kill_priorities"][actor] = 10
self.night_kill_messages[actor] = location
self.night_kill_messages[(actor, visitor)] = location
elif card == "exposed":
# non-vigilantes gain evidence about the vigilante
# (the vigilante is not aware of this)
Expand Down Expand Up @@ -376,14 +376,14 @@ def on_night_kills(self, evt: Event, var: GameState):
# (and sometimes even if they're not)
evt.data["victims"].add(owner)
evt.data["killers"][owner].append(visitor)
self.night_kill_messages[visitor] = location
self.night_kill_messages[(visitor, owner)] = location
elif (not is_home and owner in self.collected_evidence[visitor]
and owner_role == "vampire" and role != "vampire"):
# non-vampires destroy known vampires that aren't home
evt.data["victims"].add(owner)
evt.data["killers"][owner].append(visitor)
evt.data["kill_priorities"][visitor] = 5
self.night_kill_messages[visitor] = location
self.night_kill_messages[(visitor, owner)] = location
elif "evidence" in cards:
self.collected_evidence[visitor].add(owner)
if not is_home:
Expand All @@ -395,7 +395,7 @@ def on_night_kills(self, evt: Event, var: GameState):
evt.data["victims"].add(owner)
evt.data["killers"][owner].append(visitor)
evt.data["kill_priorities"][visitor] = 10
self.night_kill_messages[visitor] = location
self.night_kill_messages[(visitor, owner)] = location
elif is_home:
visitor.send(messages["pactbreaker_house_empty_2"].format(owner))
else:
Expand All @@ -410,7 +410,7 @@ def on_night_kills(self, evt: Event, var: GameState):
evt.data["victims"].add(visitor)
evt.data["killers"][visitor].append(vampires[i])
evt.data["kill_priorities"][vampires[i]] = 10
self.night_kill_messages[vampires[i]] = location
self.night_kill_messages[(vampires[i], visitor)] = location
i += 1

def on_player_protected(self,
Expand All @@ -426,7 +426,8 @@ def on_player_protected(self,
self.drained.add(target)
attacker.send(messages["pactbreaker_drain"].format(target))
target.send(messages["pactbreaker_drained"])
del self.night_kill_messages[attacker]
# mark that the player has successfully killed so we don't give them an empty-handed message later
self.night_kill_messages[(attacker, target)] = None
elif protector_role == "vigilante":
# if the vampire fully drains a vigilante, they might turn into a vampire instead of dying
# this protection triggering means they should turn
Expand All @@ -444,36 +445,43 @@ def on_player_protected(self,
def on_night_death_message(self, evt: Event, var: GameState, victim: User, killer: User | str):
victim_role = get_main_role(var, victim)
killer_role = get_main_role(var, killer)
location = self.night_kill_messages[(killer, victim)]

if killer_role == "vampire":
victim.send(messages["pactbreaker_drained_dead"])
killer.send(messages["pactbreaker_drain_kill"].format(victim))
elif killer_role == "wolf" and victim not in self.active_players and self.night_kill_messages[killer] is VillageSquare:
elif killer_role == "wolf" and victim not in self.active_players and location is VillageSquare:
victim.send(messages["pactbreaker_hunted"])
killer.send(messages["pactbreaker_hunter_square"].format(victim))
elif victim_role == "vampire" and self.night_kill_messages[killer] is get_home(var, victim):
elif victim_role == "vampire" and location is get_home(var, victim):
victim.send(messages["pactbreaker_house_daylight"])
killer.send(messages["pactbreaker_house_vampire"].format(victim))
elif killer_role == "wolf":
victim.send(messages["pactbreaker_hunted"])
killer.send(messages["pactbreaker_hunter"].format(victim))
elif killer_role == "vigilante" and self.night_kill_messages[killer] is get_home(var, victim):
elif killer_role == "vigilante" and location is get_home(var, victim):
victim.send(messages["pactbreaker_bolted"])
killer.send(messages["pactbreaker_house_kill"].format(victim, victim_role))
elif killer_role == "vigilante" and self.night_kill_messages[killer] is VillageSquare:
elif killer_role == "vigilante" and location is VillageSquare:
victim.send(messages["pactbreaker_bolted"])
killer.send(messages["pactbreaker_square_kill"].format(victim, victim_role))
else:
# shouldn't happen; indicates a bug in the mode
raise RuntimeError("Unknown night death situation")

# mark that the player has successfully killed so we don't give them an empty-handed message later
del self.night_kill_messages[killer]
self.night_kill_messages[(killer, victim)] = None

def on_transition_day_resolve(self, evt: Event, var: GameState, dead: set[User], killers: dict[User, User | str]):
# anyone remaining in this set was meant to kill someone but got their kill pre-empted by someone else
# check for players meant to kill someone but got their kill pre-empted by someone else
# report the appropriate empty-handed message to them instead
for player, location in self.night_kill_messages.items():
# this happens if *all* of their entries still have a location defined
killed = {p for (p, _), l in self.night_kill_messages.items() if l is None}

for (player, _), location in self.night_kill_messages.items():
if player in killed:
continue

if location is Forest:
player.send(messages["pactbreaker_forest_empty"])
elif location is VillageSquare:
Expand Down

0 comments on commit e16a8de

Please sign in to comment.