diff --git a/messages/en.json b/messages/en.json index 09d4bc7..780615e 100644 --- a/messages/en.json +++ b/messages/en.json @@ -366,10 +366,12 @@ "sorcerer_success": "After casting your ritual, you determine that \u0002{0}\u0002 is a{1} \u0002{2}\u0002!", "sorcerer_fail": "After casting your ritual, you determine that \u0002{0}\u0002 does not have paranormal senses.", "sorcerer_success_wolfchat": "\u0002{0}\u0002 is observing \u0002{1}\u0002.", - "already_investigated": "You may only investigate one person per round.", - "no_investigate_self": "Investigating yourself would be a waste.", + "already_investigated": "You may only investigate once per day.", + "no_investigate_self": "You may not investigate yourself.", "investigate_success": "The results of your investigation have returned. \u0002{0}\u0002 is a... \u0002{1}\u0002!", - "investigator_reveal": "Someone accidentally drops a paper. The paper reveals that \u0002{0}\u0002 is the detective!", + "detective_reveal": "Someone accidentally drops a paper. The paper reveals that \u0002{0}\u0002 is the detective!", + "investigator_results_same": "Your investigation has revealed that \u0002{0}\u0002 and \u0002{1}\u0002 are friends.", + "investigator_results_different": "Your investigation has revealed that \u0002{0}\u0002 and \u0002{1}\u0002 do not trust each other.", "harlot_already_visited": "You are already spending the night with \u0002{0}\u0002.", "harlot_success": "You are spending the night with \u0002{0}\u0002. Have a good time!", "harlot_not_self": "You may not visit yourself. Use \"pass\" to choose to not visit anyone tonight.", @@ -475,6 +477,9 @@ "detective_chance": " Each time you use your ability, you risk a {0}% chance of having your identity revealed to the wolves.", "detective_notify": "You are a \u0002detective\u0002. It is your job to determine all the wolves and traitors. During the day you can see the true identity of all players, even traitors, by using \"id \" in PM.{0}", "detective_simple": "You are a \u0002detective\u0002.", + "investigator_notify": "You are an \u0002investigator\u0002. During the day, you can see if two people are on the same side by using \"id and \" in PM.", + "investigator_simple": "You are an \u0002investigator\u0002.", + "investigator_help": "Investigate two different people by using \"id and \" in PM.", "drunk_notification": "You have been drinking too much! You are the \u0002village drunk\u0002.", "drunk_simple": "You are the \u0002village drunk\u0002.", "mystic_notify": "You are the \u0002mystic\u0002. Each night you divine the number of evil villagers (including wolves) that are still alive.", diff --git a/src/gamemodes.py b/src/gamemodes.py index 680bac1..4c67ba8 100644 --- a/src/gamemodes.py +++ b/src/gamemodes.py @@ -1290,7 +1290,7 @@ class MudkipMode(GameMode): self.ROLE_INDEX = ( 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 ) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) self.ROLE_GUIDE.update({# village roles - "detective" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), + "investigator" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), "guardian angel" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), "shaman" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), "vengeful ghost" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ), @@ -1339,7 +1339,7 @@ class MudkipMode(GameMode): avail = len(evt.params.voters) voted = sum(map(len, evt.data["votelist"].values())) - if avail != voted and not evt.params.timeout: + if (avail != voted and not evt.params.timeout) or voted == 0: return majority = avail // 2 + 1 diff --git a/src/roles/detective.py b/src/roles/detective.py index 453d5f1..f34a713 100644 --- a/src/roles/detective.py +++ b/src/roles/detective.py @@ -55,7 +55,7 @@ def investigate(cli, nick, chan, rest): else: wcroles = var.WOLF_ROLES | {"traitor"} - mass_privmsg(cli, list_players(wcroles), messages["investigator_reveal"].format(nick)) + mass_privmsg(cli, list_players(wcroles), messages["detective_reveal"].format(nick)) debuglog("{0} ({1}) PAPER DROP".format(nick, get_role(nick))) @event_listener("rename_player") diff --git a/src/roles/investigator.py b/src/roles/investigator.py new file mode 100644 index 0000000..46c5558 --- /dev/null +++ b/src/roles/investigator.py @@ -0,0 +1,141 @@ +import re +import random +import itertools +import math +from collections import defaultdict + +import botconfig +import src.settings as var +from src.utilities import * +from src import channels, users, debuglog, errlog, plog +from src.functions import get_players, get_all_players, get_main_role, get_reveal_role, get_target +from src.decorators import command, event_listener +from src.messages import messages +from src.events import Event + +INVESTIGATED = set() + +@command("id", chan=False, pm=True, playing=True, silenced=True, phases=("day",), roles=("investigator",)) +def investigate(var, wrapper, message): + """Investigate two players to determine their relationship to each other.""" + if wrapper.source in INVESTIGATED: + wrapper.pm(messages["already_investigated"]) + return + pieces = re.split(" +", message) + if len(pieces) == 1: + wrapper.pm(messages["investigator_help"]) + return + target1 = pieces[0] + target2 = pieces[1] + if target2.lower() == "and" and len(pieces) > 2: + target2 = pieces[2] + target1 = get_target(var, wrapper, target1, not_self_message="no_investigate_self") + target2 = get_target(var, wrapper, target2, not_self_message="no_investigate_self") + if not target1 or not target2: + return + elif target1 is target2: + wrapper.pm(messages["investigator_help"]) + return + + evt = Event("targeted_command", {"target": target1, "misdirection": True, "exchange": True}) + evt.dispatch(var, "identify", wrapper.source, target1, frozenset({"info", "immediate"})) + if evt.prevent_default: + return + target1 = evt.data["target"] + + evt = Event("targeted_command", {"target": target2, "misdirection": True, "exchange": True}) + evt.dispatch(var, "identify", wrapper.source, target2, frozenset({"info", "immediate"})) + if evt.prevent_default: + return + target2 = evt.data["target"] + + t1role = get_main_role(target1) + t2role = get_main_role(target2) + # FIXME: split into amnesiac via investigate event once amnesiac is split + if t1role == "amnesiac": + t1role = var.AMNESIAC_ROLES[target1.nick] + if t2role == "amnesiac": + t2role = var.AMNESIAC_ROLES[target2.nick] + + evt = Event("investigate", {"role": t1role}) + evt.dispatch(wrapper.client, var, wrapper.source.nick, target1.nick) # FIXME + t1role = evt.data["role"] + + evt = Event("investigate", {"role": t2role}) + evt.dispatch(wrapper.client, var, wrapper.source.nick, target2.nick) # FIXME + t2role = evt.data["role"] + + # FIXME: make a standardized way of getting team affiliation, and make + # augur and investigator both use it (and make it events-aware so other + # teams can be added more easily) + if t1role in var.WOLFTEAM_ROLES: + t1role = "red" + elif t1role in var.TRUE_NEUTRAL_ROLES: + t1role = "grey" + else: + t1role = "blue" + + if t2role in var.WOLFTEAM_ROLES: + t2role = "red" + elif t2role in var.TRUE_NEUTRAL_ROLES: + t2role = "grey" + else: + t2role = "blue" + + same = t1role == t2role + # FIXME: split into matchmaker once that is split and make this an event + if target2.nick in var.LOVERS.get(target1.nick, set()): + same = True + + if same: + wrapper.pm(messages["investigator_results_same"].format(target1, target2)) + else: + wrapper.pm(messages["investigator_results_different"].format(target1, target2)) + + INVESTIGATED.add(wrapper.source) + debuglog("{0} (investigator) ID: {1} ({2}) and {3} ({4}) as {5}".format( + wrapper.source, target1, get_main_role(target1), target2, get_main_role(target2), + "same" if same else "different")) + +@event_listener("swap_player") +def on_swap(evt, var, old_user, user): + if old_user in INVESTIGATED: + INVESTIGATED.discard(old_user) + INVESTIGATED.add(user) + +@event_listener("del_player") +def on_del_player(evt, var, user, mainrole, allroles, death_triggers): + INVESTIGATED.discard(user) + +@event_listener("get_special") +def on_get_special(evt, var): + evt.data["special"].update(get_players(("investigator",))) + +@event_listener("exchange_roles") +def on_exchange(evt, var, actor, target, actor_role, target_role): + if actor_role == "investigator" and target_role != "investigator": + INVESTIGATED.discard(actor) + elif target_role == "investigator" and actor_role != "investigator": + INVESTIGATED.discard(targe) + +@event_listener("transition_night_end", priority=2) +def on_transition_night_end(evt, var): + ps = get_players() + for inv in var.ROLES["investigator"]: + pl = ps[:] + random.shuffle(pl) + pl.remove(inv) + to_send = "investigator_notify" + if inv.prefers_simple(): + to_send = "investigator_simple" + inv.send(messages[to_send], "Players: " + ", ".join(p.nick for p in pl), sep="\n") + +@event_listener("transition_night_begin") +def on_transition_night_begin(evt, cli, var): + INVESTIGATED.clear() + +@event_listener("reset") +def on_reset(evt, var): + INVESTIGATED.clear() + +# vim: set sw=4 expandtab: diff --git a/src/settings.py b/src/settings.py index 47f47d5..585e2e2 100644 --- a/src/settings.py +++ b/src/settings.py @@ -262,6 +262,7 @@ ROLE_GUIDE = OrderedDict([ # This is order-sensitive - many parts of the code re ("vigilante" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), ("augur" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 )), ("detective" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 )), + ("investigator" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), ("prophet" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), ("guardian angel" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), ("bodyguard" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 )),