diff --git a/src/roles/dullahan.py b/src/roles/dullahan.py index 6353b97..d5502b4 100644 --- a/src/roles/dullahan.py +++ b/src/roles/dullahan.py @@ -239,4 +239,13 @@ def on_reset(evt, var): KILLS.clear() TARGETS.clear() +@event_listener("get_role_metadata") +def on_get_role_metadata(evt, cli, var, kind): + if kind == "night_kills": + num = 0 + for dull in var.ROLES["dullahan"]: + if TARGETS[dull] - var.DEAD: + num += 1 + evt.data["dullahan"] = num + # vim: set sw=4 expandtab: diff --git a/src/roles/hunter.py b/src/roles/hunter.py index 0b504e9..f9fae15 100644 --- a/src/roles/hunter.py +++ b/src/roles/hunter.py @@ -160,4 +160,12 @@ def on_reset(evt, var): PASSED.clear() HUNTERS.clear() +@event_listener("get_role_metadata") +def on_get_role_metadata(evt, cli, var, kind): + if kind == "night_kills": + # hunters is the set of all hunters that have not killed in a *previous* night + # (if they're in both HUNTERS and KILLS, then they killed tonight and should be counted) + hunters = (set(var.ROLES["hunter"]) - HUNTERS) | set(KILLS.keys()) + evt.data["hunter"] = len(hunters) + # vim: set sw=4 expandtab: diff --git a/src/roles/shaman.py b/src/roles/shaman.py index 2d31f3b..c22ec0e 100644 --- a/src/roles/shaman.py +++ b/src/roles/shaman.py @@ -645,4 +645,12 @@ def on_frole_role(evt, cli, var, who, role, oldrole, args): TOTEMS[shaman] = t break +@event_listener("get_role_metadata") +def on_get_role_metadata(evt, cli, var, kind): + if kind == "night_kills": + # only add shamans here if they were given a death totem + # even though retribution kills, it is given a special kill message + # note that all shaman types (shaman/CS/wolf shaman) are lumped under the "shaman" key + evt.data["shaman"] = list(TOTEMS.values()).count("death") + # vim: set sw=4 expandtab: diff --git a/src/roles/traitor.py b/src/roles/traitor.py index 77d4ea0..79b4154 100644 --- a/src/roles/traitor.py +++ b/src/roles/traitor.py @@ -28,13 +28,36 @@ def on_get_final_role(evt, cli, var, nick, role): if role == "traitor" and evt.data["role"] == "wolf": evt.data["role"] = "traitor" -@event_listener("update_stats") -def on_update_stats(evt, cli, var, nick, nickrole, nickreveal, nicktpls): +@event_listener("update_stats", priority=1) +def on_update_stats1(evt, cli, var, nick, nickrole, nickreveal, nicktpls): if nickrole == var.DEFAULT_ROLE and var.HIDDEN_TRAITOR: evt.data["possible"].add("traitor") + +@event_listener("update_stats", priority=3) +def on_update_stats3(evt, cli, var, nick, nickrole, nickreveal, nicktpls): # if this is a night death and we know for sure that wolves (and only wolves) # killed, then that kill cannot be traitor as long as they're in wolfchat. - # TODO: need to figure out how to actually piece this together, but will - # likely require splitting off every other role first. + # ismain True = night death, False = chain death; chain deaths can be traitors + # even if only wolves killed, so we short-circuit there as well + # TODO: an observant user will be able to determine if traitor dies due to luck/misdirection totem + # redirecting a wolf kill onto traitor + if "traitor" not in evt.data["possible"] or not evt.params.ismain or nickrole == "traitor": + return + if var.PHASE == "day" and var.GAMEPHASE == "night": + mevt = Event("get_role_metadata", {}) + mevt.dispatch(cli, var, "night_kills") + nonwolf = 0 + total = 0 + for role, num in mevt.data.items(): + if role != "wolf": + nonwolf += num + total += num + if nonwolf == 0: + evt.data["possible"].discard("traitor") + return + # TODO: this doesn't account for everything, for example if there was a hunter kill + # and a wolf kill, and a wolf + villager died, we know the villager was the wolf kill + # and therefore cannot be traitor. However, we currently do not have the logic to deduce this + # vim: set sw=4 expandtab: diff --git a/src/roles/vengefulghost.py b/src/roles/vengefulghost.py index 77294dc..0cce9de 100644 --- a/src/roles/vengefulghost.py +++ b/src/roles/vengefulghost.py @@ -248,4 +248,9 @@ def on_reset(evt, var): KILLS.clear() GHOSTS.clear() +@event_listener("get_role_metadata") +def on_get_role_metadata(evt, cli, var, kind): + if kind == "night_kills": + evt.data["vengeful ghost"] = sum(1 for against in GHOSTS.values() if against[0] != "!") + # vim: set sw=4 expandtab: diff --git a/src/roles/vigilante.py b/src/roles/vigilante.py index a2a19f3..3c84690 100644 --- a/src/roles/vigilante.py +++ b/src/roles/vigilante.py @@ -145,4 +145,9 @@ def on_reset(evt, var): KILLS.clear() PASSED.clear() +@event_listener("get_role_metadata") +def on_get_role_metadata(evt, cli, var, kind): + if kind == "night_kills": + evt.data["vigilante"] = len(var.ROLES["vigilante"]) + # vim: set sw=4 expandtab: diff --git a/src/roles/wolf.py b/src/roles/wolf.py index c422b27..e87e292 100644 --- a/src/roles/wolf.py +++ b/src/roles/wolf.py @@ -402,4 +402,22 @@ def on_begin_day(evt, cli, var): def on_reset(evt, var): KILLS.clear() +@event_listener("get_role_metadata") +def on_get_role_metadata(evt, cli, var, kind): + if kind == "night_kills": + if var.DISEASED_WOLVES: + evt.data["wolf"] = 0 + elif var.ANGRY_WOLVES: + evt.data["wolf"] = 2 + else: + evt.data["wolf"] = 1 + # TODO: split into alpha + if var.ALPHA_ENABLED: + # alpha wolf gives an extra kill; note that we consider someone being + # bitten a "kill" for this metadata kind as well + # rolled into wolf instead of as a separate alpha wolf key for ease of implementing + # special logic for wolf kills vs non-wolf kills (as when alpha kills it is treated + # as any other wolf kill). + evt.data["wolf"] += 1 + # vim: set sw=4 expandtab: diff --git a/src/wolfgame.py b/src/wolfgame.py index 8d620c6..dd9f18f 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -2747,7 +2747,13 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death event.dispatch(cli, var, nick, nickrole, nicktpls, evt_death_triggers) # update var.ROLE_STATS - event = Event("update_stats", {"possible": {nickrole, nickreveal}, "known_role": False}) + # Event priorities: + # 1 = Expanding the possible set (e.g. traitor would add themselves if nickrole is villager) + # 3 = Removing from the possible set (e.g. can't be traitor if was a night kill and only wolves could kill at night), + # 5 = Setting known_role to True if the role is actually known for sure publically (e.g. revealing totem) + # 2 and 4 are not used by included roles, but may be useful expansion points for custom roles to modify stats + event = Event("update_stats", {"possible": {nickrole, nickreveal}, "known_role": False}, + killer_role=killer_role, ismain=ismain) event.dispatch(cli, var, nick, nickrole, nickreveal, nicktpls) # Given the set of possible roles this nick could be (or its actual role if known_role is True), # figure out the set of roles that need deducting from their counts in var.ROLE_STATS