diff --git a/src/defaultsettings.yml b/src/defaultsettings.yml index e311136d..eb56772d 100644 --- a/src/defaultsettings.yml +++ b/src/defaultsettings.yml @@ -1149,13 +1149,13 @@ gameplay: &gameplay square: _desc: The maximum number of clue tokens drawn when pulling a clue card in the square. _type: int - _default: 3 + _default: 2 forest: _desc: > 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: 2 + _default: 3 streets: _desc: > The maximum number of clue tokens drawn when drawing evidence in the streets when there is diff --git a/src/functions.py b/src/functions.py index 3cb624ab..da99ece4 100644 --- a/src/functions.py +++ b/src/functions.py @@ -186,8 +186,10 @@ def change_role(var: GameState, return new_role, evt.data["messages"] -def get_main_role(var: GameState, user): - role = var.main_roles.get(user) +def get_main_role(var: GameState, user, *, mainroles=None): + if mainroles is None: + mainroles = var.main_roles + role = mainroles.get(user) if role is not None: return role # not found in player list, see if they're a special participant @@ -199,11 +201,13 @@ def get_main_role(var: GameState, user): raise ValueError("User {0} isn't playing and has no defined participant role".format(user)) return role -def get_all_roles(var: GameState, user: User) -> set[str]: - return {role for role, users in var.roles.items() if user in users} +def get_all_roles(var: GameState, user: User, *, rolemap=None) -> set[str]: + if rolemap is None: + rolemap = var.roles + return {role for role, users in rolemap.items() if user in users} -def get_reveal_role(var: GameState, user) -> str: - evt = Event("get_reveal_role", {"role": get_main_role(var, user)}) +def get_reveal_role(var: GameState, user, *, mainroles=None) -> str: + evt = Event("get_reveal_role", {"role": get_main_role(var, user, mainroles=mainroles)}) evt.dispatch(var, user) role = evt.data["role"] diff --git a/src/gamemodes/pactbreaker.py b/src/gamemodes/pactbreaker.py index 83cb0e66..d8dbcc78 100644 --- a/src/gamemodes/pactbreaker.py +++ b/src/gamemodes/pactbreaker.py @@ -245,13 +245,14 @@ def build_deck(self, var: GameState, location: Location, visitors: set[User]) -> + ["clue", "evidence"] * num_visitors) num_draws = 4 elif location is Graveyard: - deck = (["clue", "clue", "empty-handed", "empty-handed"] - + ["hunted"] * num_wolves - + ["empty-handed"] * num_other) - num_draws = 1 + deck = (["clue"] * 2 + + ["empty-handed"] * 6 + + ["hunted", "empty-handed"] * num_wolves + + ["empty-handed", "empty-handed"] * num_other) + num_draws = 2 elif location is Streets: - deck = (["empty-handed"] * (4 + max(0, num_visitors - 8)) - + ["evidence"] * 4 + deck = (["empty-handed"] * (1 + max(0, num_visitors - 8)) + + ["evidence"] * 7 + ["hunted", "empty-handed"] * num_wolves + ["evidence", "empty-handed"] * num_other) num_draws = 3 @@ -366,6 +367,9 @@ def on_night_kills(self, evt: Event, var: GameState): if wolf is not visitor and wolf not in self.collected_evidence[visitor]["wolf"]: evidence_target = wolf break + else: + # give non-wolves a higher chance of gaining clues after exhausting all wolf evidence + num_evidence += 1 elif location is Streets and num_evidence == 3: # refute fake evidence that the visitor may have collected, # in order of cursed -> villager and then vigilante -> vampire @@ -403,12 +407,12 @@ def on_night_kills(self, evt: Event, var: GameState): visitor.send(messages[f"pactbreaker_{loc}_empty"]) # handle share cards - if len(shares) <= 2: + if len(shares) <= 1: for visitor in shares: loc = self.visiting[visitor].name visitor.send(messages[f"pactbreaker_{loc}_empty"]) - elif len(shares) > 2: - num_tokens = min(len(shares) - 2, + elif len(shares) > 1: + num_tokens = min(len(shares) - 1, math.floor(self.clue_pool / len(shares)), config.Main.get("gameplay.modes.pactbreaker.clue.square")) for visitor in shares: @@ -453,7 +457,7 @@ def on_night_death_message(self, evt: Event, var: GameState, victim: User, kille # vigilante self-kill return - killer_role = get_main_role(var, killer) + killer_role = get_main_role(var, killer, mainroles=evt.params.mainroles) if killer_role == "vampire": victim.send(messages["pactbreaker_drained_dead"]) diff --git a/src/trans.py b/src/trans.py index ad6aa6c0..d341ab6e 100644 --- a/src/trans.py +++ b/src/trans.py @@ -251,6 +251,11 @@ def transition_day(var: GameState, game_id: int = 0): get_kill_priority = lambda x: kill_priorities[x] + random.random() killers = {u: sorted(kl, key=get_kill_priority) for u, kl in killers.items()} + # save a copy of roles so we can credit kills to the roles the players were at night, + # before any roleswaps due to night kills (e.g. lycanthropy) + rolemap = {role: set(players) for role, players in var.roles.items()} + mainroles = dict(var.main_roles) + for victim in victims: if not is_dying(var, victim): for killer in list(killers[victim]): @@ -270,7 +275,7 @@ def transition_day(var: GameState, game_id: int = 0): assert kdata["role"] is not None else: kdata["attacker"] = killer - kdata["role"] = get_main_role(var, killer) + kdata["role"] = get_main_role(var, killer, mainroles=mainroles) protected = None if kdata["try_protection"]: protected = try_protection(var, victim, kdata["attacker"], kdata["role"], reason=kdata["protection_reason"]) @@ -295,7 +300,7 @@ def transition_day(var: GameState, game_id: int = 0): mevt = Event("night_death_message", { "key": "death" if var.role_reveal in ("on", "team") else "death_no_reveal", "args": [victim, get_reveal_role(var, victim)] - }) + }, rolemap=rolemap, mainroles=mainroles) if mevt.dispatch(var, victim, killers[victim][0]): message[victim].append(messages[mevt.data["key"]].format(*mevt.data["args"])) @@ -307,7 +312,7 @@ def transition_day(var: GameState, game_id: int = 0): "message": message, "novictmsg": novictmsg, "howl": howl_count, - }, victims=victims) + }, victims=victims, rolemap=rolemap, mainroles=mainroles) evt.dispatch(var, dead, {v: k[0] for v, k in killers.items() if v in dead}) # handle howls and novictmsg