From 7d83dc4d8e3b638c8cee5751fb2afaeb77af3897 Mon Sep 17 00:00:00 2001 From: Ammon Smith Date: Wed, 25 Apr 2018 20:08:39 -0700 Subject: [PATCH] Allow pipers to pass or change targets (#322) Also adds some more info to revealroles. Some miscellaneous fixes included. --- messages/en.json | 3 +++ src/roles/detective.py | 4 ++-- src/roles/dullahan.py | 5 ++-- src/roles/hunter.py | 8 +++++-- src/roles/madscientist.py | 1 - src/roles/piper.py | 47 +++++++++++++++++++++++++++++++++----- src/roles/vengefulghost.py | 4 +++- src/roles/vigilante.py | 13 ++++++----- src/roles/wolf.py | 2 +- 9 files changed, 66 insertions(+), 21 deletions(-) diff --git a/messages/en.json b/messages/en.json index ca6a4bf..bd53edb 100644 --- a/messages/en.json +++ b/messages/en.json @@ -415,6 +415,7 @@ "warlock_pass": "You have chosen not to curse anyone tonight.", "warlock_pass_wolfchat": "\u0002{0}\u0002 has chosen not to curse anyone tonight.", "piper_pass": "You have chosen not to charm anyone tonight.", + "piper_retract": "You have retracted your decision to charm.", "turncoat_already_turned": "You have changed sides yesterday night, and may not do so again tonight.", "turncoat_error": "Please specify which team you wish to side with, villagers or wolves.", "turncoat_success": "You are now siding with \u0002{0}\u0002.", @@ -540,6 +541,8 @@ "assassin_simple": "You are an \u0002assassin\u0002.", "piper_notify": "You are a \u0002piper\u0002. You can select up to two players to charm each night. The charmed players will know each other, but not who charmed them. You win when all other players are charmed. Use \"charm and \" to select the players to charm, or \"charm \" to charm just one player.", "piper_simple": "You are a \u0002piper\u0002.", + "piper_revealroles_charmed": "\u0002charmed players\u0002: {0}", + "piper_revealroles_charming": "charming {0}", "turncoat_notify": "You are a \u0002turncoat\u0002. You can change which team you're siding with every other night. Use \"side villagers\" or \"side wolves\" to select your team. ", "turncoat_current_team": "You are currently siding with \u0002{0}\u0002.", "turncoat_no_team": "If you die before selecting a side, you will not win.", diff --git a/src/roles/detective.py b/src/roles/detective.py index 4a4245b..c5a3521 100644 --- a/src/roles/detective.py +++ b/src/roles/detective.py @@ -16,7 +16,7 @@ INVESTIGATED = UserSet() @command("id", chan=False, pm=True, playing=True, silenced=True, phases=("day",), roles=("detective",)) def investigate(var, wrapper, message): """Investigate a player to determine their exact role.""" - if wrapper.source in INVESTIGATED: + if wrapper.source in INVESTIGATED: wrapper.send(messages["already_investigated"]) return @@ -38,7 +38,7 @@ def investigate(var, wrapper, message): INVESTIGATED.add(wrapper.source) wrapper.send(messages["investigate_success"].format(target, targrole)) debuglog("{0} (detective) ID: {1} ({2})".format(wrapper.source, target, targrole)) - + if random.random() < var.DETECTIVE_REVEALED_CHANCE: # a 2/5 chance (should be changeable in settings) # The detective's identity is compromised! wcroles = var.WOLFCHAT_ROLES diff --git a/src/roles/dullahan.py b/src/roles/dullahan.py index 95fe71b..303f3aa 100644 --- a/src/roles/dullahan.py +++ b/src/roles/dullahan.py @@ -44,6 +44,7 @@ def dullahan_retract(var, wrapper, message): """Removes a dullahan's kill selection.""" if KILLS.pop(wrapper.source, None): wrapper.pm(messages["retracted_kill"]) + debuglog("{0} (dullahan) RETRACT".format(wrapper.source)) @event_listener("player_win") def on_player_win(evt, var, user, role, winner, survived): @@ -129,7 +130,7 @@ def on_exchange(evt, var, actor, target, actor_role, target_role): if actor_role == "dullahan" and target_role != "dullahan" and k is actor: TARGETS[target] = TARGETS.pop(k) - {target} elif target_role == "dullahan" and actor_role != "dullahan" and k is target: - TARGET[actor] = TARGETS.pop(k) - {actor} + TARGETS[actor] = TARGETS.pop(k) - {actor} @event_listener("chk_nightdone") def on_chk_nightdone(evt, var): @@ -205,7 +206,7 @@ def on_revealroles_role(evt, var, user, role): for target in TARGETS[user]: if target.nick in var.DEAD: targets.remove(target) - if targets: + if targets: evt.data["special_case"].append(messages["dullahan_to_kill"].format(", ".join(t.nick for t in targets))) else: evt.data["special_case"].append(messages["dullahan_all_dead"]) diff --git a/src/roles/hunter.py b/src/roles/hunter.py index 4157c0a..ac14711 100644 --- a/src/roles/hunter.py +++ b/src/roles/hunter.py @@ -45,10 +45,13 @@ def hunter_retract(var, wrapper, message): """Removes a hunter's kill selection.""" if wrapper.source not in KILLS and wrapper.source not in PASSED: return - KILLS.pop(wrapper.source, None) + + del KILLS[:wrapper.source:] HUNTERS.discard(wrapper.source) PASSED.discard(wrapper.source) + wrapper.pm(messages["retracted_kill"]) + debuglog("{0} (hunter) RETRACT".format(wrapper.source)) @command("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("hunter",)) def hunter_pass(var, wrapper, message): @@ -56,7 +59,8 @@ def hunter_pass(var, wrapper, message): if wrapper.source in HUNTERS and wrapper.source not in KILLS: wrapper.pm(messages["hunter_already_killed"]) return - KILLS.pop(wrapper.source, None) + + del KILLS[:wrapper.source:] HUNTERS.discard(wrapper.source) PASSED.add(wrapper.source) wrapper.pm(messages["hunter_pass"]) diff --git a/src/roles/madscientist.py b/src/roles/madscientist.py index 3081f5a..707934d 100644 --- a/src/roles/madscientist.py +++ b/src/roles/madscientist.py @@ -46,7 +46,6 @@ def _get_targets(var, pl, user): return (target1, target2) - @event_listener("del_player") def on_del_player(evt, var, user, mainrole, allroles, death_triggers): if not death_triggers or "mad scientist" not in allroles: diff --git a/src/roles/piper.py b/src/roles/piper.py index 11f039c..fc79dbb 100644 --- a/src/roles/piper.py +++ b/src/roles/piper.py @@ -15,10 +15,11 @@ from src.events import Event TOBECHARMED = UserDict() # type: Dict[users.User, Set[users.User]] CHARMED = UserSet() # type: Set[users.User] +PASSED = UserSet() # type: Set[users.User] @command("charm", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("piper",)) def charm(var, wrapper, message): - """Charm a player, slowly leading to your win!""" + """Charm a player or two, slowly leading to your win!""" pieces = re.split(" +", message) target1 = pieces[0] if len(pieces) > 1: @@ -75,6 +76,7 @@ def charm(var, wrapper, message): TOBECHARMED[wrapper.source] = UserSet() TOBECHARMED[wrapper.source].update({target1, target2} - {None}) + PASSED.discard(wrapper.source) if orig2: debuglog("{0} (piper) CHARM {1} ({2}) && {3} ({4})".format(wrapper.source, @@ -85,6 +87,25 @@ def charm(var, wrapper, message): debuglog("{0} (piper) CHARM {1} ({2})".format(wrapper.source, target1, get_main_role(target1))) wrapper.send(messages["charm_success"].format(orig1)) +@command("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("piper",)) +def pass_cmd(var, wrapper, message): + """Do not charm anyone tonight.""" + del TOBECHARMED[:wrapper.source:] + PASSED.add(wrapper.source) + + wrapper.send(messages["piper_pass"]) + debuglog("{0} (piper) PASS".format(wrapper.source)) + +@command("retract", "r", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("piper",)) +def retract(var, wrapper, message): + """Remove your decision to charm people.""" + if wrapper.source in TOBECHARMED or wrapper.source in PASSED: + del TOBECHARMED[:wrapper.source:] + PASSED.discard(wrapper.source) + + wrapper.send(messages["piper_retract"]) + debuglog("{0} (piper) RETRACT".format(wrapper.source)) + @event_listener("chk_win", priority=2) def on_chk_win(evt, var, rolemap, mainroles, lpl, lwolves, lrealwolves): # lpl doesn't included wounded/sick people or consecrating priests @@ -113,9 +134,7 @@ def on_player_win(evt, var, player, mainrole, winner, survived): @event_listener("del_player") def on_del_player(evt, var, player, mainrole, allroles, death_triggers): CHARMED.discard(player) - x = TOBECHARMED.pop(player, None) - if x is not None: - x.clear() + del TOBECHARMED[:player:] @event_listener("transition_day_begin") def on_transition_day_begin(evt, var): @@ -157,10 +176,11 @@ def on_transition_day_begin(evt, var): CHARMED.update(tocharm) TOBECHARMED.clear() + PASSED.clear() @event_listener("chk_nightdone") def on_chk_nightdone(evt, var): - evt.data["actedcount"] += len(TOBECHARMED.keys()) + evt.data["actedcount"] += len(TOBECHARMED) + len(PASSED) evt.data["nightroles"].extend(get_all_players(("piper",))) @event_listener("transition_night_end", priority=2) @@ -180,8 +200,14 @@ def on_exchange(evt, var, actor, target, actor_role, target_role): # if we're shifting piper around, ensure that the new piper isn't charmed if actor_role == "piper": CHARMED.discard(target) + if target_role != "piper": + del TOBECHARMED[:actor:] + PASSED.discard(actor) if target_role == "piper": CHARMED.discard(actor) + if actor_role != "piper": + del TOBECHARMED[:target:] + PASSED.discard(target) @event_listener("get_special") def on_get_special(evt, var): @@ -196,10 +222,19 @@ def on_acted(evt, var, target, spy): def on_reset(evt, var): CHARMED.clear() TOBECHARMED.clear() + PASSED.clear() @event_listener("revealroles") def on_revealroles(evt, var, wrapper): if CHARMED: - evt.data["output"].append("\u0002charmed players\u0002: {0}".format(", ".join(p.nick for p in CHARMED))) + nicks = ", ".join(p.nick for p in CHARMED) + evt.data["output"].append(messages["piper_revealroles_charmed"].format(nicks)) + +@event_listener("revealroles_role") +def on_revealroles_role(evt, var, user, role): + players = TOBECHARMED.get(user) + if players: + nicks = ", ".join(p.nick for p in players) + evt.data["special_case"].append(messages["piper_revealroles_charming"].format(nicks)) # vim: set sw=4 expandtab: diff --git a/src/roles/vengefulghost.py b/src/roles/vengefulghost.py index 48fdf21..a53c4b9 100644 --- a/src/roles/vengefulghost.py +++ b/src/roles/vengefulghost.py @@ -49,16 +49,18 @@ def vg_kill(var, wrapper, message): wrapper.pm(messages["player_kill"].format(orig)) - debuglog("{0} (vengeful ghost) KILL: {1} ({2})".format(wrapper.source.nick, target, get_main_role(target))) + debuglog("{0} (vengeful ghost) KILL: {1} ({2})".format(wrapper.source, target, get_main_role(target))) @command("retract", "r", chan=False, pm=True, playing=False, phases=("night",)) def vg_retract(var, wrapper, message): """Removes a vengeful ghost's kill selection.""" if wrapper.source not in GHOSTS: return + if wrapper.source in KILLS: del KILLS[wrapper.source] wrapper.pm(messages["retracted_kill"]) + debuglog("{0} (vengeful ghost) RETRACT".format(wrapper.source)) @event_listener("get_participants") def on_get_participants(evt, var): diff --git a/src/roles/vigilante.py b/src/roles/vigilante.py index 5cac890..60024f7 100644 --- a/src/roles/vigilante.py +++ b/src/roles/vigilante.py @@ -30,7 +30,6 @@ def vigilante_kill(var, wrapper, message): PASSED.discard(wrapper.source) wrapper.send(messages["player_kill"].format(orig)) - debuglog("{0} (vigilante) KILL: {1} ({2})".format(wrapper.source, target, get_main_role(target))) @command("retract", "r", chan=False, pm=True, playing=True, phases=("night",), roles=("vigilante",)) @@ -39,14 +38,16 @@ def vigilante_retract(var, wrapper, message): if wrapper.source not in KILLS and wrapper.source not in PASSED: return - KILLS.pop(wrapper.source, None) + del KILLS[:wrapper.source:] PASSED.discard(wrapper.source) + wrapper.send(messages["retracted_kill"]) + debuglog("{0} (vigilante) RETRACT".format(wrapper.source)) @command("pass", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("vigilante",)) def vigilante_pass(var, wrapper, message): """Do not kill anyone tonight as a vigilante.""" - KILLS.pop(wrapper.source, None) + del KILLS[:wrapper.source:] PASSED.add(wrapper.source) wrapper.send(messages["hunter_pass"]) @@ -55,7 +56,7 @@ def vigilante_pass(var, wrapper, message): @event_listener("del_player") def on_del_player(evt, var, user, mainrole, allroles, death_triggers): PASSED.discard(user) - KILLS.pop(user, None) + del KILLS[:user:] for vigilante, target in list(KILLS.items()): if target is user: vigilante.send(messages["hunter_discard"]) @@ -84,8 +85,8 @@ def on_transition_day(evt, var): @event_listener("exchange_roles") def on_exchange(evt, var, actor, target, actor_role, target_role): - KILLS.pop(actor, None) - KILLS.pop(target, None) + del KILLS[:actor:] + del KILLS[:target:] PASSED.discard(actor) PASSED.discard(target) diff --git a/src/roles/wolf.py b/src/roles/wolf.py index 4bbedf8..b61df90 100644 --- a/src/roles/wolf.py +++ b/src/roles/wolf.py @@ -62,7 +62,7 @@ def wolf_kill(cli, nick, chan, rest): victim = get_victim(cli, nick, victim, False) if not victim: return - + if victim == nick: pm(cli, nick, messages["no_suicide"]) return