Skip to content

Commit

Permalink
Various QoL and tweaks
Browse files Browse the repository at this point in the history
- !myrole now displays your secondary roles
- Retribution totem messages cleaned up to clarify it applies the next
  day and so that when it fires during daytime it doesn't incorrectly
  say "last night."
- Players with fallen angel as a secondary role no longer assist
  wolfteam in punching through protections, however their own kills will
  bypass all protections.
- Fix incorrect check in retribution totem that could lead to errors in
  the event of the killer already being dead (e.g. assassin death killing
  someone else with retribution).
  • Loading branch information
skizzerz committed Aug 16, 2024
1 parent 5edbe1d commit 57b22b0
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 17 deletions.
11 changes: 7 additions & 4 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -734,9 +734,11 @@
"night_death_angel": "{1:@} was attacked last night, but luckily, the {=guardian angel!role} was on duty.",
"night_death_bodyguard": "{2:@} sacrificed their life to guard that of another.",
"lycan_turn": "HOOOOOOOOOWL. You have become... a {=wolf!role}!",
"totem_banish": "{0:@}'s totem emitted a brilliant flash of light last night. It appears that {1:@}'s spirit was driven away by the flash.",
"totem_death": "{0:@}'s totem emitted a brilliant flash of light last night. The dead body of {1:@}, {2!role:article} {2!role:bold}, was found at the scene.",
"totem_death_no_reveal": "{0:@}'s totem emitted a brilliant flash of light last night. The dead body of {1:@} was found at the scene.",
"retribution_totem_night_banish": "{0:@}'s totem emitted a brilliant flash of light last night. It appears that {1:@}'s spirit was driven away by the flash.",
"retribution_totem_night_death": "{0:@}'s totem emitted a brilliant flash of light last night. The dead body of {1:@}, {2!role:article} {2!role:bold}, was found at the scene.",
"retribution_totem_night_death_no_reveal": "{0:@}'s totem emitted a brilliant flash of light last night. The dead body of {1:@} was found at the scene.",
"retribution_totem_day_death": "{0:@}'s totem emits a brilliant flash of light. When the villagers are able to see again, they discover that {1:@}, {2!role:article} {2!role:bold}, has fallen over dead.",
"retribution_totem_day_death_no_reveal": "{0:@}'s totem emits a brilliant flash of light. When the villagers are able to see again, they discover that {1:@} has fallen over dead.",
"death": "The dead body of {0:@}, {1!role:article} {1!role:bold}, is found. Those remaining mourn the tragedy.",
"death_no_reveal": "The dead body of {0:@} is found. Those remaining mourn the tragedy.",
"visited_victim": "{0:@}, {1!role:article} {1!role:bold}, made the unfortunate mistake of visiting the victim's house last night and is now dead.",
Expand Down Expand Up @@ -995,7 +997,7 @@
"lycanthropy_totem": "If the player who is given the lycanthropy totem is targeted by wolves tomorrow night, they will become a wolf.",
"luck_totem": "If the player who is given the luck totem is targeted tomorrow night, one of the players adjacent to them will be targeted instead.",
"pestilence_totem": "If the player who is given the pestilence totem is killed by wolves tomorrow night, the wolves will not be able to kill the night after.",
"retribution_totem": "If the player who is given the retribution totem will die tonight, they also kill anyone who killed them.",
"retribution_totem": "If the player who is given the retribution totem is killed tonight or tomorrow by anything except a village vote, they also kill one of the people who killed them.",
"misdirection_totem": "If the player who is given the misdirection totem attempts to use a power the following day or night, they will target a player adjacent to their intended target instead of the player they targeted.",
"deceit_totem": "If the player who is given the deceit totem is a seer or an oracle, or is seen by a seer or an oracle, the vision will be shifted: if the person would be seen as wolf, they are instead seen as a villager; otherwise, they are seen as a wolf.",
"hunter_notify": "You are {=hunter!role:article} {=hunter!role:bold}. Once per game, you may kill another player with \"{=kill!command} <nick>\". If you do not wish to kill anyone tonight, use \"{=pass!command}\" instead.",
Expand Down Expand Up @@ -1270,6 +1272,7 @@
"cat_land": "The cat lands on its [b]feet[/b].",
"vengeful_role": "You are {=vengeful ghost!role:article} {=vengeful ghost!role:bold} who is against the {0!role:bold:plural}.",
"show_role": "You are {0!role:article} {0!role:bold}.",
"show_secondary_roles": "You are also the following secondary roles: {0:join(!role:bold)}.",
"original_wolves": "Original wolves: {0:join}",
"assassin_targeting": "You are {=assassin!role:article} {=assassin!role:bold} and targeting {0}.",
"assassin_no_target": "You are {=assassin!role:article} {=assassin!role:bold} and do not currently have a target.",
Expand Down
8 changes: 7 additions & 1 deletion src/roles/cursed.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from src.functions import get_all_players, get_main_role
from src.gamestate import GameState
from src.messages import messages

from src.users import User

@event_listener("see")
def on_see(evt: Event, var: GameState, seer, target):
Expand All @@ -24,3 +24,9 @@ def on_send_role(evt: Event, var: GameState):
for player in cursed:
if get_main_role(var, player) == "cursed villager" or player in wolves:
player.send(messages["cursed_notify"])

@event_listener("myrole")
def on_myrole(evt: Event, var: GameState, player: User):
wolves = get_all_players(var, Wolfchat)
if player not in wolves:
evt.data["secondary"].discard("cursed villager")
8 changes: 6 additions & 2 deletions src/roles/fallenangel.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from src import status
from src.events import Event, event_listener
from src.functions import get_all_players
from src.functions import get_players, get_all_roles
from src.gamestate import GameState
from src.roles.helper.wolves import register_wolf
from src.users import User
Expand All @@ -13,7 +13,11 @@

@event_listener("try_protection")
def on_try_protection(evt: Event, var: GameState, target: User, attacker: User, attacker_role: str, reason: str):
if attacker_role == "wolf" and get_all_players(var, ("fallen angel",)):
# main role FAs punch through protections for shared wolf kills,
# secondary FAs only punch through protections for their own kills
main_fas = get_players(var, ("fallen angel",))
all_roles = get_all_roles(var, attacker) if attacker is not None else set()
if (attacker_role == "wolf" and main_fas) or "fallen angel" in all_roles:
status.remove_all_protections(var, target, attacker=attacker, attacker_role="fallen angel", reason="fallen_angel")
evt.prevent_default = True

Expand Down
14 changes: 9 additions & 5 deletions src/roles/helper/shamans.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
match_totem)
from src.gamestate import GameState
from src.messages import messages
from src.status import try_misdirection, try_protection, try_exchange, is_dying, add_dying
from src.status import try_misdirection, try_protection, try_exchange, add_dying
from src.dispatcher import MessageDispatcher
from src.users import User
from src.locations import move_player_home
Expand Down Expand Up @@ -472,29 +472,33 @@ def on_del_player(evt: Event, var: GameState, player: User, all_roles: set[str],
if not death_triggers or player not in RETRIBUTION:
return
loser = evt.params.killer
all_players = get_players(var)
if loser is None and evt.params.killer_role == "wolf" and evt.params.reason == "night_kill":
pl = get_players(var, Wolf & Killer)
if pl:
loser = random.choice(pl)
if loser is None or is_dying(var, loser):
if loser is None or loser not in all_players:
# person that killed us is already dead?
return

ret_evt = Event("retribution_kill", {"target": loser, "message": []})
ret_evt.dispatch(var, player, loser)
loser = ret_evt.data["target"]
channels.Main.send(*ret_evt.data["message"])
if loser is not None and is_dying(var, loser):
if loser not in all_players:
# another check for the person already being dead since it may have changed via the event
loser = None
if loser is not None:
protected = try_protection(var, loser, player, evt.params.main_role, "retribution_totem")
if protected is not None:
channels.Main.send(*protected)
return

to_send = "totem_death_no_reveal"
# message keys: retribution_totem_night_death, retribution_totem_day_death,
# retribution_totem_night_death_no_reveal, retribution_totem_day_death_no_reveal
to_send = f"retribution_totem_{var.current_phase}_death_no_reveal"
if var.role_reveal in ("on", "team"):
to_send = "totem_death"
to_send = f"retribution_totem_{var.current_phase}_death"
channels.Main.send(messages[to_send].format(player, loser, get_reveal_role(var, loser)))
add_dying(var, loser, evt.params.main_role, "retribution_totem", killer=player)

Expand Down
3 changes: 2 additions & 1 deletion src/roles/vengefulghost.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ def on_retribution_kill(evt: Event, var: GameState, victim: User, orig_target: U
if target in GHOSTS:
drivenoff[target] = GHOSTS[target]
GHOSTS[target] = "!" + GHOSTS[target]
evt.data["message"].append(messages["totem_banish"].format(victim, target))
# VGs only kill at night so we only need a night message
evt.data["message"].append(messages["retribution_totem_night_banish"].format(victim, target))
evt.data["target"] = None

@event_listener("get_participant_role")
Expand Down
7 changes: 5 additions & 2 deletions src/roles/wildchild.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,11 @@ def on_swap_role_state(evt: Event, var: GameState, actor: User, target: User, ro

@event_listener("myrole")
def on_myrole(evt: Event, var: GameState, user: User):
if user in IDOLS and user not in get_players(var, get_wolfchat_roles()):
evt.data["messages"].append(messages["wild_child_idol"].format(IDOLS[user]))
if user in IDOLS:
if user not in get_players(var, get_wolfchat_roles()):
evt.data["messages"].append(messages["wild_child_idol"].format(IDOLS[user]))
else:
evt.data["secondary"].discard("wild child")

@event_listener("del_player")
def on_del_player(evt: Event, var: GameState, player: User, all_roles: set[str], death_triggers: bool):
Expand Down
8 changes: 6 additions & 2 deletions src/wolfgame.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@

from src.functions import (
get_players, get_all_players, get_participants,
get_main_role, get_reveal_role,
get_main_role, get_reveal_role, get_all_roles,
match_role, match_mode
)

Expand Down Expand Up @@ -1061,15 +1061,19 @@ def myrole(wrapper: MessageDispatcher, message: str):
return

role = get_main_role(var, wrapper.source)
# we want secondary to be a set, not a Category, so any sets need a .roles accessor to get at the underlying roles
secondary = get_all_roles(var, wrapper.source) - {role} - Hidden.roles
if role in Hidden:
role = var.hidden_role

evt = Event("myrole", {"role": role, "messages": []})
evt = Event("myrole", {"role": role, "secondary": secondary, "messages": []})
if not evt.dispatch(var, wrapper.source):
return
role = evt.data["role"]

wrapper.pm(messages["show_role"].format(role))
if secondary:
wrapper.pm(messages["show_secondary_roles"].format(sorted(secondary)))

for msg in evt.data["messages"]:
wrapper.pm(msg)
Expand Down

0 comments on commit 57b22b0

Please sign in to comment.