Skip to content

Commit

Permalink
Pactbreaker tweaks
Browse files Browse the repository at this point in the history
- Fix some messages
- Graveyard now has fewer clues and a lower chance to gain a clue, but
  will give more clue tokens
- Square now gives n-2 tokens instead of n-1 when sharing evidence
- Ensure that drawing evidence at square while nobody is in the stocks
  correctly no-ops
- Slightly increase evidence chance at Forest
- After iding all wolves/cursed at Forest, it is now a fairly reliable
  way to gain 2 clue tokens at the increased risk of being hunted
- Don't let multiple vampires bite the same target on a given night
  • Loading branch information
skizzerz committed Nov 30, 2024
1 parent 1eed63b commit d94b747
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 15 deletions.
4 changes: 2 additions & 2 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1443,7 +1443,7 @@
"pactbreaker_id_fail": "Your investigation of {0} did not turn up any useful information.",
"pactbreaker_wolf_notify": "You are {=wolf!role:article} {=wolf!role:bold}. It is your job to eliminate the {=vampire!role:plural} and {=vigilante!role:plural} to re-establish your pact with the village.\n{=pactbreaker_notify!message}\nFinally, you can use \"{=kill!command} <nick>\" to kill someone in the stocks or a known {=vampire!role} or {=vigilante!role} instead of visting a location.",
"pactbreaker_vampire_notify": "You are {=vampire!role:article} {=vampire!role:bold}. It is your job to kill all the villagers.\n{=pactbreaker_notify!message}\nFinally, you can use \"{=bite!command} <nick>\" to feed on someone's blood instead of visting a location. Your target will gain clues should you do so, but biting most people twice will kill them.",
"pactbreaker_vigilante_notify": "You are {=vigilante!role:article} {=vigilante!role:bold}. It is your job to eliminate the {=wolf!role:plural} and {=vampire!role:plural} to end their threat over the village.\n{=pactbreaker_notify!message}.\nFinally, you can use \"{=kill!command} <nick>\" to kill someone in the stocks or a known {=wolf!role} or {=vampire!role} instead of visting a location. If the person you kill is not {=wolf!role:article} {=wolf!role} or {=wolf!role:article} {=vampire!role}, you will die alongside your victim.",
"pactbreaker_vigilante_notify": "You are {=vigilante!role:article} {=vigilante!role:bold}. It is your job to eliminate the {=wolf!role:plural} and {=vampire!role:plural} to end their threat over the village.\n{=pactbreaker_notify!message}\nFinally, you can use \"{=kill!command} <nick>\" to kill someone in the stocks or a known {=wolf!role} or {=vampire!role} instead of visting a location. If the person you kill is not {=wolf!role:article} {=wolf!role} or {=wolf!role:article} {=vampire!role}, you will die alongside your victim.",
"pactbreaker_villager_notify": "You are {=villager!role:article} {=villager!role:bold}. It is your job to either re-establish the pact with the {=wolf!role:plural} by killing the {=vigilante!role:plural} and {=vampire!role:plural} or to help the {=vigilante!role:plural} end the threat the {=wolf!role:plural} and {=vampire!role:plural} cause.\n{=pactbreaker_notify!message}",
"pactbreaker_notify": "You may visit the {=forest!command:bold}, {=square!command:bold}, {=streets!command:bold}, or {=graveyard!command:bold} by using \"{=visit!command} <location>\". Visiting allows you to collect evidence about other players.",
"pactbreaker_clue_notify": "You have {0:bold} clue {=token,tokens:plural({0})}. You may spend all of your clue tokens during the day (minimum {1}) by using \"{=id!command} <nick>\" in PM to gain evidence on someone.",
Expand All @@ -1464,7 +1464,7 @@
"pactbreaker_forest_evidence": "You discover some items in the forest leading you to believe that {0:@} is {1!role:article} {1!role:bold}!",
"pactbreaker_forest_clue": "Your search of the forest last night did not go as well as planned, but you still feel like you've gained a useful clue towards determining someone's identity.",
"pactbreaker_forest_empty": "Your search of the forest last night did not turn up any useful evidence.",
"pactbreaker_square_clue": "You compared notes with other people in the village square last night and gained {0:bold} useful clues towards determining someone's identity.",
"pactbreaker_square_clue": "You compared notes with other people in the village square last night and gained {0:bold} useful {=clue,clues:plural({0})} towards determining someone's identity.",
"pactbreaker_square_evidence": "Examining {0:@} while they are in the stocks leads you to the conclusion that they are {1!role:article} {1!role:bold}!",
"pactbreaker_square_empty": "Your search of the village square last night did not turn up any useful evidence.",
"pactbreaker_graveyard_clue": "While searching the graveyard last night, you gained some useful clues towards determining someone's identity.",
Expand Down
4 changes: 2 additions & 2 deletions src/defaultsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,7 @@ gameplay: &gameplay
graveyard:
_desc: The maximum number of clue tokens drawn when pulling a clue card in the graveyard.
_type: int
_default: 2
_default: 4
square:
_desc: The maximum number of clue tokens drawn when pulling a clue card in the square.
_type: int
Expand All @@ -1155,7 +1155,7 @@ gameplay: &gameplay
The maximum number of clue tokens drawn when drawing evidence in the forest when there is
nobody left at that location to obtain evidence on.
_type: int
_default: 1
_default: 2
streets:
_desc: >
The maximum number of clue tokens drawn when drawing evidence in the streets when there is
Expand Down
32 changes: 21 additions & 11 deletions src/gamemodes/pactbreaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def build_deck(self, var: GameState, location: Location, visitors: set[User]) ->
num_other = num_visitors - num_wolves

if location is Forest:
deck = (["empty-handed", "empty-handed", "evidence"]
deck = (["empty-handed", "evidence", "evidence"]
+ (["hunted", "hunted"] * num_wolves)
+ (["evidence", "evidence"] * num_other))
num_draws = 2
Expand All @@ -246,7 +246,9 @@ def build_deck(self, var: GameState, location: Location, visitors: set[User]) ->
+ ["clue", "evidence"] * num_visitors)
num_draws = 4
elif location is Graveyard:
deck = ["clue"] * 3 + ["hunted"] * num_wolves + ["empty-handed"] * num_other
deck = (["clue", "clue", "empty-handed", "empty-handed"]
+ ["hunted"] * num_wolves
+ ["empty-handed"] * num_other)
num_draws = 1
elif location is Streets:
deck = (["empty-handed"] * (4 + max(0, num_visitors - 8))
Expand Down Expand Up @@ -310,7 +312,7 @@ def on_night_kills(self, evt: Event, var: GameState):
and get_all_protections(var, player, Vampire)):
visited[location].add(player)

shares: list[User] = list()
shares: set[User] = set()
for location, visitors in visited.items():
if location is Limbo or not visitors:
continue
Expand Down Expand Up @@ -354,7 +356,7 @@ def on_night_kills(self, evt: Event, var: GameState):
visitor.send(messages[f"pactbreaker_{loc}_clue"].format(tokens))
elif location is VillageSquare:
# has to be handled after everyone finishes drawing
shares.append(visitor)
shares.add(visitor)

# handle evidence card
num_evidence = sum(1 for c in cards if c == "evidence")
Expand Down Expand Up @@ -391,7 +393,7 @@ def on_night_kills(self, evt: Event, var: GameState):
target_role = get_main_role(var, evidence_target)
self.collected_evidence[visitor][target_role].add(evidence_target)
visitor.send(messages[f"pactbreaker_{loc}_evidence"].format(evidence_target, target_role))
elif self.clue_pool > 0:
elif self.clue_pool > 0 and location is not VillageSquare:
empty = False
tokens = min(self.clue_pool, config.Main.get(f"gameplay.modes.pactbreaker.clue.{loc}"))
self.clue_pool -= tokens
Expand All @@ -402,13 +404,12 @@ def on_night_kills(self, evt: Event, var: GameState):
visitor.send(messages[f"pactbreaker_{loc}_empty"])

# handle share cards
if len(shares) == 1:
if len(shares) <= 2:
for visitor in shares:
loc = self.visiting[visitor].name
visitor.send(messages[f"pactbreaker_{loc}_empty"])
elif len(shares) > 1:
random.shuffle(shares)
num_tokens = min(len(shares) - 1,
elif len(shares) > 2:
num_tokens = min(len(shares) - 2,
math.floor(self.clue_pool / len(shares)),
config.Main.get("gameplay.modes.pactbreaker.clue.square"))
for visitor in shares:
Expand Down Expand Up @@ -543,8 +544,8 @@ def on_chk_win(self, evt: Event, var: GameState, rolemap, mainroles, lpl, lwolve
# the vigilantes and vampires are all dead
evt.data["winner"] = "wolves"
evt.data["message"] = messages["pactbreaker_wolf_win"]
elif lvampires >= num_villagers or (num_vigilantes == 0 and lwolves == 0):
# vampires can win even with wolves and vigilante alive if they match or outnumber the rest of the village
elif lvampires and (num_villagers == 0 or (num_vigilantes == 0 and lwolves == 0)):
# vampires can win even with wolves and vigilante alive if they kill the rest of the village
# or if all wolves and vigilantes are dead even if the remainder of the village outnumbers them
evt.data["winner"] = "vampires"
evt.data["message"] = messages["pactbreaker_vampire_win"]
Expand Down Expand Up @@ -649,6 +650,15 @@ def bite(self, wrapper: MessageDispatcher, message: str):
wrapper.send(messages["no_target_vampire"])
return

for killer, victim in self.killing.items():
if wrapper.source is killer:
# let the vampire target the same person multiple times in succession
# doesn't really do anything but giving an error is even weirder
continue
if target is victim and is_known_vampire_ally(var, wrapper.source, killer):
wrapper.send(messages["already_bitten_tonight"].format(target))
return

self.killing[wrapper.source] = target
self.visiting[wrapper.source] = Limbo
wrapper.pm(messages["vampire_bite"].format(target))
Expand Down

0 comments on commit d94b747

Please sign in to comment.