Merge pull request #277 from lykoss/part-quit-hooks

Improve user part/quit/nick hooks
This commit is contained in:
Ryan Schmidt 2017-01-06 10:17:28 -07:00 committed by GitHub
commit b1271ecbe0
3 changed files with 217 additions and 227 deletions

View File

@ -222,10 +222,8 @@
"quit_death_no_reveal": "\u0002{0}\u0002 was mauled by wild animals and has died.", "quit_death_no_reveal": "\u0002{0}\u0002 was mauled by wild animals and has died.",
"part_death": "\u0002{0}\u0002, a \u0002{1}\u0002, ate some poisonous berries and has died.", "part_death": "\u0002{0}\u0002, a \u0002{1}\u0002, ate some poisonous berries and has died.",
"part_death_no_reveal": "\u0002{0}\u0002 ate some poisonous berries and has died.", "part_death_no_reveal": "\u0002{0}\u0002 ate some poisonous berries and has died.",
"account_death": "\u0002{0}\u0002 has died of a heart attack. The villagers couldn't save the \u0002{1}\u0002.", "account_death": "\u0002{0}\u0002 fell into a river and was swept away. The villagers couldn't save the \u0002{1}\u0002.",
"account_death_2": "\u0002{0}\u0002 fell into a river and was swept away. The villagers couldn't save the \u0002{1}\u0002.", "account_death_no_reveal": "\u0002{0}\u0002 fell into a river and was swept away.",
"account_death_no_reveal": "\u0002{0}\u0002 has died of a heart attack.",
"account_death_no_reveal_2": "\u0002{0}\u0002 fell into a river and was swept away.",
"goat_fail": "This can only be done once per day.", "goat_fail": "This can only be done once per day.",
"not_enough_parameters": "Not enough parameters.", "not_enough_parameters": "Not enough parameters.",
"goat_target_not_in_channel": "\u0002{0}\u0002 is not in this channel.", "goat_target_not_in_channel": "\u0002{0}\u0002 is not in this channel.",

View File

@ -157,6 +157,21 @@ class users: # backwards-compatible API
def items(): def items():
yield from var.USERS.items() yield from var.USERS.items()
def complete_match(string, users):
matches = []
string = lower(string)
for user in users:
nick = lower(user.nick)
if nick == string:
return user, 1
elif nick.startswith(string) or nick.lstrip("[{\\^_`|}]").startswith(string):
matches.append(user)
if len(matches) != 1:
return None, len(matches)
return matches[0], 1
_raw_nick_pattern = re.compile(r"^(?P<nick>.+?)(?:!(?P<ident>.+?)@(?P<host>.+))?$") _raw_nick_pattern = re.compile(r"^(?P<nick>.+?)(?:!(?P<ident>.+?)@(?P<host>.+))?$")
def parse_rawnick(rawnick, *, default=None): def parse_rawnick(rawnick, *, default=None):

View File

@ -96,6 +96,7 @@ var.RESTARTING = False
var.BITTEN_ROLES = {} var.BITTEN_ROLES = {}
var.LYCAN_ROLES = {} var.LYCAN_ROLES = {}
var.CHARMED = set() var.CHARMED = set()
var.START_VOTES = set()
if botconfig.DEBUG_MODE and var.DISABLE_DEBUG_MODE_TIMERS: if botconfig.DEBUG_MODE and var.DISABLE_DEBUG_MODE_TIMERS:
var.NIGHT_TIME_LIMIT = 0 # 120 var.NIGHT_TIME_LIMIT = 0 # 120
@ -131,6 +132,7 @@ def connect_callback():
SIGUSR2 = getattr(signal, "SIGUSR2", None) SIGUSR2 = getattr(signal, "SIGUSR2", None)
def sighandler(signum, frame): def sighandler(signum, frame):
wrapper = decorators.MessageDispatcher(users.FakeUser.from_nick("<console>"), channels.Main)
if signum == signal.SIGINT: if signum == signal.SIGINT:
# Exit immediately if Ctrl-C is pressed twice # Exit immediately if Ctrl-C is pressed twice
signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL)
@ -141,7 +143,7 @@ def connect_callback():
restart_program.func(cli, "<console>", botconfig.CHANNEL, "") # XXX: Old API restart_program.func(cli, "<console>", botconfig.CHANNEL, "") # XXX: Old API
elif signum == SIGUSR2: elif signum == SIGUSR2:
plog("Scheduling aftergame restart") plog("Scheduling aftergame restart")
aftergame.func(cli, "<console>", botconfig.CHANNEL, "frestart") # XXX: Old API aftergame.func(var, wrapper, "frestart")
signal.signal(signal.SIGINT, sighandler) signal.signal(signal.SIGINT, sighandler)
signal.signal(signal.SIGTERM, sighandler) signal.signal(signal.SIGTERM, sighandler)
@ -309,7 +311,7 @@ def reset():
var.NO_LYNCH = set() var.NO_LYNCH = set()
var.FGAMED = False var.FGAMED = False
var.GAMEMODE_VOTES = {} #list of players who have used !game var.GAMEMODE_VOTES = {} #list of players who have used !game
var.START_VOTES = set() # list of players who have voted to !start var.START_VOTES.clear() # list of players who have voted to !start
var.LOVERS = {} # need to be here for purposes of random var.LOVERS = {} # need to be here for purposes of random
var.ENTRANCED = set() var.ENTRANCED = set()
var.ROLE_STATS = frozenset() # type: FrozenSet[FrozenSet[Tuple[str, int]]] var.ROLE_STATS = frozenset() # type: FrozenSet[FrozenSet[Tuple[str, int]]]
@ -607,71 +609,65 @@ def mark_prefer_notice(cli, nick, chan, rest):
reply(cli, nick, chan, messages["notice_on"], private=True) reply(cli, nick, chan, messages["notice_on"], private=True)
@cmd("swap", "replace", pm=True, phases=("join", "day", "night")) @command("swap", "replace", pm=True, phases=("join", "day", "night"))
def replace(cli, nick, chan, rest): def replace(var, wrapper, message):
"""Swap out a player logged in to your account.""" """Swap out a player logged in to your account."""
if not users.exists(nick) or not users.get(nick).inchan: if wrapper.source not in channels.Main.users:
pm(cli, nick, messages["invalid_channel"].format(botconfig.CHANNEL)) wrapper.pm(messages["invalid_channel"].format(channels.Main))
return return
if nick in list_players(): if wrapper.source.nick in list_players(): # FIXME: Need to fix when list_players() holds User instances
reply(cli, nick, chan, messages["already_playing"].format("You"), private=True) wrapper.pm(messages["already_playing"].format("You"))
return return
account = irc_lower(users.get(nick).account) if temp.account is None:
wrapper.pm(messages["not_logged_in"])
if not account or account == "*":
reply(cli, nick, chan, messages["not_logged_in"], private=True)
return return
rest = rest.split() rest = message.split()
if not rest: # bare call if not rest: # bare call
target = None target = None
for user in users.users(): for user in users.users_(): # FIXME: Backwards-compatible API
if irc_lower(users.get(user).account) == account: if users.equals(user.account, wrapper.source.account):
if user == nick or user not in list_participants(): if user is wrapper.source or user.nick not in list_participants(): # FIXME: Need to fix once list_participants() holds User instances
pass continue
elif target is None: elif target is None:
target = user target = user
else: else:
reply(cli, nick, chan, messages["swap_notice"].format(botconfig.CMD_CHAR), private=True) wrapper.pm(messages["swap_notice"].format(botconfig.CMD_CHAR))
return return
if target is None: if target is None:
msg = messages["account_not_playing"] wrapper.pm(messages["account_not_playing"])
reply(cli, nick, chan, msg, private=True)
return return
else: else:
pl = list_participants() pl = [users._get(p) for p in list_participants()] # FIXME: Need to fix once list_participants() holds User instances
pll = [irc_lower(i) for i in pl]
target, _ = complete_match(irc_lower(rest[0]), pll) target, _ = users.complete_match(rest[0], pl)
if target is not None: if target is None:
target = pl[pll.index(target)] wrapper.pm(messages["target_not_playing"].format(" longer" if target in var.DEAD else "t"))
if target not in pl or target not in var.USERS:
msg = messages["target_not_playing"].format(" longer" if target in var.DEAD else "t")
reply(cli, nick, chan, msg, private=True)
return return
if users.get(target).account in ("*", None): if target.account is None:
reply(cli, nick, chan, messages["target_not_logged_in"], private=True) wrapper.pm(messages["target_not_logged_in"])
return return
if irc_lower(users.get(target).account) == account and nick != target: if users.equals(target.account, wrapper.source.account) and target is not wrapper.source:
rename_player(cli, target, nick) rename_player(var, wrapper.source, target.nick)
# Make sure to remove player from var.DISCONNECTED if they were in there # Make sure to remove player from var.DISCONNECTED if they were in there
if var.PHASE in var.GAME_PHASES: if var.PHASE in var.GAME_PHASES:
return_to_village(cli, chan, target, False) return_to_village(var, channels.Main, target, show_message=False)
if not var.DEVOICE_DURING_NIGHT or var.PHASE != "night": if not var.DEVOICE_DURING_NIGHT or var.PHASE != "night":
mass_mode(cli, [("-v", target), ("+v", nick)], []) mode = hooks.Features["PREFIX"]["+"]
channels.Main.mode(("-" + mode, target), ("+" + mode, wrapper.source))
cli.msg(botconfig.CHANNEL, messages["player_swap"].format(nick, target)) channels.Main.send(messages["player_swap"].format(wrapper.source, target))
myrole.caller(cli, nick, chan, "") myrole.caller(wrapper.source.client, wrapper.source.nick, wrapper.target.name, "")
@cmd("pingif", "pingme", "pingat", "pingpref", pm=True) @cmd("pingif", "pingme", "pingat", "pingpref", pm=True)
def altpinger(cli, nick, chan, rest): def altpinger(cli, nick, chan, rest):
@ -1280,42 +1276,6 @@ def on_kicked(cli, nick, chan, victim, reason):
users.get(victim).modes = set() users.get(victim).modes = set()
users.get(victim).moded = set() users.get(victim).moded = set()
@hook("account")
def on_account(cli, rnick, acc):
nick, _, ident, host = parse_nick(rnick)
hostmask = irc_lower(ident) + "@" + host.lower()
lacc = irc_lower(acc)
chan = botconfig.CHANNEL
if acc == "*" and var.ACCOUNTS_ONLY and nick in list_players():
leave(cli, "account", nick)
if var.PHASE not in "join":
cli.mode(chan, "-v", nick)
cli.notice(nick, messages["account_reidentify"].format(users.get(nick).account))
else:
cli.notice(nick, messages["account_midgame_change"])
if users.exists(nick):
users.get(nick).ident = ident
users.get(nick).host = host
users.get(nick).account = acc
if nick in var.DISCONNECTED.keys():
if lacc == var.DISCONNECTED[nick][0]:
if users.exists(nick) and users.get(nick).inchan:
with var.GRAVEYARD_LOCK:
hm = var.DISCONNECTED[nick][1]
act = var.DISCONNECTED[nick][0]
if (lacc == act and not var.DISABLE_ACCOUNTS) or (hostmask == hm and not var.ACCOUNTS_ONLY):
cli.mode(chan, "+v", nick, nick+"!*@*")
del var.DISCONNECTED[nick]
var.LAST_SAID_TIME[nick] = datetime.now()
cli.msg(chan, messages["player_return"].format(nick))
for r,rset in var.ORIGINAL_ROLES.items():
if "(dced)"+nick in rset:
rset.remove("(dced)"+nick)
rset.add(nick)
break
if nick in var.DCED_PLAYERS.keys():
var.PLAYERS[nick] = var.DCED_PLAYERS.pop(nick)
@cmd("stats", "players", pm=True, phases=("join", "day", "night")) @cmd("stats", "players", pm=True, phases=("join", "day", "night"))
def stats(cli, nick, chan, rest): def stats(cli, nick, chan, rest):
"""Displays the player statistics.""" """Displays the player statistics."""
@ -2495,8 +2455,8 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True):
if var.AFTER_FLASTGAME is not None: if var.AFTER_FLASTGAME is not None:
var.AFTER_FLASTGAME() var.AFTER_FLASTGAME()
var.AFTER_FLASTGAME = None var.AFTER_FLASTGAME = None
if var.ADMIN_TO_PING: # It was an flastgame if var.ADMIN_TO_PING is not None: # It was an flastgame
cli.msg(chan, "PING! " + var.ADMIN_TO_PING) cli.msg(chan, "PING! {0}".format(var.ADMIN_TO_PING))
var.ADMIN_TO_PING = None var.ADMIN_TO_PING = None
return True return True
@ -2517,8 +2477,8 @@ def chk_win(cli, end_game=True, winner=None):
if var.AFTER_FLASTGAME is not None: if var.AFTER_FLASTGAME is not None:
var.AFTER_FLASTGAME() var.AFTER_FLASTGAME()
var.AFTER_FLASTGAME = None var.AFTER_FLASTGAME = None
if var.ADMIN_TO_PING: # It was an flastgame if var.ADMIN_TO_PING is not None: # It was an flastgame
cli.msg(chan, "PING! " + var.ADMIN_TO_PING) cli.msg(chan, "PING! {0}".format(var.ADMIN_TO_PING))
var.ADMIN_TO_PING = None var.ADMIN_TO_PING = None
return True return True
@ -3283,35 +3243,29 @@ def fgoat(cli, nick, chan, rest):
cli.msg(chan, messages["goat_success"].format(nick, goatact, togoat)) cli.msg(chan, messages["goat_success"].format(nick, goatact, togoat))
@handle_error @handle_error
def return_to_village(cli, chan, nick, show_message): def return_to_village(var, chan, target, *, show_message):
with var.GRAVEYARD_LOCK: with var.GRAVEYARD_LOCK:
if nick in var.DISCONNECTED.keys(): temp = target.lower()
hm = var.DISCONNECTED[nick][1] if temp.nick in var.DISCONNECTED:
act = var.DISCONNECTED[nick][0] if (temp.account is not None and users.equals(temp.account, account) or
if users.exists(nick): temp.userhost is not None and users.equals(temp.userhost, hostmask) and not var.ACCOUNTS_ONLY):
ident = irc_lower(users.get(nick).ident)
host = users.get(nick).host.lower()
acc = irc_lower(users.get(nick).account)
else:
acc = None
if not acc or acc == "*":
acc = None
hostmask = ident + "@" + host
if (acc and acc == act) or (hostmask == hm and not var.ACCOUNTS_ONLY):
del var.DISCONNECTED[nick]
var.LAST_SAID_TIME[nick] = datetime.now()
for r,rset in var.ORIGINAL_ROLES.items():
if "(dced)"+nick in rset:
rset.remove("(dced)"+nick)
rset.add(nick)
if nick in var.DCED_PLAYERS.keys():
var.PLAYERS[nick] = var.DCED_PLAYERS.pop(nick)
if show_message:
cli.mode(chan, "+v", nick, nick+"!*@*")
cli.msg(chan, messages["player_return"].format(nick))
def rename_player(cli, prefix, nick): del var.DISCONNECTED[temp.nick]
chan = botconfig.CHANNEL var.LAST_SAID_TIME[temp.nick] = datetime.now()
for roleset in var.ORIGINAL_ROLES.values():
if "(dced)" + temp.nick in roleset:
roleset.remove("(dced)" + temp.nick)
roleset.add(temp.nick)
if temp.nick in var.DCED_PLAYERS:
var.PLAYERS[temp.nick] = var.DCED_PLAYERS.pop(temp.nick)
if show_message:
channels.Main.mode(("+" + hooks.Features["PREFIX"]["+"], target))
channels.Main.send(messages["player_return"].format(target))
def rename_player(var, user, prefix):
nick = user.nick
if var.PHASE in var.GAME_PHASES: if var.PHASE in var.GAME_PHASES:
if prefix in var.ENTRANCED: # need to update this after death, too if prefix in var.ENTRANCED: # need to update this after death, too
@ -3328,7 +3282,7 @@ def rename_player(cli, prefix, nick):
var.SPECTATING_WOLFCHAT.add(nick) var.SPECTATING_WOLFCHAT.add(nick)
event = Event("rename_player", {}) event = Event("rename_player", {})
event.dispatch(cli, var, prefix, nick) event.dispatch(user.client, var, prefix, nick) # FIXME: Need to update all the callbacks
if prefix in var.ALL_PLAYERS: if prefix in var.ALL_PLAYERS:
pl = list_players() pl = list_players()
@ -3455,128 +3409,152 @@ def rename_player(cli, prefix, nick):
# Check if player was disconnected # Check if player was disconnected
if var.PHASE in var.GAME_PHASES: if var.PHASE in var.GAME_PHASES:
return_to_village(cli, chan, nick, True) return_to_village(var, channels.Main, user, show_message=True)
if prefix in var.NO_LYNCH: if prefix in var.NO_LYNCH:
var.NO_LYNCH.remove(prefix) var.NO_LYNCH.remove(prefix)
var.NO_LYNCH.add(nick) var.NO_LYNCH.add(nick)
@hook("nick") # XXX Update once the user/channel refactor is done @event_listener("account_change")
def on_nick(cli, oldnick, nick): def account_change(evt, var, user):
prefix, _, ident, host = parse_nick(oldnick) if user not in channels.Main.users:
chan = botconfig.CHANNEL return # We only care about game-related changes in this function
if re.search(var.GUEST_NICK_PATTERN, nick) and nick not in var.DISCONNECTED.keys() and prefix in list_players(): voice = hooks.Features["PREFIX"]["+"]
if user.account is None and var.ACCOUNTS_ONLY and user.nick in list_players(): # FIXME: need changed when list_players holds User instances
leave(var, "account", user)
if var.PHASE == "join":
user.send(messages["account_midgame_change"], notice=True)
else:
channels.Main.mode(["-" + voice, user.nick])
user.send(messages["account_reidentify"].format(user.account), notice=True)
if user.nick in var.DISCONNECTED: # FIXME: need to change this when var.DISCONNECTED holds User instances
account, hostmask, when, what = var.DISCONNECTED[user.nick]
if users.equals(user.account, account):
with var.GRAVEYARD_LOCK:
if not var.DISABLE_ACCOUNTS or not var.ACCOUNTS_ONLY and user.match_hostmask(hostmask):
channels.Main.mode(["+" + voice, user.nick])
del var.DISCONNECTED[user.nick]
var.LAST_SAID[user.nick] = datetime.now() # FIXME: need updating when var.LAST_SAID holds User instances
channels.Main.send(messages["player_return"].format(user))
for roleset in var.ORIGINAL_ROLES.values():
if "(dced)" + user.nick in roleset: # FIXME: Get rid of the (dced) hack for everything at once, and also fix once role sets hold User instances
roleset.remove("(dced)" + user.nick)
roleset.add(user.nick)
break
if user.nick in var.DCED_PLAYERS: # FIXME: Fix this once this variable holds User instances
var.PLAYERS[user.nick] = var.DCED_PLAYERS.pop(nick)
@event_listener("nick_change")
def nick_change(evt, var, user, old_rawnick):
nick = users.parse_rawnick_as_dict(old_rawnick)["nick"] # FIXME: We won't need that when all variables hold User instances
if user.nick not in var.DISCONNECTED and nick in list_players() and re.search(var.GUEST_NICK_PATTERN, user.nick): # FIXME: Fix this once var.DISCONNECTED and list_players() hold User instances
if var.PHASE != "join": if var.PHASE != "join":
cli.mode(chan, "-v", nick) channels.Main.mode(["-" + hooks.Features["PREFIX"]["+"], user.nick])
leave(cli, "badnick", oldnick) temp = users.FakeUser(None, nick, user.ident, user.host, user.realname, user.account)
# update var.USERS after so that leave() can keep track of new nick to use properly leave(var, "badnick", temp) # pass in a fake user with the old nick (since the user holds the new nick)
# return after doing this so that none of the game vars are updated with the bad nickname return # Don't do anything else; they're using a guest/away nick
if prefix in var.USERS:
var.USERS[nick] = var.USERS.pop(prefix) if user not in channels.Main.users:
return return
if prefix in var.USERS: if nick not in var.DISCONNECTED: # FIXME: Need to update this once var.DISCONNECTED holds User instances
var.USERS[nick] = var.USERS.pop(prefix) rename_player(var, user, nick) # FIXME: Fix when rename_player supports the new interface
if not var.USERS[nick]["inchan"]:
return
if prefix == var.ADMIN_TO_PING: @event_listener("nick_change")
var.ADMIN_TO_PING = nick def update_users(evt, var, user, old_rawnick): # FIXME: This is a temporary hack while var.USERS still exists
nick = users.parse_rawnick_as_dict(old_rawnick)["nick"]
if nick in var.USERS:
var.USERS[user.nick] = var.USERS.pop(nick)
if prefix not in var.DISCONNECTED.keys(): @event_listener("chan_part")
rename_player(cli, prefix, nick) def left_channel(evt, var, chan, user, reason):
leave(var, "part", user, chan)
def leave(cli, what, nick, why=""): @event_listener("chan_kick")
nick, _, ident, host = parse_nick(nick) def channel_kicked(evt, var, chan, actor, user, reason):
if users.exists(nick): leave(var, "kick", user, chan)
acc = irc_lower(users.get(nick).account)
ident = irc_lower(users.get(nick).ident)
host = users.get(nick).host.lower()
if what == "quit" or (not what in ("account",) and why == botconfig.CHANNEL):
users.get(nick).inchan = False
else:
acc = None
if not acc or acc == "*":
acc = None
if what in ("part", "kick") and why != botconfig.CHANNEL: return @event_listener("server_quit")
def quit_server(evt, var, user, reason):
leave(var, "quit", user, reason)
def leave(var, what, user, why=None):
if what in ("part", "kick") and why is not channels.Main:
return
if why and why == botconfig.CHANGING_HOST_QUIT_MESSAGE: if why and why == botconfig.CHANGING_HOST_QUIT_MESSAGE:
return return
if var.PHASE == "none": if var.PHASE == "none":
return return
# only mark living players as dced, unless they were kicked
if nick in var.PLAYERS and (what == "kick" or nick in list_players()): # Only mark living players as disconnected, unless they were kicked
# must prevent double entry in var.ORIGINAL_ROLES if user.nick in var.PLAYERS and (what == "kick" or user.nick in list_players()): # FIXME: Need to fix this once var.PLAYERS and list_players() hold User instances
for r,rset in var.ORIGINAL_ROLES.items(): # Prevent duplicate entry in var.ORIGINAL_ROLES
if nick in rset: for roleset in var.ORIGINAL_ROLES.values():
var.ORIGINAL_ROLES[r].remove(nick) if user.nick in roleset: # FIXME: Need to fix this once the role sets hold User instances
var.ORIGINAL_ROLES[r].add("(dced)"+nick) roleset.remove(user.nick)
roleset.add("(dced)" + user.nick) # FIXME: Need to get rid of all the (dced) hacks
break break
var.DCED_PLAYERS[nick] = var.PLAYERS.pop(nick)
if nick not in list_players() or nick in var.DISCONNECTED.keys(): var.DCED_PLAYERS[user.nick] = var.PLAYERS.pop(user.nick) # FIXME: Need to fix this once var.{DCED_}PLAYERS hold User instances
if user.nick not in list_players() or user.nick in var.DISCONNECTED: # FIXME: Need to fix this once list_players() and var.DISCONNECTED hold User instances
return return
# the player who just quit was in the game # If we got that far, the player was in the game. This variable tracks whether or not we want to kill them off.
killplayer = True killplayer = True
population = "" population = ""
if var.PHASE == "join": if var.PHASE == "join":
lpl = len(list_players()) - 1 lpl = len(list_players()) - 1
if lpl < var.MIN_PLAYERS: if lpl < var.MIN_PLAYERS:
with var.WARNING_LOCK: with var.WARNING_LOCK:
var.START_VOTES = set() var.START_VOTES.clear()
if lpl == 0: if lpl <= 0:
population = (messages["no_players_remaining"]) population = messages["no_players_remaining"]
else: else:
population = (messages["new_player_count"]).format(lpl) population = messages["new_player_count"].format(lpl)
if what == "part" and (not var.PART_GRACE_TIME or var.PHASE == "join"): reveal = ""
if get_role(nick) != "person" and var.ROLE_REVEAL in ("on", "team"): if get_role(user.nick) == "person" or var.ROLE_REVEAL not in ("on", "team"):
msg = (messages["part_death"] + "{2}").format(nick, get_reveal_role(nick), population) reveal = "_no_reveal"
else:
msg = (messages["part_death_no_reveal"] + "{1}").format(nick, population) grace_times = {"part": var.PART_GRACE_TIME, "quit": var.QUIT_GRACE_TIME, "account": var.ACC_GRACE_TIME, "leave": 0}
elif what in ("quit", "badnick") and (not var.QUIT_GRACE_TIME or var.PHASE == "join"):
if get_role(nick) != "person" and var.ROLE_REVEAL in ("on", "team"): reason = what
msg = (messages["quit_death"] + "{2}").format(nick, get_reveal_role(nick), population) if reason == "badnick":
else: reason = "quit"
msg = (messages["quit_death_no_reveal"] + "{1}").format(nick, population) elif reason == "kick":
elif what == "account" and (not var.ACC_GRACE_TIME or var.PHASE == "join"): reason = "leave"
if get_role(nick) != "person" and var.ROLE_REVEAL in ("on", "team"):
msg = (messages["account_death_2"] + "{2}").format(nick, get_reveal_role(nick), population) if reason in grace_times and (grace_times[reason] <= 0 or var.PHASE == "join"):
else: msg = messages["{0}_death{1}".format(reason, reveal)]
msg = (messages["account_death_no_reveal_2"] + "{1}").format(nick, population) elif what != "kick": # There's time for the player to rejoin the game
elif what != "kick": user.send(messages["part_grace_time_notice"].format(botconfig.CHANNEL, var.PART_GRACE_TIME))
cli.msg(nick, messages["part_grace_time_notice"].format(botconfig.CHANNEL, var.PART_GRACE_TIME)) msg = messages["player_missing"]
msg = messages["player_missing"].format(nick) population = ""
killplayer = False killplayer = False
else:
if get_role(nick) != "person" and var.ROLE_REVEAL in ("on", "team"): channels.Main.send(msg.format(user, get_reveal_role(user.nick)) + population) # FIXME: Need to fix this once get_reveal_role() accepts User instances
msg = (messages["leave_death"] + "{2}").format(nick, get_reveal_role(nick), population) var.SPECTATING_WOLFCHAT.discard(user.nick) # FIXME: Need to fix this line and the one below once the variables hold User instances
else: var.SPECTATING_DEADCHAT.discard(user.nick)
msg = (messages["leave_death_no_reveal"] + "{1}").format(nick, population) leave_deadchat(user.client, user.nick)
# kick used to give stasis, now it does not; the op that performed the kick should add their own warning
cli.msg(botconfig.CHANNEL, msg) if what not in ("badnick", "account") and user.nick in var.USERS: # FIXME: Need to move mode toggling somewhere saner
var.SPECTATING_WOLFCHAT.discard(nick) var.USERS[user.nick]["modes"] = set()
var.SPECTATING_DEADCHAT.discard(nick) var.USERS[user.nick]["moded"] = set()
leave_deadchat(cli, nick)
if what not in ("badnick", "account") and users.exists(nick):
users.get(nick).modes = set()
users.get(nick).moded = set()
if killplayer: if killplayer:
del_player(cli, nick, death_triggers = False) del_player(user.client, user.nick, death_triggers=False)
else: else:
var.DISCONNECTED[nick] = (acc, ident + "@" + host, datetime.now(), what) temp = user.lower()
var.DISCONNECTED[user.nick] = (temp.account, temp.userhost, datetime.now(), what) # FIXME: Need to make var.DISCONNECTED hold User instances
#Functions decorated with hook do not parse the nick by default
hook("part")(lambda cli, nick, *rest: leave(cli, "part", nick, rest[0]))
hook("quit")(lambda cli, nick, *rest: leave(cli, "quit", nick, rest[0]))
hook("kick")(lambda cli, nick, *rest: leave(cli, "kick", rest[1], rest[0]))
@cmd("quit", "leave", pm=True, phases=("join", "day", "night")) @cmd("quit", "leave", pm=True, phases=("join", "day", "night"))
def leave_game(cli, nick, chan, rest): def leave_game(cli, nick, chan, rest):
@ -5964,7 +5942,7 @@ def expire_start_votes(cli, chan):
return return
with var.WARNING_LOCK: with var.WARNING_LOCK:
var.START_VOTES = set() var.START_VOTES.clear()
cli.msg(chan, messages["start_expired"]) cli.msg(chan, messages["start_expired"])
@cmd("start", phases=("none", "join")) @cmd("start", phases=("none", "join"))
@ -6124,9 +6102,9 @@ def start(cli, nick, chan, forced = False, restart = ""):
var.PHASE = "join" var.PHASE = "join"
return return
if var.ADMIN_TO_PING and not restart: if var.ADMIN_TO_PING is not None and not restart:
for decor in (COMMANDS.get("join", []) + COMMANDS.get("start", [])): for decor in (COMMANDS["join"] + COMMANDS["start"]):
decor(lambda *spam: cli.msg(chan, messages["command_disabled_admin"])) decor(_command_disabled)
var.ROLES = {} var.ROLES = {}
var.GUNNERS = {} var.GUNNERS = {}
@ -7034,55 +7012,54 @@ def myrole(cli, nick, chan, rest):
message += "." message += "."
pm(cli, nick, message) pm(cli, nick, message)
@cmd("aftergame", "faftergame", flag="D", raw_nick=True, pm=True) @command("aftergame", "faftergame", flag="D", pm=True)
def aftergame(cli, rawnick, chan, rest): # XXX: lastgame (just below this one) and sighandler (top of file) also need updating alongside this one def aftergame(var, wrapper, message):
"""Schedule a command to be run after the current game.""" """Schedule a command to be run after the current game."""
nick = parse_nick(rawnick)[0] if not message.strip():
if not rest.strip(): wrapper.pm(messages["incorrect_syntax"])
cli.notice(nick, messages["incorrect_syntax"])
return return
rst = re.split(" +", rest) args = re.split(" +", message)
cmd = rst.pop(0).lower().replace(botconfig.CMD_CHAR, "", 1).strip() before, prefix, after = args.pop(0).lower().partition(botconfig.CMD_CHAR)
if not prefix: # the prefix was not in the string
cmd = before
elif after and not before: # message was prefixed
cmd = after
else: # some weird thing, e.g. "fsay!" or even "fs!ay"; we don't care about that
return
if cmd in COMMANDS.keys(): if cmd in COMMANDS:
def do_action(): def do_action():
for fn in COMMANDS[cmd]: for fn in COMMANDS[cmd]:
fn.aftergame = True fn.aftergame = True
fn.caller(cli, rawnick, botconfig.CHANNEL if fn.chan else nick, " ".join(rst)) fn.caller(wrapper.source.client, wrapper.source.rawnick, channels.Main.name if fn.chan else users.Bot.nick, " ".join(args))
fn.aftergame = False fn.aftergame = False
else: else:
cli.notice(nick, messages["command_not_found"]) wrapper.pm(messages["command_not_found"])
return return
if var.PHASE == "none": if var.PHASE == "none":
do_action() do_action()
return return
fullcmd = cmd channels.Main.send(messages["command_scheduled"].format(" ".join([cmd] + args), wrapper.source))
if rst:
fullcmd += " "
fullcmd += " ".join(rst)
cli.msg(botconfig.CHANNEL, messages["command_scheduled"].format(fullcmd, nick))
var.AFTER_FLASTGAME = do_action var.AFTER_FLASTGAME = do_action
def _command_disabled(var, wrapper, message):
wrapper.send(messages["command_disabled_admin"])
@cmd("lastgame", "flastgame", flag="D", raw_nick=True, pm=True) @command("lastgame", "flastgame", flag="D", pm=True)
def flastgame(cli, rawnick, chan, rest): def flastgame(var, wrapper, message):
"""Disables starting or joining a game, and optionally schedules a command to run after the current game ends.""" """Disables starting or joining a game, and optionally schedules a command to run after the current game ends."""
nick, _, ident, host = parse_nick(rawnick)
chan = botconfig.CHANNEL
if var.PHASE != "join": if var.PHASE != "join":
for decor in (COMMANDS.get("join", []) + COMMANDS.get("start", [])): for decor in (COMMANDS["join"] + COMMANDS["start"]):
decor(lambda *spam: cli.msg(chan, messages["command_disabled_admin"])) decor(_command_disabled)
cli.msg(chan, messages["disable_new_games"].format(nick)) channels.Main.send(messages["disable_new_games"].format(wrapper.source))
var.ADMIN_TO_PING = nick var.ADMIN_TO_PING = wrapper.source
if rest.strip(): if message.strip():
aftergame.func(cli, rawnick, botconfig.CHANNEL, rest) # XXX: Old API aftergame.func(var, wrapper, message)
@cmd("gamestats", "gstats", pm=True) @cmd("gamestats", "gstats", pm=True)
def game_stats(cli, nick, chan, rest): def game_stats(cli, nick, chan, rest):