banned/src/roles/angel.py

306 lines
13 KiB
Python

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 debuglog, errlog, plog
from src.decorators import cmd, event_listener
from src.messages import messages
from src.events import Event
GUARDED = {} # type: Dict[str, str]
LASTGUARDED = {} # type: Dict[str, str]
PASSED = set() # type: Set[str]
@cmd("guard", "protect", "save", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=("bodyguard", "guardian angel"))
def guard(cli, nick, chan, rest):
"""Guard a player, preventing them from being killed that night."""
if nick in GUARDED:
pm(cli, nick, messages["already_protecting"])
return
role = get_role(nick)
self_in_list = role == "guardian angel" and var.GUARDIAN_ANGEL_CAN_GUARD_SELF
victim = get_victim(cli, nick, re.split(" +",rest)[0], False, self_in_list)
if not victim:
return
if (role == "bodyguard" or not var.GUARDIAN_ANGEL_CAN_GUARD_SELF) and victim == nick:
pm(cli, nick, messages["cannot_guard_self"])
return
if role == "guardian angel" and LASTGUARDED.get(nick) == victim:
pm(cli, nick, messages["guardian_target_another"].format(victim))
return
# self-guard ignores luck/misdirection/exchange totem
evt = Event("targeted_command", {"target": victim, "misdirection": victim != nick, "exchange": victim != nick})
if not evt.dispatch(cli, var, "guard", nick, victim, frozenset({"beneficial"})):
return
victim = evt.data["target"]
GUARDED[nick] = victim
LASTGUARDED[nick] = victim
if victim == nick:
pm(cli, nick, messages["guardian_guard_self"])
else:
pm(cli, nick, messages["protecting_target"].format(GUARDED[nick]))
pm(cli, victim, messages["target_protected"])
debuglog("{0} ({1}) GUARD: {2} ({3})".format(nick, role, victim, get_role(victim)))
chk_nightdone(cli)
@cmd("pass", chan=False, pm=True, playing=True, phases=("night",), roles=("bodyguard", "guardian angel"))
def pass_cmd(cli, nick, chan, rest):
"""Decline to use your special power for that night."""
if nick in GUARDED:
pm(cli, nick, messages["already_protecting"])
return
PASSED.add(nick)
pm(cli, nick, messages["guardian_no_protect"])
debuglog("{0} ({1}) PASS".format(nick, get_role(nick)))
chk_nightdone(cli)
@event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick):
for dictvar in (GUARDED, LASTGUARDED):
kvp = {}
for a,b in dictvar.items():
if a == prefix:
if b == prefix:
kvp[nick] = nick
else:
kvp[nick] = b
elif b == prefix:
kvp[a] = nick
dictvar.update(kvp)
if prefix in dictvar:
del dictvar[prefix]
if prefix in PASSED:
PASSED.discard(prefix)
PASSED.add(nick)
@event_listener("del_player")
def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
if var.PHASE == "night" and nick in GUARDED:
pm(cli, GUARDED[nick], messages["protector_disappeared"])
for dictvar in (GUARDED, LASTGUARDED):
for k,v in list(dictvar.items()):
if nick in (k, v):
del dictvar[k]
if nick in PASSED:
PASSED.discard(nick)
@event_listener("night_acted")
def on_acted(evt, cli, var, nick, sender):
if nick in GUARDED:
evt.data["acted"] = True
@event_listener("get_special")
def on_get_special(evt, cli, var):
evt.data["special"].update(list_players(("guardian angel", "bodyguard")))
@event_listener("exchange_roles")
def on_exchange(evt, cli, var, actor, nick, actor_role, nick_role):
if actor_role in ("bodyguard", "guardian angel"):
if actor in GUARDED:
pm(cli, GUARDED.pop(actor), messages["protector disappeared"])
if actor in LASTGUARDED:
del LASTGUARDED[actor]
if nick_role in ("bodyguard", "guardian angel"):
if nick in GUARDED:
pm(cli, GUARDED.pop(nick), messages["protector disappeared"])
if nick in LASTGUARDED:
del LASTGUARDED[nick]
@event_listener("chk_nightdone")
def on_chk_nightdone(evt, cli, var):
evt.data["actedcount"] += len(GUARDED) + len(PASSED)
evt.data["nightroles"].extend(list_players(("guardian angel", "bodyguard")))
@event_listener("transition_day", priority=4.2)
def on_transition_day(evt, cli, var):
pl = list_players()
vs = set(evt.data["victims"])
for v in pl:
if v in vs:
if v in var.DYING:
continue
for g in var.ROLES["guardian angel"]:
if GUARDED.get(g) == v:
evt.data["numkills"][v] -= 1
if evt.data["numkills"][v] >= 0:
evt.data["killers"][v].pop(0)
if evt.data["numkills"][v] <= 0 and v not in evt.data["protected"]:
evt.data["protected"][v] = "angel"
elif evt.data["numkills"][v] <= 0:
var.ACTIVE_PROTECTIONS[v].append("angel")
for g in var.ROLES["bodyguard"]:
if GUARDED.get(g) == v:
evt.data["numkills"][v] -= 1
if evt.data["numkills"][v] >= 0:
evt.data["killers"][v].pop(0)
if evt.data["numkills"][v] <= 0 and v not in evt.data["protected"]:
evt.data["protected"][v] = "bodyguard"
elif evt.data["numkills"][v] <= 0:
var.ACTIVE_PROTECTIONS[v].append("bodyguard")
else:
for g in var.ROLES["guardian angel"]:
if GUARDED.get(g) == v:
var.ACTIVE_PROTECTIONS[v].append("angel")
for g in var.ROLES["bodyguard"]:
if GUARDED.get(g) == v:
var.ACTIVE_PROTECTIONS[v].append("bodyguard")
@event_listener("fallen_angel_guard_break")
def on_fagb(evt, cli, var, victim, killer):
for g in var.ROLES["guardian angel"]:
if GUARDED.get(g) == victim:
if random.random() < var.FALLEN_ANGEL_KILLS_GUARDIAN_ANGEL_CHANCE:
if g in evt.data["protected"]:
del evt.data["protected"][g]
evt.data["bywolves"].add(g)
if g not in evt.data["victims"]:
evt.data["onlybywolves"].add(g)
evt.data["victims"].append(g)
evt.data["killers"][g].append(killer)
if g != victim:
pm(cli, g, messages["fallen_angel_success"].format(victim))
for g in var.ROLES["bodyguard"]:
if GUARDED.get(g) == victim:
if g in evt.data["protected"]:
del evt.data["protected"][g]
evt.data["bywolves"].add(g)
if g not in evt.data["victims"]:
evt.data["onlybywolves"].add(g)
evt.data["victims"].append(g)
evt.data["killers"][g].append(killer)
if g != victim:
pm(cli, g, messages["fallen_angel_success"].format(victim))
@event_listener("transition_day_resolve", priority=2)
def on_transition_day_resolve(evt, cli, var, victim):
# TODO: remove these checks once everything is split
# right now they're needed because otherwise protection may fire off even if the person isn't home
# that will not be an issue once everything is using the event
if victim in var.ROLES["harlot"] | var.ROLES["succubus"] and var.HVISITED.get(victim) and victim not in evt.data["dead"] and victim in evt.data["onlybywolves"]:
return
# END checks to remove
if evt.data["protected"].get(victim) == "angel":
evt.data["message"].append(messages["angel_protection"].format(victim))
evt.data["novictmsg"] = False
evt.stop_processing = True
evt.prevent_default = True
elif evt.data["protected"].get(victim) == "bodyguard":
for bodyguard in var.ROLES["bodyguard"]:
if GUARDED.get(bodyguard) == victim:
evt.data["dead"].append(bodyguard)
evt.data["message"].append(messages["bodyguard_protection"].format(bodyguard))
evt.data["novictmsg"] = False
evt.stop_processing = True
evt.prevent_default = True
break
@event_listener("transition_day_resolve_end")
def on_transition_day_resolve_end(evt, cli, var, victims):
for bodyguard in var.ROLES["bodyguard"]:
if GUARDED.get(bodyguard) in list_players(var.WOLF_ROLES) and bodyguard not in evt.data["dead"] and bodyguard not in evt.data["bitten"]:
r = random.random()
if r < var.BODYGUARD_DIES_CHANCE:
evt.data["bywolves"].add(bodyguard)
evt.data["onlybywolves"].add(bodyguard)
if var.ROLE_REVEAL == "on":
evt.data["message"].append(messages["bodyguard_protected_wolf"].format(bodyguard))
else: # off and team
evt.data["message"].append(messages["bodyguard_protection"].format(bodyguard))
evt.data["dead"].append(bodyguard)
for gangel in var.ROLES["guardian angel"]:
if GUARDED.get(gangel) in list_players(var.WOLF_ROLES) and gangel not in evt.data["dead"] and gangel not in evt.data["bitten"]:
r = random.random()
if r < var.GUARDIAN_ANGEL_DIES_CHANCE:
evt.data["bywolves"].add(gangel)
evt.data["onlybywolves"].add(gangel)
if var.ROLE_REVEAL == "on":
evt.data["message"].append(messages["guardian_angel_protected_wolf"].format(gangel))
else: # off and team
evt.data["message"].append(messages["guardian_angel_protected_wolf_no_reveal"].format(gangel))
evt.data["dead"].append(gangel)
@event_listener("transition_night_begin")
def on_transition_night_begin(evt, cli, var):
# needs to be here in order to allow bodyguard protections to work during the daytime
# (right now they don't due to other reasons, but that may change)
GUARDED.clear()
@event_listener("transition_night_end", priority=2)
def on_transition_night_end(evt, cli, var):
# the messages for angel and guardian angel are different enough to merit individual loops
ps = list_players()
for bg in var.ROLES["bodyguard"]:
pl = ps[:]
random.shuffle(pl)
pl.remove(bg)
chance = math.floor(var.BODYGUARD_DIES_CHANCE * 100)
warning = ""
if chance > 0:
warning = messages["bodyguard_death_chance"].format(chance)
if bg in var.PLAYERS and not is_user_simple(bg):
pm(cli, bg, messages["bodyguard_notify"].format(warning))
else:
pm(cli, bg, messages["bodyguard_simple"]) # !simple
pm(cli, bg, "Players: " + ", ".join(pl))
for gangel in var.ROLES["guardian angel"]:
pl = ps[:]
random.shuffle(pl)
gself = messages["guardian_self_notification"]
if not var.GUARDIAN_ANGEL_CAN_GUARD_SELF:
pl.remove(gangel)
gself = ""
if LASTGUARDED.get(gangel) in pl:
pl.remove(LASTGUARDED[gangel])
chance = math.floor(var.GUARDIAN_ANGEL_DIES_CHANCE * 100)
warning = ""
if chance > 0:
warning = messages["bodyguard_death_chance"].format(chance)
if gangel in var.PLAYERS and not is_user_simple(gangel):
pm(cli, gangel, messages["guardian_notify"].format(warning, gself))
else:
pm(cli, gangel, messages["guardian_simple"]) # !simple
pm(cli, gangel, "Players: " + ", ".join(pl))
@event_listener("assassinate")
def on_assassinate(evt, cli, var, nick, target, prot):
if prot == "angel" and var.GAMEPHASE == "night":
var.ACTIVE_PROTECTIONS[target].remove("angel")
evt.prevent_default = True
evt.stop_processing = True
cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "angel"].format(nick, target))
elif prot == "bodyguard":
var.ACTIVE_PROTECTIONS[target].remove("bodyguard")
evt.prevent_default = True
evt.stop_processing = True
for bg in var.ROLES["bodyguard"]:
if GUARDED.get(bg) == target:
cli.msg(botconfig.CHANNEL, messages[evt.params.message_prefix + "bodyguard"].format(nick, target, bg))
evt.params.del_player(cli, bg, True, end_game=False, killer_role=nickrole, deadlist=evt.params.deadlist, original=evt.params.original, ismain=False)
evt.data["pl"] = evt.params.refresh_pl(pl)
break
@event_listener("begin_day")
def on_begin_day(evt, cli, var):
PASSED.clear()
# clear out LASTGUARDED for people that didn't guard last night
for g in list(LASTGUARDED.keys()):
if g not in GUARDED:
del LASTGUARDED[g]
@event_listener("reset")
def on_reset(evt, var):
GUARDED.clear()
LASTGUARDED.clear()
PASSED.clear()
# vim: set sw=4 expandtab: