Split and convert clone (#340)

This commit is contained in:
Em Barry 2018-06-21 15:35:03 -04:00 committed by GitHub
parent aaeb51b203
commit 213f7b2c3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 224 additions and 170 deletions

View File

@ -83,12 +83,7 @@ def get_all_roles(user):
return {role for role, users in var.ROLES.items() if user in users}
def get_reveal_role(user):
# FIXME: when clone is split, move this into an event
role = get_main_role(user)
if var.HIDDEN_CLONE and user in var.ORIGINAL_ROLES["clone"]:
role = "clone"
evt = Event("get_reveal_role", {"role": role})
evt = Event("get_reveal_role", {"role": get_main_role(user)})
evt.dispatch(var, user)
role = evt.data["role"]

View File

@ -23,7 +23,7 @@ def setup_variables(rolename, *, send_role, types):
def on_transition_night_end(evt, var):
villagers = set(get_players(("priest", "doctor")))
win_stealers = set(get_players(("fool", "monster", "demoniac")))
neutrals = set(get_players(("turncoat", "clone", "jester")))
neutrals = set(get_players(("turncoat", "jester")))
special_evt = Event("get_special", {"villagers": villagers, "wolves": set(), "win_stealers": win_stealers, "neutrals": neutrals})
special_evt.dispatch(var)

214
src/roles/clone.py Normal file
View File

@ -0,0 +1,214 @@
import re
import random
import itertools
import math
from collections import defaultdict
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.containers import UserList, UserSet, UserDict, DefaultUserDict
from src.messages import messages
from src.events import Event
CLONED = UserDict() # type: Dict[users.User, users.User]
CLONE_ENABLED = False # becomes True if at least one person died and there are clones
@command("clone", chan=False, pm=True, playing=True, phases=("night",), roles=("clone",))
def clone(var, wrapper, message):
"""Clone another player. You will turn into their role if they die."""
if not var.FIRST_NIGHT:
return
if wrapper.source in CLONED:
wrapper.pm(messages["already_cloned"])
return
params = re.split(" +", message)
# allow for role-prefixed command such as !clone clone target
# if we get !clone clone (with no 3rd arg), we give preference to prefixed version;
# meaning if the person wants to clone someone named clone, they must type !clone clone clone
# (or just !clone clon, !clone clo, etc. assuming those would be unambiguous matches)
if params[0] == "clone":
if len(params) > 1:
del params[0]
else:
wrapper.pm(messages["clone_clone_clone"])
return
target = get_target(var, wrapper, params[0])
if target is None:
return
CLONED[wrapper.source] = target
wrapper.pm(messages["clone_target_success"].format(target))
debuglog("{0} (clone) CLONE: {1} ({2})".format(wrapper.source, target, get_main_role(target)))
@event_listener("init")
def on_init(evt):
# We need to add "clone" to the role command exceptions so there's no error
# This is done here so that var isn't imported at the global scope
# (when we implement proper game state this will be in a different event)
from src import settings as var
var.ROLE_COMMAND_EXCEPTIONS.add("clone")
@event_listener("get_reveal_role")
def on_get_reveal_role(evt, var, user):
if var.HIDDEN_CLONE and user in var.ORIGINAL_ROLES["clone"]:
evt.data["role"] = "clone"
@event_listener("del_player")
def on_del_player(evt, var, player, mainrole, allroles, death_triggers):
# clone happens regardless of death_triggers being true or not
if var.PHASE not in var.GAME_PHASES:
return
clones = get_all_players(("clone",))
for clone in clones:
if clone in CLONED and clone not in evt.params.deadlist:
target = CLONED[clone]
if player is target:
# clone is cloning target, so clone becomes target's role
# clone does NOT get any of target's templates (gunner/assassin/etc.)
del CLONED[clone]
if mainrole == "amnesiac":
from src.roles.amnesiac import ROLES as amn_roles
# clone gets the amnesiac's real role
mainrole = amn_roles[player]
change_role(clone, "clone", mainrole)
debuglog("{0} (clone) CLONE DEAD PLAYER: {1} ({2})".format(clone, target, mainrole))
sayrole = mainrole
if sayrole in var.HIDDEN_VILLAGERS:
sayrole = "villager"
elif sayrole in var.HIDDEN_ROLES:
sayrole = var.DEFAULT_ROLE
an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else ""
clone.send(messages["clone_turn"].format(an, sayrole))
# if a clone is cloning a clone, clone who the old clone cloned
if mainrole == "clone" and player in CLONED:
if CLONED[player] is clone:
clone.send(messages["forever_aclone"].format(player))
else:
CLONED[clone] = CLONED[player]
clone.send(messages["clone_success"].format(CLONED[clone]))
debuglog("{0} (clone) CLONE: {1} ({2})".format(clone, CLONED[clone], get_main_role(CLONED[clone])))
elif mainrole in var.WOLFCHAT_ROLES:
wolves = get_players(var.WOLFCHAT_ROLES)
wolves.remove(clone) # remove self from list
for wolf in wolves:
wolf.queue_message(messages["clone_wolf"].format(clone, player))
if wolves:
wolf.send_messages()
if var.PHASE == "day":
random.shuffle(wolves)
for i, wolf in enumerate(wolves):
wolfrole = get_main_role(wolf)
wevt = Event("wolflist", {"tags": set()})
wevt.dispatch(var, wolf, clone)
tags = " ".join(wevt.data["tags"])
if tags:
tags += " "
wolves[i] = "\u0002{0}\u0002 ({1}{2})".format(wolf, tags, wolfrole)
if wolves:
clone.send(messages["wolves_list"].format(wolves))
else:
clone.send(messages["no_other_wolves"])
elif mainrole == "turncoat":
var.TURNCOATS[clone.nick] = ("none", -1) # FIXME
if mainrole == "clone" and player in CLONED:
del CLONED[player]
@event_listener("transition_night_end")
def on_transition_night_end(evt, var):
if var.FIRST_NIGHT or var.ALWAYS_PM_ROLE:
ps = get_players()
for clone in get_all_players(("clone",)):
pl = ps[:]
random.shuffle(pl)
pl.remove(clone)
if clone.prefers_simple():
clone.send(messages["clone_simple"])
else:
clone.send(messages["clone_notify"])
clone.send(messages["players_list"].format(", ".join(p.nick for p in pl)))
@event_listener("chk_nightdone")
def on_chk_nightdone(evt, var):
if var.FIRST_NIGHT:
evt.data["actedcount"] += len(CLONED)
evt.data["nightroles"].extend(get_all_players(("clone",)))
@event_listener("transition_day_begin")
def on_transition_day_begin(evt, var):
if var.FIRST_NIGHT:
# Select a random target for clone if they didn't choose someone
pl = get_players()
for clone in get_all_players(("clone",)):
if clone not in CLONED:
ps = pl[:]
ps.remove(clone)
if len(ps) > 0:
target = random.choice(ps)
CLONED[clone] = target
clone.send(messages["random_clone"].format(target))
@event_listener("exchange_roles")
def on_exchange_roles(evt, var, actor, target, actor_role, target_role):
actor_target = None
target_target = None
if actor_role == "clone":
if actor in CLONED:
actor_target = CLONED.pop(actor)
evt.data["target_messages"].append(messages["clone_target"].format(actor_target))
if target_role == "clone":
if target in CLONED:
target_target = CLONED.pop(target)
evt.data["actor_messages"].append(messages["clone_target"].format(target_target))
if actor_target is not None:
CLONED[target] = actor_target
if target_target is not None:
CLONED[actor] = target_target
@event_listener("player_win")
def on_player_win(evt, var, player, role, winner, survived):
# this means they ended game while being clone and not some other role
if survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
evt.data["iwon"] = True
@event_listener("del_player")
def first_death_occured(evt, var, player, mainrole, allroles, death_triggers):
global CLONE_ENABLED
if CLONE_ENABLED:
return
if (CLONED or get_all_players(("clone",))) and var.PHASE in var.GAME_PHASES and not var.FIRST_NIGHT:
CLONE_ENABLED = True
@event_listener("update_stats")
def on_update_stats(evt, var, player, mainrole, revealrole, allroles):
if CLONE_ENABLED:
evt.data["possible"].add("clone")
@event_listener("myrole")
def on_myrole(evt, var, user):
# Remind clone who they have cloned
if evt.data["role"] == "clone" and user in CLONED:
evt.data["messages"].append(messages["clone_target"].format(CLONED[user]))
@event_listener("revealroles_role")
def on_revealroles_role(evt, var, user, role):
if role == "clone" and user in CLONED:
evt.data["special_case"].append("cloning {0}".format(CLONED[user]))
@event_listener("get_special")
def on_get_special(evt, var):
evt.data["neutrals"].update(get_players(("clone",)))
@event_listener("reset")
def on_reset(evt, var):
global CLONE_ENABLED
CLONE_ENABLED = False
CLONED.clear()

View File

@ -2123,10 +2123,6 @@ def stop_game(var, winner="", abort=False, additional_winners=None, log=True):
iwon = True
elif rol == "demoniac" and plr in survived and winner == "demoniacs":
iwon = True
elif rol == "clone":
# this means they ended game while being clone and not some other role
if plr in survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
iwon = True
elif rol == "jester" and splr in var.JESTERS:
iwon = True
elif not iwon:
@ -2344,72 +2340,6 @@ def del_player(player, *, devoice=True, end_game=True, death_triggers=True, kill
if player.nick in var.BITTEN_ROLES:
del var.BITTEN_ROLES[player.nick] # FIXME
pl.discard(player)
# handle roles that trigger on death
# clone happens regardless of death_triggers being true or not
if var.PHASE in var.GAME_PHASES:
clones = get_all_players(("clone",))
for clone in clones:
# clone is a User, var.CLONED is a Dict[str,str]
# dealist is a List[User]; ensure we add .nick appropriately
# FIXME: someone should convert var.CLONED
if clone.nick in var.CLONED and clone not in deadlist:
target = var.CLONED[clone.nick]
if player.nick == target and clone.nick in var.CLONED:
# clone is cloning nick, so clone becomes nick's role
# clone does NOT get any of nick's templates (gunner/assassin/etc.)
del var.CLONED[clone.nick]
if mainrole == "amnesiac":
from src.roles.amnesiac import ROLES
# clone gets the amnesiac's real role
sayrole = ROLES[player]
else:
sayrole = mainrole
change_role(clone, "clone", sayrole)
debuglog("{0} (clone) CLONE DEAD PLAYER: {1} ({2})".format(clone, target, sayrole))
if sayrole in var.HIDDEN_VILLAGERS:
sayrole = "villager"
elif sayrole in var.HIDDEN_ROLES:
sayrole = var.DEFAULT_ROLE
an = "n" if sayrole.startswith(("a", "e", "i", "o", "u")) else ""
clone.send(messages["clone_turn"].format(an, sayrole))
# if a clone is cloning a clone, clone who the old clone cloned
if mainrole == "clone" and player.nick in var.CLONED:
if var.CLONED[player.nick] == clone.nick:
clone.send(messages["forever_aclone"].format(player))
else:
var.CLONED[clone.nick] = var.CLONED[player.nick]
clone.send(messages["clone_success"].format(var.CLONED[clone.nick]))
# FIXME: change below to get_main_role(var.CLONED[clone]) once var.CLONED is converted
debuglog("{0} (clone) CLONE: {1} ({2})".format(clone, var.CLONED[clone.nick], get_role(var.CLONED[clone.nick])))
elif mainrole in var.WOLFCHAT_ROLES:
wolves = get_players(var.WOLFCHAT_ROLES)
wolves.remove(clone) # remove self from list
for wolf in wolves:
wolf.queue_message(messages["clone_wolf"].format(clone, player))
if wolves:
wolf.send_messages()
if var.PHASE == "day":
random.shuffle(wolves)
for i, wolf in enumerate(wolves):
wolfrole = get_main_role(wolf)
wevt = Event("wolflist", {"tags": set()})
wevt.dispatch(var, wolf, clone)
tags = " ".join(wevt.data["tags"])
if tags:
tags += " "
wolves[i] = "\u0002{0}\u0002 ({1}{2})".format(wolf, tags, wolfrole)
if wolves:
clone.send(messages["wolves_list"].format(wolves))
else:
clone.send(messages["no_other_wolves"])
elif mainrole == "turncoat":
var.TURNCOATS[clone.nick] = ("none", -1) # FIXME
if mainrole == "clone" and player.nick in var.CLONED:
del var.CLONED[player.nick]
pl = refresh_pl(pl)
# i herd u liek parameters
evt_death_triggers = death_triggers and var.PHASE in var.GAME_PHASES
event = Event("del_player", {"pl": pl},
@ -2793,7 +2723,7 @@ def rename_player(var, user, prefix):
if prefix == k:
var.PLAYERS[nick] = var.PLAYERS.pop(k)
for dictvar in (var.OBSERVED, var.CLONED, var.LASTHEXED, var.BITE_PREFERENCES):
for dictvar in (var.OBSERVED, var.LASTHEXED, var.BITE_PREFERENCES):
kvp = []
for a,b in dictvar.items():
if a == prefix:
@ -3096,27 +3026,14 @@ def transition_day(gameid=0):
event_begin = Event("transition_day_begin", {})
event_begin.dispatch(var)
pl = get_players()
if not var.START_WITH_DAY or not var.FIRST_DAY:
if len(var.HEXED) < len(var.ROLES["hag"]):
for hag in var.ROLES["hag"]:
if hag.nick not in var.HEXED: # FIXME
var.LASTHEXED[hag.nick] = None # FIXME
# NOTE: Random assassin selection is further down, since if we're choosing at random we pick someone
# that isn't going to be dying today, meaning we need to know who is dying first :)
if var.FIRST_NIGHT:
# Select a random target for clone if they didn't choose someone
for clone in get_all_players(("clone",)):
if clone.nick not in var.CLONED:
ps = pl[:]
ps.remove(clone)
if len(ps) > 0:
target = random.choice(ps)
var.CLONED[clone.nick] = target.nick
clone.send(messages["random_clone"].format(target))
# NOTE: Random assassin selection is further down, since if we're choosing at random we pick someone
# that isn't going to be dying today, meaning we need to know who is dying first :)
# Reset daytime variables
var.WOUNDED.clear()
@ -3515,10 +3432,6 @@ def chk_nightdone():
nightroles = list(get_all_players(("sorcerer", "hag", "warlock", "werecrow")))
if var.FIRST_NIGHT:
actedcount += len(var.CLONED.keys())
nightroles.extend(get_all_players(("clone",)))
if var.ALPHA_ENABLED:
# alphas both kill and bite if they're activated at night, so add them into the counts
nightroles.extend(get_all_players(("alpha wolf",)))
@ -3704,10 +3617,7 @@ def check_exchange(cli, actor, nick):
# var.PASSED is used by many roles
var.PASSED.discard(actor)
if actor_role == "clone":
if actor in var.CLONED:
actor_target = var.CLONED.pop(actor)
elif actor_role in ("werecrow", "sorcerer"):
if actor_role in ("werecrow", "sorcerer"):
if actor in var.OBSERVED:
del var.OBSERVED[actor]
elif actor_role == "hag":
@ -3732,10 +3642,7 @@ def check_exchange(cli, actor, nick):
# var.PASSED is used by many roles
var.PASSED.discard(nick)
if nick_role == "clone":
if nick in var.CLONED:
nick_target = var.CLONED.pop(nick)
elif nick_role in ("werecrow", "sorcerer"):
if nick_role in ("werecrow", "sorcerer"):
if nick in var.OBSERVED:
del var.OBSERVED[nick]
elif nick_role == "hag":
@ -3809,9 +3716,7 @@ def check_exchange(cli, actor, nick):
else:
wcroles = var.WOLF_ROLES | {"traitor"}
if nick_role == "clone":
pm(cli, actor, messages["clone_target"].format(nick_target))
elif nick_role not in wcroles and nick_role == "warlock":
if nick_role not in wcroles and nick_role == "warlock":
# this means warlock isn't in wolfchat, so only give cursed list
pl = list_players()
random.shuffle(pl)
@ -3823,9 +3728,7 @@ def check_exchange(cli, actor, nick):
elif nick_role == "turncoat":
var.TURNCOATS[actor] = ("none", -1)
if actor_role == "clone":
pm(cli, nick, messages["clone_target"].format(actor_target))
elif actor_role not in wcroles and actor_role == "warlock":
if actor_role not in wcroles and actor_role == "warlock":
# this means warlock isn't in wolfchat, so only give cursed list
pl = list_players()
random.shuffle(pl)
@ -4265,46 +4168,6 @@ def curse(cli, nick, chan, rest):
debuglog("{0} ({1}) CURSE: {2} ({3})".format(nick, get_role(nick), victim, vrole))
@cmd("clone", chan=False, pm=True, playing=True, phases=("night",), roles=("clone",))
def clone(cli, nick, chan, rest):
"""Clone another player. You will turn into their role if they die."""
if not var.FIRST_NIGHT:
return
if nick in var.CLONED.keys():
pm(cli, nick, messages["already_cloned"])
return
params = re.split(" +", rest)
# allow for role-prefixed command such as !clone clone target
# if we get !clone clone (with no 3rd arg), we give preference to prefixed version;
# meaning if the person wants to clone someone named clone, they must type !clone clone clone
# (or just !clone clon, !clone clo, etc. assuming thos would be unambiguous matches)
if params[0] == "clone":
if len(params) > 1:
del params[0]
else:
pm(cli, nick, messages["clone_clone_clone"])
return
# no var.SILENCED check for night 1 only roles; silence should only apply for the night after
# but just in case, it also sucks if the one night you're allowed to act is when you are
# silenced, so we ignore it here anyway.
victim = get_victim(cli, nick, params[0], False)
if not victim:
return
if nick == victim:
pm(cli, nick, messages["no_target_self"])
return
var.CLONED[nick] = victim
pm(cli, nick, messages["clone_target_success"].format(victim))
debuglog("{0} ({1}) CLONE: {2} ({3})".format(nick, get_role(nick), victim, get_role(victim)))
var.ROLE_COMMAND_EXCEPTIONS.add("clone")
@event_listener("targeted_command", priority=9)
def on_targeted_command(evt, var, actor, orig_target):
if evt.data["misdirection"]:
@ -4594,17 +4457,6 @@ def transition_night():
else:
priest.send(messages["priest_notify"])
if var.FIRST_NIGHT or var.ALWAYS_PM_ROLE:
for clone in get_all_players(("clone",)):
pl = ps[:]
random.shuffle(pl)
pl.remove(clone)
if clone.prefers_simple():
clone.send(messages["clone_simple"])
else:
clone.send(messages["clone_notify"])
clone.send(messages["players_list"].format(", ".join(p.nick for p in pl)))
for g in var.GUNNERS:
if g not in ps:
continue
@ -4847,7 +4699,6 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.MAIN_ROLES.clear()
var.GUNNERS.clear()
var.OBSERVED = {}
var.CLONED = {}
var.LASTHEXED = {}
var.SILENCED = set()
var.TOBESILENCED = set()
@ -5702,10 +5553,6 @@ def myrole(var, wrapper, message):
for msg in evt.data["messages"]:
wrapper.pm(msg)
# Remind clone who they have cloned
if role == "clone" and wrapper.source.nick in var.CLONED:
wrapper.pm(messages["clone_target"].format(var.CLONED[wrapper.source.nick]))
# Remind turncoats of their side
if role == "turncoat":
wrapper.pm(messages["turncoat_side"].format(var.TURNCOATS.get(wrapper.source.nick, "none")[0]))
@ -6177,10 +6024,8 @@ def revealroles(var, wrapper, message):
# go through each nickname, adding extra info if necessary
for user in users:
special_case = []
if role == "clone" and user.nick in var.CLONED:
special_case.append("cloning {0}".format(var.CLONED[user.nick]))
# print how many bullets normal gunners have
elif (role == "gunner" or role == "sharpshooter") and user in var.GUNNERS:
if (role == "gunner" or role == "sharpshooter") and user in var.GUNNERS:
special_case.append("{0} bullet{1}".format(var.GUNNERS[user], "" if var.GUNNERS[user] == 1 else "s"))
elif role == "turncoat" and user.nick in var.TURNCOATS:
special_case.append("currently with \u0002{0}\u0002".format(var.TURNCOATS[user.nick][0])