From d94b7475bf12c1fd67008e024c777c77dbd55249 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 29 Nov 2024 19:50:31 -0700 Subject: [PATCH] Pactbreaker tweaks - 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 --- messages/en.json | 4 ++-- src/defaultsettings.yml | 4 ++-- src/gamemodes/pactbreaker.py | 32 +++++++++++++++++++++----------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/messages/en.json b/messages/en.json index 17f5dd13..6bd06e87 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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} \" 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} \" 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} \" 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} \" 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} \". 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} \" in PM to gain evidence on someone.", @@ -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.", diff --git a/src/defaultsettings.yml b/src/defaultsettings.yml index 612a5cbe..e311136d 100644 --- a/src/defaultsettings.yml +++ b/src/defaultsettings.yml @@ -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 @@ -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 diff --git a/src/gamemodes/pactbreaker.py b/src/gamemodes/pactbreaker.py index 0e977e28..6ed4ce18 100644 --- a/src/gamemodes/pactbreaker.py +++ b/src/gamemodes/pactbreaker.py @@ -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 @@ -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)) @@ -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 @@ -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") @@ -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 @@ -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: @@ -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"] @@ -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))