Amnesiac fixes and redo stop_game readout logic
- Account for revealing totem + amnesiac in experimental !stats properly - Fix amnesiac blacklist checks to be consistent with each other - Remove non-events from villager.py -- these always ran before or after all other events, so there was no point in them being events in the first place - stop_game now follows the mainroles/allroles pattern instead of roles/templates pattern. This also modifies the data stored in db stats, and fixes readouts for cases where we do goofy stuff with secondary roles
This commit is contained in:
parent
1a446205ce
commit
56f2bacd3a
@ -713,6 +713,8 @@
|
||||
"guardian_villager_win": "Game over! All the wolves are dead! The remaining villagers throw a party in honor of the guardian angels that watched over the village, and live happily ever after.",
|
||||
"guardian_lose_no_guards": "Game over! The remaining villagers managed to destroy the wolves, however the guardians that used to watch over the village are nowhere to be found. The village lives on in an uneasy peace, not knowing when they will be destroyed completely now that they are defenseless.",
|
||||
"guardian_lose_with_guards": "Game over! The guardians, angered by the loss of everyone they were meant to guard, engage the wolves in battle. After the dust settles, the wolves remain standing.",
|
||||
"endgame_roleswap_long": "was {0}",
|
||||
"endgame_roleswap_short": "{0}",
|
||||
"sleepy_nightmare_begin": "While walking through the woods, you hear the clopping of hooves behind you. Turning around, you see a large black horse with dark red eyes and flames where its mane and tail would be. After a brief period of time, it starts chasing after you! You think if you can cross the bridge over the nearby river you'll be safe, but your surroundings are almost unrecognizable in this darkness.",
|
||||
"sleepy_nightmare_navigate": "You can pm me \"north\", \"east\", \"south\", and \"west\", or their abbreviations \"n\", \"e\", \"s\", and \"w\" to navigate.",
|
||||
"sleepy_nightmare_0": "You find yourself deep in the heart of the woods, with imposing trees covering up what little light exists with their dense canopy. The paths here are very twisty, and it's easy to wind up going in circles if one is not careful. Directions are {0}.",
|
||||
|
@ -260,12 +260,15 @@ def add_game(mode, size, started, finished, winner, players, options):
|
||||
|
||||
Players dict format:
|
||||
{
|
||||
version: 2 (key is omitted for v1)
|
||||
nick: "Nickname"
|
||||
account: "Account name" (or None, "*" is converted to None)
|
||||
ident: "Ident"
|
||||
host: "Host"
|
||||
role: "role name"
|
||||
templates: ["template names", ...]
|
||||
mainrole: "role name" (v2+)
|
||||
allroles: {"role name", ...} (v2+)
|
||||
role: "role name" (v1 only)
|
||||
templates: ["template names", ...] (v1 only)
|
||||
special: ["special qualities", ... (lover, entranced, etc.)]
|
||||
won: True/False
|
||||
iwon: True/False
|
||||
|
@ -14,25 +14,35 @@ from src.messages import messages
|
||||
from src.events import Event
|
||||
|
||||
ROLES = UserDict() # type: Dict[users.User, str]
|
||||
STATS_FLAG = False # if True, we begin accounting for amnesiac in update_stats
|
||||
|
||||
def _get_blacklist(var):
|
||||
# matchmaker is blacklisted if AMNESIAC_NIGHTS > 1 due to only being able to act night 1.
|
||||
|
||||
@event_listener("role_assignment")
|
||||
def on_role_assignment(evt, var, gamemode, pl):
|
||||
# matchmaker is blacklisted if AMNESIAC_NIGHTS > 1 due to only being able to act night 1
|
||||
# clone and traitor are blacklisted due to assumptions made in default !stats computations.
|
||||
# If you remove these from the blacklist you will need to modify the default !stats logic
|
||||
# chains in order to correctly account for these. As a forewarning, such modifications are
|
||||
# nontrivial and will likely require a great deal of thought (and likely new tracking vars)
|
||||
# FIXME: once experimental stats become the new stats, clone and traitor will work properly
|
||||
# and we can remove those from hardcoded blacklist and remove this comment block.
|
||||
blacklist = var.TEMPLATE_RESTRICTIONS.keys() | var.AMNESIAC_BLACKLIST | {var.DEFAULT_ROLE, "amnesiac", "clone", "traitor"}
|
||||
if var.AMNESIAC_NIGHTS > 1:
|
||||
blacklist.add("matchmaker")
|
||||
return blacklist
|
||||
|
||||
roles = var.ROLE_GUIDE.keys() - var.TEMPLATE_RESTRICTIONS.keys() - var.AMNESIAC_BLACKLIST - {var.DEFAULT_ROLE, "amnesiac", "clone", "traitor"}
|
||||
if var.AMNESIAC_NIGHTS > 1 and "matchmaker" in roles:
|
||||
roles.remove("matchmaker")
|
||||
@event_listener("role_assignment")
|
||||
def on_role_assignment(evt, var, gamemode, pl):
|
||||
roles = var.ROLE_GUIDE.keys() - _get_blacklist(var)
|
||||
for amnesiac in get_all_players(("amnesiac",)):
|
||||
ROLES[amnesiac] = random.choice(list(roles))
|
||||
|
||||
@event_listener("transition_night_begin")
|
||||
def on_transition_night_begin(evt, var):
|
||||
global STATS_FLAG
|
||||
if var.NIGHT_COUNT == var.AMNESIAC_NIGHTS:
|
||||
amnesiacs = get_all_players(("amnesiac",))
|
||||
if amnesiacs and not var.HIDDEN_AMNESIAC:
|
||||
STATS_FLAG = True
|
||||
|
||||
for amn in amnesiacs:
|
||||
role = ROLES[amn]
|
||||
@ -72,6 +82,7 @@ def on_investigate(evt, var, actor, target):
|
||||
|
||||
@event_listener("exchange_roles")
|
||||
def on_exchange_roles(evt, var, actor, target, actor_role, target_role):
|
||||
# FIXME: exchange totem messes with var.HIDDEN_AMNESIAC (the new amnesiac is no longer hidden should they die)
|
||||
if actor_role == "amnesiac":
|
||||
actor_role = ROLES[actor]
|
||||
if target in ROLES:
|
||||
@ -94,6 +105,9 @@ def on_exchange_roles(evt, var, actor, target, actor_role, target_role):
|
||||
|
||||
@event_listener("revealing_totem")
|
||||
def on_revealing_totem(evt, var, votee):
|
||||
if evt.data["role"] not in _get_blacklist(var) and not var.HIDDEN_AMNESIAC and len(var.ORIGINAL_ROLES["amnesiac"]):
|
||||
global STATS_FLAG
|
||||
STATS_FLAG = True
|
||||
if evt.data["role"] == "amnesiac":
|
||||
role = ROLES[votee]
|
||||
change_role(votee, "amnesiac", role)
|
||||
@ -106,15 +120,14 @@ def on_revealing_totem(evt, var, votee):
|
||||
|
||||
@event_listener("get_reveal_role")
|
||||
def on_reveal_role(evt, var, user):
|
||||
if var.HIDDEN_AMNESIAC and user in var.ORIGINAL_ROLES["amnesiac"]:
|
||||
if var.HIDDEN_AMNESIAC and var.ORIGINAL_MAIN_ROLES[user] == "amnesiac":
|
||||
evt.data["role"] = "amnesiac"
|
||||
|
||||
@event_listener("get_endgame_message")
|
||||
def on_get_endgame_message(evt, var, role, players, original_roles):
|
||||
def on_get_endgame_message(evt, var, player, role, is_mainrole):
|
||||
if role == "amnesiac":
|
||||
for player in players:
|
||||
evt.data["message"].append("\u0002{0}\u0002 (would be {1})".format(player, ROLES[player]))
|
||||
evt.stop_processing = True
|
||||
# FIXME: Harcoded English
|
||||
evt.data["message"].append("would be {0}".format(ROLES[player]))
|
||||
|
||||
@event_listener("revealroles_role")
|
||||
def on_revealroles_role(evt, var, user, role):
|
||||
@ -123,12 +136,13 @@ def on_revealroles_role(evt, var, user, role):
|
||||
|
||||
@event_listener("update_stats")
|
||||
def on_update_stats(evt, var, player, mainrole, revealrole, allroles):
|
||||
if not var.HIDDEN_AMNESIAC and var.NIGHT_COUNT >= var.AMNESIAC_NIGHTS:
|
||||
if not var.AMNESIAC_BLACKLIST & {mainrole, revealrole}: # make sure roles aren't blacklisted
|
||||
evt.data["possible"].add("amnesiac")
|
||||
if STATS_FLAG and not _get_blacklist(var) & {mainrole, revealrole}:
|
||||
evt.data["possible"].add("amnesiac")
|
||||
|
||||
@event_listener("reset")
|
||||
def on_reset(evt, var):
|
||||
global STATS_FLAG
|
||||
ROLES.clear()
|
||||
STATS_FLAG = False
|
||||
|
||||
# vim: set sw=4 expandtab:
|
||||
|
@ -35,25 +35,6 @@ def on_transition_night_end(evt, var):
|
||||
cultist.queue_message(messages[to_send])
|
||||
cultist.send_messages()
|
||||
|
||||
# No listeners should register before this one
|
||||
# This sets up the initial state, based on village/wolfteam/neutral affiliation
|
||||
@event_listener("player_win", priority=0)
|
||||
def on_player_win(evt, var, user, role, winner, survived):
|
||||
# init won/iwon to False
|
||||
evt.data["won"] = False
|
||||
evt.data["iwon"] = False
|
||||
|
||||
if role in var.WOLFTEAM_ROLES or (var.DEFAULT_ROLE == "cultist" and role in var.HIDDEN_ROLES):
|
||||
if winner == "wolves":
|
||||
evt.data["won"] = True
|
||||
evt.data["iwon"] = survived
|
||||
elif role in var.TRUE_NEUTRAL_ROLES:
|
||||
# handled in their individual files
|
||||
pass
|
||||
elif winner == "villagers":
|
||||
evt.data["won"] = True
|
||||
evt.data["iwon"] = survived
|
||||
|
||||
@event_listener("chk_win", priority=3)
|
||||
def on_chk_win(evt, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
|
||||
if evt.data["winner"] is not None:
|
||||
@ -68,18 +49,4 @@ def on_chk_win(evt, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
|
||||
evt.data["winner"] = "wolves"
|
||||
evt.data["message"] = messages["wolf_win_greater"]
|
||||
|
||||
@event_listener("get_final_role", priority=0)
|
||||
def on_get_final_role(evt, var, user, role):
|
||||
if user.nick in var.FINAL_ROLES:
|
||||
evt.data["role"] = var.FINAL_ROLES[user.nick]
|
||||
|
||||
@event_listener("get_endgame_message", priority=10)
|
||||
def on_get_endgame_message(evt, var, role, players, original_roles):
|
||||
for player in players:
|
||||
if player in original_roles and role not in var.TEMPLATE_RESTRICTIONS:
|
||||
evt.data["message"].append("\u0002{0}\u0002 ({1}{2})".format(player, "" if evt.data["done"] else "was ", original_roles[player]))
|
||||
evt.data["done"] = True
|
||||
else:
|
||||
evt.data["message"].append("\u0002{0}\u0002".format(player))
|
||||
|
||||
# vim: set sw=4 expandtab:
|
||||
|
@ -186,7 +186,7 @@ class User(IRCContext):
|
||||
self._ident = ident
|
||||
self._host = host
|
||||
self.realname = realname
|
||||
self.account = account if not var.DISABLE_ACCOUNTS else None
|
||||
self.account = account
|
||||
self.channels = {}
|
||||
self.timestamp = time.time()
|
||||
self.sets = []
|
||||
@ -199,7 +199,7 @@ class User(IRCContext):
|
||||
self.ident = ident
|
||||
self.host = host
|
||||
self.realname = realname
|
||||
self.account = account if not var.DISABLE_ACCOUNTS else None
|
||||
self.account = account
|
||||
self.timestamp = time.time()
|
||||
|
||||
elif ident is not None and host is not None:
|
||||
|
117
src/wolfgame.py
117
src/wolfgame.py
@ -73,7 +73,6 @@ var.LAST_GOAT = {}
|
||||
var.USERS = {}
|
||||
|
||||
var.ADMIN_PINGING = False
|
||||
var.ORIGINAL_ROLES = UserDict() # type: Dict[str, Set[users.User]]
|
||||
var.DCED_LOSERS = UserSet() # type: Set[users.User]
|
||||
var.PLAYERS = {}
|
||||
var.DCED_PLAYERS = {}
|
||||
@ -84,7 +83,9 @@ var.TIMERS = {}
|
||||
var.OLD_MODES = defaultdict(set)
|
||||
|
||||
var.ROLES = UserDict() # type: Dict[str, Set[users.User]]
|
||||
var.ORIGINAL_ROLES = UserDict() # type: Dict[str, Set[users.User]]
|
||||
var.MAIN_ROLES = UserDict() # type: Dict[users.User, str]
|
||||
var.ORIGINAL_MAIN_ROLES = UserDict() # type: Dict[users.User, str]
|
||||
var.ALL_PLAYERS = UserList()
|
||||
var.FORCE_ROLES = DefaultUserDict(UserSet)
|
||||
|
||||
@ -340,6 +341,7 @@ def reset():
|
||||
var.ORIGINAL_ROLES.clear()
|
||||
var.ROLES["person"] = UserSet()
|
||||
var.MAIN_ROLES.clear()
|
||||
var.ORIGINAL_MAIN_ROLES.clear()
|
||||
var.FORCE_ROLES.clear()
|
||||
|
||||
evt = Event("reset", {})
|
||||
@ -1988,36 +1990,53 @@ def stop_game(var, winner="", abort=False, additional_winners=None, log=True):
|
||||
|
||||
roles_msg = []
|
||||
|
||||
origroles = {} # user-based list of original roles
|
||||
with copy.deepcopy(var.ORIGINAL_ROLES) as rolelist:
|
||||
for role, playerlist in var.ORIGINAL_ROLES.items():
|
||||
if role in var.TEMPLATE_RESTRICTIONS.keys():
|
||||
continue
|
||||
for p in playerlist:
|
||||
# The final role is set at priority 0, other roles can override that
|
||||
evt = Event("get_final_role", {"role": role})
|
||||
evt.dispatch(var, p, role)
|
||||
if role != evt.data["role"]:
|
||||
origroles[p] = role
|
||||
rolelist[role].remove(p)
|
||||
rolelist[evt.data["role"]].add(p)
|
||||
# squirrel away a copy of our original roleset for stats recording, as the following code
|
||||
# modifies var.ORIGINAL_ROLES and var.ORIGINAL_MAIN_ROLES.
|
||||
rolecounts = {role: len(players) for role, players in var.ORIGINAL_ROLES.items()}
|
||||
|
||||
done = False
|
||||
for role in role_order():
|
||||
if len(rolelist[role]) == 0:
|
||||
continue
|
||||
evt = Event("get_endgame_message", {"message": [], "done": done})
|
||||
evt.dispatch(var, role, rolelist[role], origroles)
|
||||
# save some typing
|
||||
rolemap = var.ORIGINAL_ROLES
|
||||
mainroles = var.ORIGINAL_MAIN_ROLES
|
||||
orig_main = {} # if get_final_role changes mainroles, we want to stash original main role
|
||||
|
||||
msg = evt.data["message"]
|
||||
done = evt.data["done"]
|
||||
for player, role in mainroles.items():
|
||||
evt = Event("get_final_role", {"role": var.FINAL_ROLES.get(player.nick, role)})
|
||||
evt.dispatch(var, player, role)
|
||||
if role != evt.data["role"]:
|
||||
rolemap[role].remove(player)
|
||||
rolemap[evt.data["role"]].add(player)
|
||||
mainroles[player] = evt.data["role"]
|
||||
orig_main[player] = role
|
||||
|
||||
if len(rolelist[role]) == 2:
|
||||
roles_msg.append("The {1} were {0[0]} and {0[1]}.".format(msg, plural(role)))
|
||||
elif len(rolelist[role]) == 1:
|
||||
roles_msg.append("The {1} was {0[0]}.".format(msg, role))
|
||||
# track if we already printed "was" for a role swap, e.g. The wolves were A (was seer), B (harlot)
|
||||
# so that we can make the message a bit more concise
|
||||
roleswap_key = "endgame_roleswap_long"
|
||||
|
||||
for role in role_order():
|
||||
numrole = len(rolemap[role])
|
||||
if numrole == 0:
|
||||
continue
|
||||
msg = []
|
||||
for player in rolemap[role]:
|
||||
# check if the player changed roles during game, and if so insert the "was X" message
|
||||
player_msg = []
|
||||
if mainroles[player] == role and player in orig_main:
|
||||
player_msg.append(messages[roleswap_key].format(orig_main[player]))
|
||||
roleswap_key = "endgame_roleswap_short"
|
||||
evt = Event("get_endgame_message", {"message": player_msg})
|
||||
evt.dispatch(var, player, role, is_mainrole=mainroles[player] == role)
|
||||
if player_msg:
|
||||
msg.append("\u0002{0}\u0002 ({1})".format(player, ", ".join(player_msg)))
|
||||
else:
|
||||
roles_msg.append("The {2} were {0}, and {1}.".format(", ".join(msg[0:-1]), msg[-1], plural(role)))
|
||||
msg.append("\u0002{0}\u0002".format(player))
|
||||
|
||||
# FIXME: get rid of hardcoded English
|
||||
if numrole == 2:
|
||||
roles_msg.append("The {1} were {0[0]} and {0[1]}.".format(msg, plural(role)))
|
||||
elif numrole == 1:
|
||||
roles_msg.append("The {1} was {0[0]}.".format(msg, role))
|
||||
else:
|
||||
roles_msg.append("The {2} were {0}, and {1}.".format(", ".join(msg[0:-1]), msg[-1], plural(role)))
|
||||
|
||||
message = ""
|
||||
count = 0
|
||||
@ -2041,30 +2060,19 @@ def stop_game(var, winner="", abort=False, additional_winners=None, log=True):
|
||||
|
||||
channels.Main.send(*roles_msg)
|
||||
|
||||
# map player: all roles of that player (for below)
|
||||
allroles = {player: {role for role, players in rolemap.items() if player in players} for player in mainroles}
|
||||
|
||||
# "" indicates everyone died or abnormal game stop
|
||||
if winner != "" or log:
|
||||
plrl = {}
|
||||
pltp = defaultdict(list)
|
||||
winners = set()
|
||||
player_list = []
|
||||
if additional_winners is not None:
|
||||
winners.update(additional_winners)
|
||||
for role, ppl in var.ORIGINAL_ROLES.items():
|
||||
if role in var.TEMPLATE_RESTRICTIONS.keys():
|
||||
for x in ppl:
|
||||
if x is not None:
|
||||
pltp[x].append(role)
|
||||
continue
|
||||
for x in ppl:
|
||||
if x is not None:
|
||||
if x.nick in var.FINAL_ROLES:
|
||||
plrl[x] = var.FINAL_ROLES[x.nick]
|
||||
else:
|
||||
plrl[x] = role
|
||||
for plr, rol in plrl.items():
|
||||
orol = rol # original role, since we overwrite rol in case of clone
|
||||
for plr, rol in mainroles.items():
|
||||
splr = plr.nick # FIXME: for backwards-compat
|
||||
pentry = {"nick": None,
|
||||
pentry = {"version": 2,
|
||||
"nick": None,
|
||||
"account": None,
|
||||
"ident": None,
|
||||
"host": None,
|
||||
@ -2081,8 +2089,8 @@ def stop_game(var, winner="", abort=False, additional_winners=None, log=True):
|
||||
pentry["ident"] = plr.ident
|
||||
pentry["host"] = plr.host
|
||||
|
||||
pentry["role"] = rol
|
||||
pentry["templates"] = pltp[plr]
|
||||
pentry["mainrole"] = rol
|
||||
pentry["allroles"] = allroles[plr]
|
||||
if splr in var.LOVERS:
|
||||
pentry["special"].append("lover")
|
||||
|
||||
@ -2090,6 +2098,16 @@ def stop_game(var, winner="", abort=False, additional_winners=None, log=True):
|
||||
iwon = False
|
||||
survived = get_players()
|
||||
if not pentry["dced"]:
|
||||
# determine default win status (event can override)
|
||||
if rol in var.WOLFTEAM_ROLES or (var.DEFAULT_ROLE == "cultist" and role in var.HIDDEN_ROLES):
|
||||
if winner == "wolves":
|
||||
won = True
|
||||
iwon = plr in survived
|
||||
elif role not in var.TRUE_NEUTRAL_ROLES and winner == "villagers":
|
||||
won = True
|
||||
iwon = plr in survived
|
||||
# true neutral roles are handled via the event below
|
||||
|
||||
evt = Event("player_win", {"won": won, "iwon": iwon, "special": pentry["special"]})
|
||||
evt.dispatch(var, plr, rol, winner, plr in survived)
|
||||
won = evt.data["won"]
|
||||
@ -2125,9 +2143,7 @@ def stop_game(var, winner="", abort=False, additional_winners=None, log=True):
|
||||
# cannot win with dead lover (if splr in survived and lvr is not, that means lvr idled out)
|
||||
continue
|
||||
|
||||
lvrrol = "" #somehow lvrrol wasn't set and caused a crash once
|
||||
if lvuser in plrl:
|
||||
lvrrol = plrl[lvuser]
|
||||
lvrrol = mainroles[lvuser]
|
||||
|
||||
if not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
|
||||
iwon = True
|
||||
@ -5390,12 +5406,14 @@ def start(cli, nick, chan, forced = False, restart = ""):
|
||||
return
|
||||
for user in var.FORCE_ROLES[role]:
|
||||
var.MAIN_ROLES[user] = role
|
||||
var.ORIGINAL_MAIN_ROLES[user] = role
|
||||
to_add.add(user)
|
||||
count -= 1
|
||||
|
||||
selected = random.sample(vils, count)
|
||||
for x in selected:
|
||||
var.MAIN_ROLES[x] = role
|
||||
var.ORIGINAL_MAIN_ROLES[x] = role
|
||||
vils.remove(x)
|
||||
var.ROLES[role] = UserSet(selected)
|
||||
var.ROLES[role].update(to_add)
|
||||
@ -5406,6 +5424,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
|
||||
var.ROLES[var.DEFAULT_ROLE].update(vils)
|
||||
for x in vils:
|
||||
var.MAIN_ROLES[x] = var.DEFAULT_ROLE
|
||||
var.ORIGINAL_MAIN_ROLES[x] = var.DEFAULT_ROLE
|
||||
if vils:
|
||||
for pr in possible_rolesets:
|
||||
pr[var.DEFAULT_ROLE] += len(vils)
|
||||
|
Loading…
x
Reference in New Issue
Block a user