Skip to content

Commit

Permalink
Properly credit kills to original roles and rebalance clues
Browse files Browse the repository at this point in the history
Previously, if your role changes while evaluating night deaths (e.g.
lycanthropy), any kills you make are potentially credited to your new
role instead of your old role. This could mean, for example, that a
hunter with lycanthropy totem who shot vengeful ghost could make vg
aligned against wolves if the hunter is also attacked the same night,
even though the vg died due to the hunter ability rather than a wolf
kill.

Also rebalance clue tokens a bit in pactbreaker, to match with the
following intent:
- Graveyard has a low chance of gaining tokens, but a high payout
- Forest has a good chance of gaining tokens with a good payout for
  wolves, and a high chance of gaining tokens with a good payout for
  non-wolves, albeit payout is delayed for the latter until they collect
  all of the available wolf evidence
- Square is entirely dependent on multiple people visiting, and has a
  moderate chance of gaining tokens for a moderate payout
- Streets has a high chance of gaining tokens, but a low payout

Depending on your risk/reward evaluations each location should provide a
reason to go there in addition to the main effect of the location.
  • Loading branch information
skizzerz committed Dec 1, 2024
1 parent 1b8ef20 commit c6be875
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/defaultsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 10 additions & 6 deletions src/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"]

Expand Down
24 changes: 14 additions & 10 deletions src/gamemodes/pactbreaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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"])
Expand Down
11 changes: 8 additions & 3 deletions src/trans.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]):
Expand All @@ -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"])
Expand All @@ -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"]))

Expand All @@ -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
Expand Down

0 comments on commit c6be875

Please sign in to comment.