8-9p has only one safe, 2 wolves, and a dullahan. Usually this means dulla controls the game and the winner is entirely determined by whether or not both wolves are on the kill list. Aka it sucks.
1349 lines
68 KiB
Python
1349 lines
68 KiB
Python
import random
|
|
import math
|
|
import threading
|
|
import copy
|
|
from datetime import datetime
|
|
from collections import defaultdict, OrderedDict
|
|
|
|
import botconfig
|
|
import src.settings as var
|
|
from src.utilities import *
|
|
from src.messages import messages
|
|
from src.decorators import handle_error
|
|
from src import events, channels, users
|
|
|
|
def game_mode(name, minp, maxp, likelihood = 0):
|
|
def decor(c):
|
|
c.name = name
|
|
var.GAME_MODES[name] = (c, minp, maxp, likelihood)
|
|
return c
|
|
return decor
|
|
|
|
reset_roles = lambda i: OrderedDict([(role, (0,) * len(i)) for role in var.ROLE_GUIDE])
|
|
|
|
def get_lovers():
|
|
lovers = []
|
|
pl = list_players()
|
|
for lover in var.LOVERS:
|
|
done = None
|
|
for i, lset in enumerate(lovers):
|
|
if lover in pl and lover in lset:
|
|
if done is not None: # plot twist! two clusters turn out to be linked!
|
|
done.update(lset)
|
|
for lvr in var.LOVERS[lover]:
|
|
if lvr in pl:
|
|
done.add(lvr)
|
|
|
|
lset.clear()
|
|
continue
|
|
|
|
for lvr in var.LOVERS[lover]:
|
|
if lvr in pl:
|
|
lset.add(lvr)
|
|
done = lset
|
|
|
|
if done is None and lover in pl:
|
|
lovers.append(set())
|
|
lovers[-1].add(lover)
|
|
for lvr in var.LOVERS[lover]:
|
|
if lvr in pl:
|
|
lovers[-1].add(lvr)
|
|
|
|
while set() in lovers:
|
|
lovers.remove(set())
|
|
|
|
return lovers
|
|
|
|
class GameMode:
|
|
def __init__(self, arg=""):
|
|
if not arg:
|
|
return
|
|
|
|
arg = arg.replace("=", ":").replace(";", ",")
|
|
|
|
pairs = [arg]
|
|
while pairs:
|
|
pair, *pairs = pairs[0].split(",", 1)
|
|
change = pair.lower().replace(":", " ").strip().rsplit(None, 1)
|
|
if len(change) != 2:
|
|
raise InvalidModeException(messages["invalid_mode_args"].format(arg))
|
|
|
|
key, val = change
|
|
if key in ("role reveal", "reveal roles"):
|
|
if val not in ("on", "off", "team"):
|
|
raise InvalidModeException(messages["invalid_reveal"].format(val))
|
|
self.ROLE_REVEAL = val
|
|
if val == "off" and not hasattr(self, "STATS_TYPE"):
|
|
self.STATS_TYPE = "disabled"
|
|
elif val == "team" and not hasattr(self, "STATS_TYPE"):
|
|
self.STATS_TYPE = "team"
|
|
elif key in ("stats type", "stats"):
|
|
if val not in ("default", "accurate", "team", "disabled"):
|
|
raise InvalidModeException(messages["invalid_stats"].format(val))
|
|
self.STATS_TYPE = val
|
|
elif key == "abstain":
|
|
if val not in ("enabled", "restricted", "disabled"):
|
|
raise InvalidModeException(messages["invalid_abstain"].format(val))
|
|
if val == "enabled":
|
|
self.ABSTAIN_ENABLED = True
|
|
self.LIMIT_ABSTAIN = False
|
|
elif val == "restricted":
|
|
self.ABSTAIN_ENABLED = True
|
|
self.LIMIT_ABSTAIN = True
|
|
elif val == "disabled":
|
|
self.ABSTAIN_ENABLED = False
|
|
elif key == "lover wins with fool":
|
|
if val not in ("true", "false"):
|
|
raise InvalidModeException(messages["invalid_lover_wins_with_fool"].format(val))
|
|
self.LOVER_WINS_WITH_FOOL = True if val == "true" else False
|
|
|
|
def startup(self):
|
|
pass
|
|
|
|
def teardown(self):
|
|
pass
|
|
|
|
# Here so any game mode can use it
|
|
def lovers_chk_win(self, evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
|
|
winner = evt.data["winner"]
|
|
if winner is not None and winner.startswith("@"):
|
|
return # fool won, lovers can't win even if they would
|
|
all_lovers = get_lovers()
|
|
if len(all_lovers) != 1:
|
|
return # we need exactly one cluster alive for this to trigger
|
|
|
|
lovers = all_lovers[0]
|
|
|
|
if len(lovers) == lpl:
|
|
evt.data["winner"] = "lovers"
|
|
evt.data["additional_winners"] = list(lovers)
|
|
evt.data["message"] = messages["lovers_win"]
|
|
|
|
def all_dead_chk_win(self, evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
|
|
if evt.data["winner"] == "no_team_wins":
|
|
evt.data["winner"] = "everyone"
|
|
evt.data["message"] = messages["everyone_died_won"]
|
|
|
|
@game_mode("roles", minp = 4, maxp = 35)
|
|
class ChangedRolesMode(GameMode):
|
|
"""Example: !fgame roles=wolf:1,seer:0,guardian angel:1"""
|
|
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.MAX_PLAYERS = 35
|
|
self.ROLE_GUIDE = var.ROLE_GUIDE.copy()
|
|
self.ROLE_INDEX = (var.MIN_PLAYERS,)
|
|
arg = arg.replace("=", ":").replace(";", ",")
|
|
|
|
for role in self.ROLE_GUIDE.keys():
|
|
self.ROLE_GUIDE[role] = (0,)
|
|
|
|
pairs = [arg]
|
|
while pairs:
|
|
pair, *pairs = pairs[0].split(",", 1)
|
|
change = pair.replace(":", " ").strip().rsplit(None, 1)
|
|
if len(change) != 2:
|
|
raise InvalidModeException(messages["invalid_mode_roles"].format(arg))
|
|
role, num = change
|
|
try:
|
|
if role.lower() in var.DISABLED_ROLES:
|
|
raise InvalidModeException(messages["role_disabled"].format(role))
|
|
elif role.lower() in self.ROLE_GUIDE:
|
|
self.ROLE_GUIDE[role.lower()] = tuple([int(num)] * len(var.ROLE_INDEX))
|
|
elif role.lower() == "default" and num.lower() in self.ROLE_GUIDE:
|
|
self.DEFAULT_ROLE = num.lower()
|
|
elif role.lower() in ("role reveal", "reveal roles", "stats type", "stats", "abstain", "lover wins with fool"):
|
|
# handled in parent constructor
|
|
pass
|
|
else:
|
|
raise InvalidModeException(messages["specific_invalid_role"].format(role))
|
|
except ValueError:
|
|
raise InvalidModeException(messages["bad_role_value"])
|
|
|
|
@game_mode("default", minp = 4, maxp = 24, likelihood = 20)
|
|
class DefaultMode(GameMode):
|
|
"""Default game mode."""
|
|
def __init__(self, arg="", role_index=var.ROLE_INDEX, role_guide=var.ROLE_GUIDE.copy()):
|
|
# No extra settings, just an explicit way to revert to default settings
|
|
super().__init__(arg)
|
|
self.ROLE_INDEX = role_index
|
|
self.ROLE_GUIDE = role_guide
|
|
|
|
@game_mode("villagergame", minp = 4, maxp = 9, likelihood = 0)
|
|
class VillagergameMode(GameMode):
|
|
"""This mode definitely does not exist, now please go away."""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.fake_index = var.ROLE_INDEX
|
|
self.fake_guide = var.ROLE_GUIDE.copy()
|
|
self.ROLE_INDEX = ( 4 , 6 , 7 , 8 , 9 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"shaman" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
"harlot" : ( 0 , 0 , 0 , 1 , 1 ),
|
|
"crazed shaman" : ( 0 , 0 , 0 , 0 , 1 ),
|
|
"cursed villager" : ( 0 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
def startup(self):
|
|
events.add_listener("chk_win", self.chk_win)
|
|
events.add_listener("chk_nightdone", self.chk_nightdone)
|
|
events.add_listener("transition_day_begin", self.transition_day)
|
|
events.add_listener("retribution_kill", self.on_retribution_kill, priority=4)
|
|
|
|
def teardown(self):
|
|
events.remove_listener("chk_win", self.chk_win)
|
|
events.remove_listener("chk_nightdone", self.chk_nightdone)
|
|
events.remove_listener("transition_day_begin", self.transition_day)
|
|
events.remove_listener("retribution_kill", self.on_retribution_kill, priority=4)
|
|
|
|
def chk_win(self, evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
|
|
# village can only win via unanimous vote on the bot nick
|
|
# villagergame_lose should probably explain that mechanic
|
|
# Note: not implemented here since that needs to work in default too
|
|
pc = len(var.ALL_PLAYERS)
|
|
if (pc >= 8 and lpl <= 4) or lpl <= 2:
|
|
evt.data["winner"] = ""
|
|
evt.data["message"] = messages["villagergame_lose"].format(botconfig.CMD_CHAR, botconfig.NICK)
|
|
else:
|
|
evt.data["winner"] = None
|
|
|
|
def chk_nightdone(self, evt, cli, var):
|
|
transition_day = evt.data["transition_day"]
|
|
evt.data["transition_day"] = lambda cli2, gameid=0: self.prolong_night(cli2, var, gameid, transition_day)
|
|
|
|
def prolong_night(self, cli, var, gameid, transition_day):
|
|
nspecials = len(var.ROLES["seer"] | var.ROLES["harlot"] | var.ROLES["shaman"] | var.ROLES["crazed shaman"])
|
|
rand = random.gauss(5, 1.5)
|
|
if rand <= 0 and nspecials > 0:
|
|
transition_day(cli, gameid=gameid)
|
|
else:
|
|
t = threading.Timer(abs(rand), transition_day, args=(cli,), kwargs={"gameid": gameid})
|
|
t.start()
|
|
|
|
def transition_day(self, evt, cli, var):
|
|
# 30% chance we kill a safe, otherwise kill at random
|
|
# when killing safes, go after seer, then harlot, then shaman
|
|
self.delaying_night = False
|
|
pl = list_players()
|
|
tgt = None
|
|
seer = None
|
|
hlt = None
|
|
hvst = None
|
|
shmn = None
|
|
if len(var.ROLES["seer"]) == 1:
|
|
seer = list(var.ROLES["seer"])[0]
|
|
if len(var.ROLES["harlot"]) == 1:
|
|
hlt = list(var.ROLES["harlot"])[0]
|
|
hvst = var.HVISITED.get(hlt)
|
|
if hvst:
|
|
pl.remove(hlt)
|
|
if len(var.ROLES["shaman"]) == 1:
|
|
shmn = list(var.ROLES["shaman"])[0]
|
|
if random.random() < 0.3:
|
|
if seer:
|
|
tgt = seer
|
|
elif hvst:
|
|
tgt = hvst
|
|
elif shmn:
|
|
tgt = shmn
|
|
elif hlt and not hvst:
|
|
tgt = hlt
|
|
if not tgt:
|
|
tgt = random.choice(pl)
|
|
from src.roles import wolf
|
|
wolf.KILLS[botconfig.NICK] = [tgt]
|
|
|
|
def on_retribution_kill(self, evt, cli, var, victim, orig_target):
|
|
# There are no wolves for this totem to kill
|
|
if orig_target == "@wolves":
|
|
evt.data["target"] = None
|
|
evt.stop_processing = True
|
|
|
|
@game_mode("foolish", minp = 8, maxp = 24, likelihood = 8)
|
|
class FoolishMode(GameMode):
|
|
"""Contains the fool, be careful not to lynch them!"""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.ROLE_INDEX = ( 8 , 9 , 10 , 11 , 12 , 15 , 17 , 20 , 21 , 22 , 24 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"oracle" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"harlot" : ( 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 ),
|
|
"bodyguard" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
"augur" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"hunter" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"shaman" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 2 , 2 , 2 , 2 , 3 , 3 , 3 , 3 , 4 ),
|
|
"traitor" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 ),
|
|
"wolf cub" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"sorcerer" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"clone" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"fool" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"gunner" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"sharpshooter" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"mayor" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
@game_mode("mad", minp = 7, maxp = 22, likelihood = 4)
|
|
class MadMode(GameMode):
|
|
"""This game mode has mad scientist and many things that may kill you."""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
# gunner and sharpshooter always get 1 bullet
|
|
self.SHOTS_MULTIPLIER = 0.0001
|
|
self.SHARPSHOOTER_MULTIPLIER = 0.0001
|
|
self.ROLE_INDEX = ( 7 , 8 , 10 , 12 , 14 , 15 , 17 , 18 , 20 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"mad scientist" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"detective" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"guardian angel" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
|
|
"hunter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 ),
|
|
"harlot" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
"village drunk" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 ),
|
|
"traitor" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"werecrow" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"wolf cub" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 2 ),
|
|
"cultist" : ( 1 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"vengeful ghost" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
"jester" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"gunner" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"sharpshooter" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
@game_mode("evilvillage", minp = 6, maxp = 18, likelihood = 1)
|
|
class EvilVillageMode(GameMode):
|
|
"""Majority of the village is wolf aligned, safes must secretly try to kill the wolves."""
|
|
def __init__(self, arg=""):
|
|
self.ABSTAIN_ENABLED = False
|
|
super().__init__(arg)
|
|
self.DEFAULT_ROLE = "cultist"
|
|
self.DEFAULT_SEEN_AS_VILL = False
|
|
self.ROLE_INDEX = ( 6 , 8 , 10 , 12 , 15 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"seer" : ( 0 , 1 , 1 , 1 , 1 ),
|
|
"guardian angel" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
"shaman" : ( 0 , 0 , 0 , 1 , 1 ),
|
|
"hunter" : ( 1 , 1 , 1 , 1 , 2 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 1 , 2 ),
|
|
"minion" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"fool" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
def startup(self):
|
|
events.add_listener("chk_win", self.chk_win)
|
|
|
|
def teardown(self):
|
|
events.remove_listener("chk_win", self.chk_win)
|
|
|
|
def chk_win(self, evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
|
|
lsafes = len(list_players(["oracle", "seer", "guardian angel", "shaman", "hunter", "villager"]))
|
|
lcultists = len(list_players(["cultist"]))
|
|
evt.stop_processing = True
|
|
|
|
if lrealwolves == 0 and lsafes == 0:
|
|
evt.data["winner"] = "no_team_wins"
|
|
evt.data["message"] = messages["evil_no_win"]
|
|
elif lrealwolves == 0:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = messages["evil_villager_win"]
|
|
elif lsafes == 0:
|
|
evt.data["winner"] = "wolves"
|
|
evt.data["message"] = messages["evil_wolf_win"]
|
|
elif lcultists == 0:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = messages["evil_cultists_dead"]
|
|
elif lsafes == lpl / 2:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = messages["evil_villager_tie"]
|
|
elif lsafes > lpl / 2:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = messages["evil_more_villagers"]
|
|
else:
|
|
try:
|
|
if evt.data["winner"][0] != "@":
|
|
evt.data["winner"] = None
|
|
except TypeError:
|
|
evt.data["winner"] = None
|
|
|
|
|
|
@game_mode("classic", minp = 4, maxp = 21, likelihood = 0)
|
|
class ClassicMode(GameMode):
|
|
"""Classic game mode from before all the changes."""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.ABSTAIN_ENABLED = False
|
|
self.ROLE_INDEX = ( 4 , 6 , 8 , 10 , 12 , 15 , 17 , 18 , 20 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"village drunk" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"harlot" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"bodyguard" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"detective" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 2 , 2 , 3 , 3 , 3 , 4 ),
|
|
"traitor" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"werecrow" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"gunner" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
@game_mode("rapidfire", minp = 6, maxp = 24, likelihood = 0)
|
|
class RapidFireMode(GameMode):
|
|
"""Many roles that lead to multiple chain deaths."""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.SHARPSHOOTER_CHANCE = 1
|
|
self.DAY_TIME_LIMIT = 480
|
|
self.DAY_TIME_WARN = 360
|
|
self.SHORT_DAY_LIMIT = 240
|
|
self.SHORT_DAY_WARN = 180
|
|
self.MAD_SCIENTIST_SKIPS_DEAD_PLAYERS = 0
|
|
self.ROLE_INDEX = ( 6 , 8 , 10 , 12 , 15 , 18 , 22 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"mad scientist" : ( 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"matchmaker" : ( 0 , 0 , 1 , 1 , 1 , 1 , 2 ),
|
|
"hunter" : ( 0 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"augur" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"time lord" : ( 0 , 0 , 1 , 1 , 1 , 2 , 2 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 2 , 2 , 3 , 4 ),
|
|
"wolf cub" : ( 0 , 1 , 1 , 1 , 2 , 2 , 2 ),
|
|
"traitor" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"vengeful ghost" : ( 0 , 0 , 0 , 1 , 1 , 1 , 2 ),
|
|
"amnesiac" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"assassin" : ( 0 , 1 , 1 , 1 , 2 , 2 , 2 ),
|
|
"gunner" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
"sharpshooter" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
def startup(self):
|
|
events.add_listener("chk_win", self.all_dead_chk_win)
|
|
|
|
def teardown(self):
|
|
events.remove_listener("chk_win", self.all_dead_chk_win)
|
|
|
|
@game_mode("drunkfire", minp = 8, maxp = 17, likelihood = 0)
|
|
class DrunkFireMode(GameMode):
|
|
"""Most players get a gun, quickly shoot all the wolves!"""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.SHARPSHOOTER_CHANCE = 1
|
|
self.DAY_TIME_LIMIT = 480
|
|
self.DAY_TIME_WARN = 360
|
|
self.SHORT_DAY_LIMIT = 240
|
|
self.SHORT_DAY_WARN = 180
|
|
self.NIGHT_TIME_LIMIT = 60
|
|
self.NIGHT_TIME_WARN = 40 # HIT MISS SUICIDE HEADSHOT
|
|
self.GUN_CHANCES = ( 3/7 , 3/7 , 1/7 , 4/5 )
|
|
self.WOLF_GUN_CHANCES = ( 4/7 , 3/7 , 0/7 , 1 )
|
|
self.ROLE_INDEX = ( 8 , 10 , 12 , 14 , 16 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"seer" : ( 1 , 1 , 1 , 2 , 2 ),
|
|
"village drunk" : ( 2 , 3 , 4 , 4 , 5 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 2 , 2 , 3 , 3 ),
|
|
"traitor" : ( 1 , 1 , 1 , 1 , 2 ),
|
|
"hag" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"crazed shaman" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"assassin" : ( 0 , 0 , 0 , 1 , 1 ),
|
|
"gunner" : ( 5 , 6 , 7 , 8 , 9 ),
|
|
"sharpshooter" : ( 2 , 2 , 3 , 3 , 4 ),
|
|
})
|
|
|
|
def startup(self):
|
|
events.add_listener("chk_win", self.all_dead_chk_win)
|
|
|
|
def teardown(self):
|
|
events.remove_listener("chk_win", self.all_dead_chk_win)
|
|
|
|
@game_mode("noreveal", minp = 4, maxp = 21, likelihood = 2)
|
|
class NoRevealMode(GameMode):
|
|
"""Roles are not revealed when players die."""
|
|
def __init__(self, arg=""):
|
|
self.ROLE_REVEAL = "off"
|
|
self.STATS_TYPE = "disabled"
|
|
super().__init__(arg)
|
|
self.ROLE_INDEX = ( 4 , 6 , 8 , 10 , 12 , 15 , 17 , 19 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"guardian angel" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
"mystic" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"detective" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"hunter" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 1 , 2 , 2 , 2 , 3 ),
|
|
"wolf mystic" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"traitor" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
"werecrow" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"clone" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"lycan" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 ),
|
|
"amnesiac" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 0 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
})
|
|
|
|
@game_mode("lycan", minp = 7, maxp = 21, likelihood = 6)
|
|
class LycanMode(GameMode):
|
|
"""Many lycans will turn into wolves. Hunt them down before the wolves overpower the village."""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.ROLE_INDEX = ( 7 , 8 , 9 , 10 , 11 , 12 , 15 , 17 , 19 , 20 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"bodyguard" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"matchmaker" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
"hunter" : ( 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"wolf shaman" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"traitor" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"clone" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 ),
|
|
"lycan" : ( 1 , 1 , 1 , 2 , 2 , 3 , 4 , 4 , 4 , 5 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 ),
|
|
"gunner" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"sharpshooter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"mayor" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
@game_mode("valentines", minp = 8, maxp = 24, likelihood = 0)
|
|
class MatchmakerMode(GameMode):
|
|
"""Love is in the air!"""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.NIGHT_TIME_LIMIT = 150
|
|
self.NIGHT_TIME_WARN = 105
|
|
self.ROLE_INDEX = range(8, 25)
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({
|
|
"wolf" : [math.ceil((i ** 1.4) * 0.06) for i in self.ROLE_INDEX],
|
|
"matchmaker" : [i - math.ceil((i ** 1.4) * 0.06) - (i >= 12) - (i >= 18) for i in self.ROLE_INDEX],
|
|
"monster" : [i >= 12 for i in self.ROLE_INDEX],
|
|
"mad scientist" : [i >= 18 for i in self.ROLE_INDEX],
|
|
})
|
|
|
|
def startup(self):
|
|
events.add_listener("chk_win", self.lovers_chk_win)
|
|
|
|
def teardown(self):
|
|
events.remove_listener("chk_win", self.lovers_chk_win)
|
|
|
|
@game_mode("random", minp = 8, maxp = 24, likelihood = 0)
|
|
class RandomMode(GameMode):
|
|
"""Completely random and hidden roles."""
|
|
def __init__(self, arg=""):
|
|
self.ROLE_REVEAL = random.choice(("on", "off", "team"))
|
|
self.STATS_TYPE = "disabled" if self.ROLE_REVEAL == "off" else random.choice(("disabled", "team"))
|
|
super().__init__(arg)
|
|
self.LOVER_WINS_WITH_FOOL = True
|
|
self.MAD_SCIENTIST_SKIPS_DEAD_PLAYERS = 0 # always make it happen
|
|
self.TEMPLATE_RESTRICTIONS = OrderedDict((template, frozenset()) for template in var.TEMPLATE_RESTRICTIONS)
|
|
|
|
self.TOTEM_CHANCES = { # shaman , crazed , wolf
|
|
"death": ( 8 , 1 , 1 ),
|
|
"protection": ( 6 , 1 , 6 ),
|
|
"silence": ( 4 , 1 , 3 ),
|
|
"revealing": ( 2 , 1 , 5 ),
|
|
"desperation": ( 4 , 1 , 7 ),
|
|
"impatience": ( 7 , 1 , 2 ),
|
|
"pacifism": ( 7 , 1 , 2 ),
|
|
"influence": ( 7 , 1 , 2 ),
|
|
"narcolepsy": ( 4 , 1 , 3 ),
|
|
"exchange": ( 1 , 1 , 1 ),
|
|
"lycanthropy": ( 1 , 1 , 3 ),
|
|
"luck": ( 6 , 1 , 7 ),
|
|
"pestilence": ( 3 , 1 , 1 ),
|
|
"retribution": ( 5 , 1 , 6 ),
|
|
"misdirection": ( 6 , 1 , 4 ),
|
|
"deceit": ( 3 , 1 , 6 ),
|
|
}
|
|
|
|
def startup(self):
|
|
events.add_listener("role_attribution", self.role_attribution)
|
|
events.add_listener("chk_win", self.lovers_chk_win)
|
|
|
|
def teardown(self):
|
|
events.remove_listener("role_attribution", self.role_attribution)
|
|
events.remove_listener("chk_win", self.lovers_chk_win)
|
|
|
|
def role_attribution(self, evt, cli, var, chk_win_conditions, villagers):
|
|
lpl = len(villagers) - 1
|
|
addroles = evt.data["addroles"]
|
|
for role in var.ROLE_GUIDE:
|
|
addroles[role] = 0
|
|
|
|
wolves = var.WOLF_ROLES - {"wolf cub"}
|
|
addroles[random.choice(list(wolves))] += 1 # make sure there's at least one wolf role
|
|
roles = list(var.ROLE_GUIDE.keys() - var.TEMPLATE_RESTRICTIONS.keys() - {"villager", "cultist", "amnesiac"})
|
|
while lpl:
|
|
addroles[random.choice(roles)] += 1
|
|
lpl -= 1
|
|
|
|
addroles["gunner"] = random.randrange(int(len(villagers) ** 1.2 / 4))
|
|
addroles["assassin"] = random.randrange(max(int(len(villagers) ** 1.2 / 8), 1))
|
|
|
|
rolemap = defaultdict(set)
|
|
mainroles = {}
|
|
i = 0
|
|
for role, count in addroles.items():
|
|
if count > 0:
|
|
for j in range(count):
|
|
u = users.FakeUser.from_nick(str(i + j))
|
|
rolemap[role].add(u.nick)
|
|
if role not in var.TEMPLATE_RESTRICTIONS:
|
|
mainroles[u] = role
|
|
i += count
|
|
|
|
if chk_win_conditions(cli, rolemap, mainroles, end_game=False):
|
|
return self.role_attribution(evt, cli, var, chk_win_conditions, villagers)
|
|
|
|
evt.prevent_default = True
|
|
|
|
# Credits to Metacity for designing and current name
|
|
# Blame arkiwitect for the original name of KrabbyPatty
|
|
@game_mode("aleatoire", minp = 8, maxp = 24, likelihood = 4)
|
|
class AleatoireMode(GameMode):
|
|
"""Game mode created by Metacity and balanced by woffle."""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.SHARPSHOOTER_CHANCE = 1
|
|
# SHAMAN , CRAZED SHAMAN , WOLF SHAMAN
|
|
self.TOTEM_CHANCES = { "death": ( 4 , 1 , 0 ),
|
|
"protection": ( 8 , 1 , 0 ),
|
|
"silence": ( 2 , 1 , 0 ),
|
|
"revealing": ( 0 , 1 , 0 ),
|
|
"desperation": ( 1 , 1 , 0 ),
|
|
"impatience": ( 0 , 1 , 0 ),
|
|
"pacifism": ( 0 , 1 , 0 ),
|
|
"influence": ( 0 , 1 , 0 ),
|
|
"narcolepsy": ( 0 , 1 , 0 ),
|
|
"exchange": ( 0 , 1 , 0 ),
|
|
"lycanthropy": ( 0 , 1 , 0 ),
|
|
"luck": ( 0 , 1 , 0 ),
|
|
"pestilence": ( 1 , 1 , 0 ),
|
|
"retribution": ( 4 , 1 , 0 ),
|
|
"misdirection": ( 0 , 1 , 0 ),
|
|
"deceit": ( 0 , 1 , 0 ),
|
|
}
|
|
|
|
# get default values for wolf shaman's chances
|
|
for totem, (s, cs, ws) in self.TOTEM_CHANCES.items():
|
|
self.TOTEM_CHANCES[totem] = (s, cs, var.TOTEM_CHANCES[totem][2])
|
|
|
|
self.ROLE_INDEX = ( 8 , 10 , 12 , 13 , 14 , 15 , 17 , 18 , 21 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({ # village roles
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"shaman" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"matchmaker" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"hunter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"augur" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
"time lord" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ),
|
|
"guardian angel" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 2 , 2 , 2 , 2 , 2 , 3 , 3 , 3 ),
|
|
"wolf cub" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ),
|
|
"traitor" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"werecrow" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
"hag" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"vengeful ghost" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"amnesiac" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"turncoat" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ),
|
|
"assassin" : ( 0 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 ),
|
|
"gunner" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"mayor" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
@game_mode("alpha", minp=10, maxp=24, likelihood=5)
|
|
class AlphaMode(GameMode):
|
|
"""Features the alpha wolf who can turn other people into wolves, be careful whom you trust!"""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.ROLE_INDEX = ( 10 , 12 , 13 , 14 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 24 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({
|
|
#village roles
|
|
"oracle" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"doctor" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"harlot" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"guardian angel" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 ),
|
|
"matchmaker" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"augur" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"vigilante" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 0 , 0 , 0 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 3 , 4 ),
|
|
"alpha wolf" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"traitor" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"werecrow" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"amnesiac" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"lycan" : ( 2 , 2 , 2 , 2 , 3 , 3 , 3 , 3 , 4 , 4 , 4 , 4 ),
|
|
"crazed shaman" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"clone" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 3 , 3 ),
|
|
"mayor" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
# original idea by Rossweisse, implemented by Vgr with help from woffle and jacob1
|
|
@game_mode("guardian", minp = 8, maxp = 16, likelihood = 0)
|
|
class GuardianMode(GameMode):
|
|
"""Game mode full of guardian angels, wolves need to pick them apart!"""
|
|
def __init__(self, arg=""):
|
|
self.LIMIT_ABSTAIN = False
|
|
super().__init__(arg)
|
|
self.ROLE_INDEX = ( 8 , 10 , 12 , 13 , 15 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({
|
|
# village roles
|
|
"bodyguard" : ( 0 , 0 , 0 , 0 , 1 ),
|
|
"guardian angel" : ( 1 , 1 , 2 , 2 , 2 ),
|
|
"shaman" : ( 0 , 1 , 1 , 1 , 1 ),
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 1 , 2 ),
|
|
"werecrow" : ( 0 , 1 , 1 , 1 , 1 ),
|
|
"werekitten" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"alpha wolf" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"jester" : ( 0 , 0 , 0 , 1 , 1 ),
|
|
# templates
|
|
"gunner" : ( 0 , 0 , 0 , 1 , 1 ),
|
|
"cursed villager" : ( 1 , 1 , 2 , 2 , 2 ),
|
|
})
|
|
|
|
self.TOTEM_CHANCES = { # shaman , crazed , wolf
|
|
"death": ( 4 , 1 , 0 ),
|
|
"protection": ( 8 , 1 , 0 ),
|
|
"silence": ( 2 , 1 , 0 ),
|
|
"revealing": ( 0 , 1 , 0 ),
|
|
"desperation": ( 0 , 1 , 0 ),
|
|
"impatience": ( 0 , 1 , 0 ),
|
|
"pacifism": ( 0 , 1 , 0 ),
|
|
"influence": ( 0 , 1 , 0 ),
|
|
"narcolepsy": ( 0 , 1 , 0 ),
|
|
"exchange": ( 0 , 1 , 0 ),
|
|
"lycanthropy": ( 0 , 1 , 0 ),
|
|
"luck": ( 3 , 1 , 0 ),
|
|
"pestilence": ( 0 , 1 , 0 ),
|
|
"retribution": ( 6 , 1 , 0 ),
|
|
"misdirection": ( 4 , 1 , 0 ),
|
|
"deceit": ( 0 , 1 , 0 ),
|
|
}
|
|
|
|
for totem, (s, cs, ws) in self.TOTEM_CHANCES.items():
|
|
self.TOTEM_CHANCES[totem] = (s, cs, var.TOTEM_CHANCES[totem][2])
|
|
|
|
def startup(self):
|
|
events.add_listener("chk_win", self.chk_win)
|
|
|
|
def teardown(self):
|
|
events.remove_listener("chk_win", self.chk_win)
|
|
|
|
def chk_win(self, evt, cli, var, rolemap, mainroles, lpl, lwolves, lrealwolves):
|
|
lguardians = len(list_players(["guardian angel", "bodyguard"]))
|
|
|
|
if lpl < 1:
|
|
# handled by default win cond checking
|
|
return
|
|
elif not lguardians and lwolves > lpl / 2:
|
|
evt.data["winner"] = "wolves"
|
|
evt.data["message"] = messages["guardian_wolf_win"]
|
|
elif not lguardians and lwolves == lpl / 2:
|
|
evt.data["winner"] = "wolves"
|
|
evt.data["message"] = messages["guardian_wolf_tie_no_guards"]
|
|
elif not lrealwolves and lguardians:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = messages["guardian_villager_win"]
|
|
elif not lrealwolves and not lguardians:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = messages["guardian_lose_no_guards"]
|
|
elif lwolves == lguardians and lpl - lwolves - lguardians == 0:
|
|
evt.data["winner"] = "wolves"
|
|
evt.data["message"] = messages["guardian_lose_with_guards"]
|
|
else:
|
|
evt.data["winner"] = None
|
|
|
|
@game_mode("charming", minp = 6, maxp = 24, likelihood = 4)
|
|
class CharmingMode(GameMode):
|
|
"""Charmed players must band together to find the piper in this game mode."""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.ROLE_INDEX = ( 6 , 8 , 10 , 11 , 12 , 14 , 16 , 18 , 19 , 22 , 24 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({# village roles
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"harlot" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"shaman" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"detective" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"bodyguard" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 2 , 2 , 2 , 2 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 3 , 3 ),
|
|
"traitor" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"werekitten" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"warlock" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"sorcerer" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"piper" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"vengeful ghost" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"gunner" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 ),
|
|
"sharpshooter" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 ),
|
|
"mayor" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
@game_mode("sleepy", minp=10, maxp=24, likelihood=5)
|
|
class SleepyMode(GameMode):
|
|
"""A small village has become the playing ground for all sorts of supernatural beings."""
|
|
def __init__(self, arg=""):
|
|
super().__init__(arg)
|
|
self.ROLE_INDEX = ( 10 , 12 , 15 , 18 , 21 )
|
|
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
|
self.ROLE_GUIDE.update({
|
|
# village roles
|
|
"seer" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"priest" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"harlot" : ( 0 , 0 , 0 , 1 , 1 ),
|
|
"detective" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
"vigilante" : ( 0 , 1 , 1 , 1 , 1 ),
|
|
"village drunk" : ( 0 , 0 , 0 , 0 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 2 , 3 , 4 , 5 ),
|
|
"werecrow" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"traitor" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"cultist" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"dullahan" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"vengeful ghost" : ( 0 , 0 , 1 , 1 , 1 ),
|
|
"monster" : ( 0 , 0 , 0 , 1 , 2 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"blessed villager" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"prophet" : ( 1 , 1 , 1 , 1 , 1 ),
|
|
"gunner" : ( 0 , 0 , 0 , 0 , 1 ),
|
|
})
|
|
# this ensures that priest will always receive the blessed villager and prophet templates
|
|
# prophet is normally a role by itself, but we're turning it into a template for this mode
|
|
self.TEMPLATE_RESTRICTIONS = var.TEMPLATE_RESTRICTIONS.copy()
|
|
self.TEMPLATE_RESTRICTIONS["cursed villager"] |= {"priest"}
|
|
self.TEMPLATE_RESTRICTIONS["blessed villager"] = frozenset(self.ROLE_GUIDE.keys()) - {"priest", "blessed villager", "prophet"}
|
|
self.TEMPLATE_RESTRICTIONS["prophet"] = frozenset(self.ROLE_GUIDE.keys()) - {"priest", "blessed villager", "prophet"}
|
|
# this ensures that village drunk will always receive the gunner template
|
|
self.TEMPLATE_RESTRICTIONS["gunner"] = frozenset(self.ROLE_GUIDE.keys()) - {"village drunk", "cursed villager", "gunner"}
|
|
# disable wolfchat
|
|
#self.RESTRICT_WOLFCHAT = 0x0f
|
|
|
|
self.having_nightmare = None
|
|
|
|
def startup(self):
|
|
from src import decorators
|
|
events.add_listener("dullahan_targets", self.dullahan_targets)
|
|
events.add_listener("transition_night_begin", self.setup_nightmares)
|
|
events.add_listener("chk_nightdone", self.prolong_night)
|
|
events.add_listener("transition_day_begin", self.nightmare_kill)
|
|
events.add_listener("del_player", self.happy_fun_times)
|
|
events.add_listener("rename_player", self.rename_player)
|
|
self.north_cmd = decorators.cmd("north", "n", chan=False, pm=True, playing=True, phases=("night",))(self.north)
|
|
self.east_cmd = decorators.cmd("east", "e", chan=False, pm=True, playing=True, phases=("night",))(self.east)
|
|
self.south_cmd = decorators.cmd("south", "s", chan=False, pm=True, playing=True, phases=("night",))(self.south)
|
|
self.west_cmd = decorators.cmd("west", "w", chan=False, pm=True, playing=True, phases=("night",))(self.west)
|
|
|
|
def teardown(self):
|
|
from src import decorators
|
|
events.remove_listener("dullahan_targets", self.dullahan_targets)
|
|
events.remove_listener("transition_night_begin", self.setup_nightmares)
|
|
events.remove_listener("chk_nightdone", self.prolong_night)
|
|
events.remove_listener("transition_day_begin", self.nightmare_kill)
|
|
events.remove_listener("del_player", self.happy_fun_times)
|
|
events.remove_listener("rename_player", self.rename_player)
|
|
def remove_command(name, command):
|
|
if len(decorators.COMMANDS[name]) > 1:
|
|
decorators.COMMANDS[name].remove(command)
|
|
else:
|
|
del decorators.COMMANDS[name]
|
|
remove_command("north", self.north_cmd)
|
|
remove_command("n", self.north_cmd)
|
|
remove_command("east", self.east_cmd)
|
|
remove_command("e", self.east_cmd)
|
|
remove_command("south", self.south_cmd)
|
|
remove_command("s", self.south_cmd)
|
|
remove_command("west", self.west_cmd)
|
|
remove_command("w", self.west_cmd)
|
|
|
|
def dullahan_targets(self, evt, cli, var, dullahans, max_targets):
|
|
for dull in dullahans:
|
|
evt.data["targets"][dull] = {users._get(x) for x in var.ROLES["priest"]}
|
|
|
|
def setup_nightmares(self, evt, cli, var):
|
|
if random.random() < 1/5:
|
|
self.having_nightmare = True
|
|
with var.WARNING_LOCK:
|
|
t = threading.Timer(60, self.do_nightmare, (cli, var, random.choice(list_players()), var.NIGHT_COUNT))
|
|
t.daemon = True
|
|
t.start()
|
|
else:
|
|
self.having_nightmare = None
|
|
|
|
def rename_player(self, evt, cli, var, prefix, nick):
|
|
if self.having_nightmare == prefix:
|
|
self.having_nightmare = nick
|
|
|
|
@handle_error
|
|
def do_nightmare(self, cli, var, target, night):
|
|
if var.PHASE != "night" or var.NIGHT_COUNT != night:
|
|
return
|
|
if target not in list_players():
|
|
return
|
|
self.having_nightmare = target
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_begin"])
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_navigate"])
|
|
self.correct = [None, None, None]
|
|
self.fake1 = [None, None, None]
|
|
self.fake2 = [None, None, None]
|
|
directions = ["n", "e", "s", "w"]
|
|
self.step = 0
|
|
self.prev_direction = None
|
|
opposite = {"n": "s", "e": "w", "s": "n", "w": "e"}
|
|
for i in range(3):
|
|
corrdir = directions[:]
|
|
f1dir = directions[:]
|
|
f2dir = directions[:]
|
|
if i > 0:
|
|
corrdir.remove(opposite[self.correct[i-1]])
|
|
f1dir.remove(opposite[self.fake1[i-1]])
|
|
f2dir.remove(opposite[self.fake2[i-1]])
|
|
else:
|
|
corrdir.remove("s")
|
|
f1dir.remove("s")
|
|
f2dir.remove("s")
|
|
self.correct[i] = random.choice(corrdir)
|
|
self.fake1[i] = random.choice(f1dir)
|
|
self.fake2[i] = random.choice(f2dir)
|
|
self.prev_direction = "n"
|
|
self.start_direction = "n"
|
|
self.on_path = set()
|
|
self.nightmare_step(cli)
|
|
|
|
def nightmare_step(self, cli):
|
|
if self.prev_direction == "n":
|
|
directions = "north, east, and west"
|
|
elif self.prev_direction == "e":
|
|
directions = "north, east, and south"
|
|
elif self.prev_direction == "s":
|
|
directions = "east, south, and west"
|
|
elif self.prev_direction == "w":
|
|
directions = "north, south, and west"
|
|
|
|
if self.step == 0:
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_0"].format(directions))
|
|
elif self.step == 1:
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_1"].format(directions))
|
|
elif self.step == 2:
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_2"].format(directions))
|
|
elif self.step == 3:
|
|
if "correct" in self.on_path:
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_wake"])
|
|
self.having_nightmare = None
|
|
chk_nightdone(cli)
|
|
elif "fake1" in self.on_path:
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_fake_1"])
|
|
self.step = 0
|
|
self.on_path = set()
|
|
self.prev_direction = self.start_direction
|
|
self.nightmare_step(cli)
|
|
elif "fake2" in self.on_path:
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_fake_2"])
|
|
self.step = 0
|
|
self.on_path = set()
|
|
self.prev_direction = self.start_direction
|
|
self.nightmare_step(cli)
|
|
|
|
def north(self, cli, nick, chan, rest):
|
|
if nick != self.having_nightmare:
|
|
return
|
|
if self.prev_direction == "s":
|
|
pm(cli, nick, messages["sleepy_nightmare_invalid_direction"])
|
|
return
|
|
advance = False
|
|
if ("correct" in self.on_path or self.step == 0) and self.correct[self.step] == "n":
|
|
self.on_path.add("correct")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("correct")
|
|
if ("fake1" in self.on_path or self.step == 0) and self.fake1[self.step] == "n":
|
|
self.on_path.add("fake1")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("fake1")
|
|
if ("fake2" in self.on_path or self.step == 0) and self.fake2[self.step] == "n":
|
|
self.on_path.add("fake2")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("fake2")
|
|
if advance:
|
|
self.step += 1
|
|
self.prev_direction = "n"
|
|
else:
|
|
self.step = 0
|
|
self.on_path = set()
|
|
self.prev_direction = self.start_direction
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_restart"])
|
|
self.nightmare_step(cli)
|
|
|
|
def east(self, cli, nick, chan, rest):
|
|
if nick != self.having_nightmare:
|
|
return
|
|
if self.prev_direction == "w":
|
|
pm(cli, nick, messages["sleepy_nightmare_invalid_direction"])
|
|
return
|
|
advance = False
|
|
if ("correct" in self.on_path or self.step == 0) and self.correct[self.step] == "e":
|
|
self.on_path.add("correct")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("correct")
|
|
if ("fake1" in self.on_path or self.step == 0) and self.fake1[self.step] == "e":
|
|
self.on_path.add("fake1")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("fake1")
|
|
if ("fake2" in self.on_path or self.step == 0) and self.fake2[self.step] == "e":
|
|
self.on_path.add("fake2")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("fake2")
|
|
if advance:
|
|
self.step += 1
|
|
self.prev_direction = "e"
|
|
else:
|
|
self.step = 0
|
|
self.on_path = set()
|
|
self.prev_direction = self.start_direction
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_restart"])
|
|
self.nightmare_step(cli)
|
|
|
|
def south(self, cli, nick, chan, rest):
|
|
if nick != self.having_nightmare:
|
|
return
|
|
if self.prev_direction == "n":
|
|
pm(cli, nick, messages["sleepy_nightmare_invalid_direction"])
|
|
return
|
|
advance = False
|
|
if ("correct" in self.on_path or self.step == 0) and self.correct[self.step] == "s":
|
|
self.on_path.add("correct")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("correct")
|
|
if ("fake1" in self.on_path or self.step == 0) and self.fake1[self.step] == "s":
|
|
self.on_path.add("fake1")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("fake1")
|
|
if ("fake2" in self.on_path or self.step == 0) and self.fake2[self.step] == "s":
|
|
self.on_path.add("fake2")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("fake2")
|
|
if advance:
|
|
self.step += 1
|
|
self.prev_direction = "s"
|
|
else:
|
|
self.step = 0
|
|
self.on_path = set()
|
|
self.prev_direction = self.start_direction
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_restart"])
|
|
self.nightmare_step(cli)
|
|
|
|
def west(self, cli, nick, chan, rest):
|
|
if nick != self.having_nightmare:
|
|
return
|
|
if self.prev_direction == "e":
|
|
pm(cli, nick, messages["sleepy_nightmare_invalid_direction"])
|
|
return
|
|
advance = False
|
|
if ("correct" in self.on_path or self.step == 0) and self.correct[self.step] == "w":
|
|
self.on_path.add("correct")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("correct")
|
|
if ("fake1" in self.on_path or self.step == 0) and self.fake1[self.step] == "w":
|
|
self.on_path.add("fake1")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("fake1")
|
|
if ("fake2" in self.on_path or self.step == 0) and self.fake2[self.step] == "w":
|
|
self.on_path.add("fake2")
|
|
advance = True
|
|
else:
|
|
self.on_path.discard("fake2")
|
|
if advance:
|
|
self.step += 1
|
|
self.prev_direction = "w"
|
|
else:
|
|
self.step = 0
|
|
self.on_path = set()
|
|
self.prev_direction = self.start_direction
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_restart"])
|
|
self.nightmare_step(cli)
|
|
|
|
def prolong_night(self, evt, cli, var):
|
|
if self.having_nightmare is not None:
|
|
evt.data["actedcount"] = -1
|
|
|
|
def nightmare_kill(self, evt, cli, var):
|
|
# if True, it means night ended before 1 minute
|
|
if self.having_nightmare is not None and self.having_nightmare is not True and self.having_nightmare in list_players():
|
|
var.DYING.add(self.having_nightmare)
|
|
pm(cli, self.having_nightmare, messages["sleepy_nightmare_death"])
|
|
|
|
def happy_fun_times(self, evt, cli, var, nick, mainrole, allroles, death_triggers):
|
|
if death_triggers:
|
|
if mainrole == "priest":
|
|
pl = evt.data["pl"]
|
|
turn_chance = 3/4
|
|
seers = [p for p in var.ROLES["seer"] if p in pl and random.random() < turn_chance]
|
|
harlots = [p for p in var.ROLES["harlot"] if p in pl and random.random() < turn_chance]
|
|
cultists = [p for p in var.ROLES["cultist"] if p in pl and random.random() < turn_chance]
|
|
cli.msg(botconfig.CHANNEL, messages["sleepy_priest_death"])
|
|
for seer in seers:
|
|
change_role(users._get(seer), "seer", "doomsayer") # FIXME
|
|
pm(cli, seer, messages["sleepy_doomsayer_turn"])
|
|
relay_wolfchat_command(cli, seer, messages["sleepy_doomsayer_wolfchat"].format(seer), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
|
|
for harlot in harlots:
|
|
change_role(users._get(harlot), "harlot", "succubus") # FIXME
|
|
pm(cli, harlot, messages["sleepy_succubus_turn"])
|
|
for cultist in cultists:
|
|
change_role(users._get(cultist), "cultist", "demoniac") # FIXME
|
|
pm(cli, cultist, messages["sleepy_demoniac_turn"])
|
|
# NOTE: chk_win is called by del_player, don't need to call it here even though this has a chance of ending game
|
|
|
|
@game_mode("maelstrom", minp = 8, maxp = 24, likelihood = 0)
|
|
class MaelstromMode(GameMode):
|
|
"""Some people just want to watch the world burn."""
|
|
def __init__(self, arg=""):
|
|
self.ROLE_REVEAL = "on"
|
|
self.STATS_TYPE = "disabled"
|
|
super().__init__(arg)
|
|
self.LOVER_WINS_WITH_FOOL = True
|
|
self.MAD_SCIENTIST_SKIPS_DEAD_PLAYERS = 0 # always make it happen
|
|
self.ALWAYS_PM_ROLE = True
|
|
# clone is pointless in this mode
|
|
# dullahan doesn't really work in this mode either, if enabling anyway special logic to determine kill list
|
|
# needs to be added above for when dulls are added during the game
|
|
# matchmaker is conditionally enabled during night 1 only
|
|
# monster and demoniac are nearly impossible to counter and don't add any interesting gameplay
|
|
# succubus keeps around entranced people, who are then unable to win even if there are later no succubi (not very fun)
|
|
self.roles = list(var.ROLE_GUIDE.keys() - var.TEMPLATE_RESTRICTIONS.keys() - {"amnesiac", "clone", "dullahan", "matchmaker", "monster", "demoniac", "wild child", "succubus"})
|
|
|
|
self.DEAD_ACCOUNTS = set()
|
|
self.DEAD_HOSTS = set()
|
|
|
|
def startup(self):
|
|
events.add_listener("role_attribution", self.role_attribution)
|
|
events.add_listener("transition_night_begin", self.transition_night_begin)
|
|
events.add_listener("del_player", self.on_del_player)
|
|
events.add_listener("join", self.on_join)
|
|
|
|
def teardown(self):
|
|
events.remove_listener("role_attribution", self.role_attribution)
|
|
events.remove_listener("transition_night_begin", self.transition_night_begin)
|
|
events.remove_listener("del_player", self.on_del_player)
|
|
events.remove_listener("join", self.on_join)
|
|
|
|
def on_del_player(self, evt, cli, var, nick, mainrole, allroles, death_triggers):
|
|
if is_fake_nick(nick):
|
|
return
|
|
|
|
if not var.DISABLE_ACCOUNTS:
|
|
self.DEAD_ACCOUNTS.add(irc_lower(var.USERS[nick]["account"]))
|
|
|
|
if not var.ACCOUNTS_ONLY:
|
|
self.DEAD_HOSTS.add(var.USERS[nick]["host"].lower())
|
|
|
|
def on_join(self, evt, var, wrapper, message, forced=False):
|
|
if var.PHASE != "day" or (wrapper.public and wrapper.target is not channels.Main):
|
|
return
|
|
temp = wrapper.source.lower()
|
|
if (wrapper.source in var.ALL_PLAYERS or
|
|
temp.account in self.DEAD_ACCOUNTS or
|
|
temp.host in self.DEAD_HOSTS):
|
|
wrapper.pm(messages["maelstrom_dead"])
|
|
return
|
|
if not forced and evt.data["join_player"](var, type(wrapper)(wrapper.source, channels.Main), sanity=False):
|
|
self._on_join(var, wrapper)
|
|
evt.prevent_default = True
|
|
elif forced:
|
|
# in fjoin, handle this differently
|
|
jp = evt.data["join_player"]
|
|
evt.data["join_player"] = lambda var, wrapper, who=None, forced=False: jp(var, wrapper, who=who, forced=forced, sanity=False) and self._on_join(var, wrapper)
|
|
|
|
def _on_join(self, var, wrapper):
|
|
role = random.choice(self.roles)
|
|
rolemap = copy.deepcopy(var.ROLES)
|
|
rolemap[role].add(wrapper.source.nick) # FIXME: add user instead of nick (can only be done once var.ROLES itself uses users)
|
|
mainroles = copy.deepcopy(var.MAIN_ROLES)
|
|
mainroles[wrapper.source] = role
|
|
|
|
if self.chk_win_conditions(wrapper.client, rolemap, mainroles, end_game=False):
|
|
return self._on_join(var, wrapper)
|
|
|
|
var.ROLES[role].add(wrapper.source.nick) # FIXME: add user instead of nick
|
|
var.ORIGINAL_ROLES[role].add(wrapper.source.nick)
|
|
var.FINAL_ROLES[wrapper.source.nick] = role
|
|
var.MAIN_ROLES[wrapper.source] = role
|
|
var.LAST_SAID_TIME[wrapper.source.nick] = datetime.now()
|
|
if wrapper.source.nick in var.USERS:
|
|
var.PLAYERS[wrapper.source.nick] = var.USERS[wrapper.source.nick]
|
|
|
|
if role == "doctor":
|
|
lpl = len(list_players())
|
|
var.DOCTORS[wrapper.source.nick] = math.ceil(var.DOCTOR_IMMUNIZATION_MULTIPLIER * lpl)
|
|
# let them know their role
|
|
from src.decorators import COMMANDS
|
|
COMMANDS["myrole"][0].caller(wrapper.source.client, wrapper.source.nick, wrapper.target.name, "") # FIXME: New/old API
|
|
# if they're a wolfchat role, alert the other wolves
|
|
if role in var.WOLFCHAT_ROLES:
|
|
relay_wolfchat_command(wrapper.source.client, wrapper.source.nick, messages["wolfchat_new_member"].format(wrapper.source.nick, role), var.WOLFCHAT_ROLES, is_wolf_command=True, is_kill_command=True)
|
|
# TODO: make this part of !myrole instead, no reason we can't give out wofllist in that
|
|
wolves = list_players(var.WOLFCHAT_ROLES)
|
|
pl = list_players()
|
|
random.shuffle(pl)
|
|
pl.remove(wrapper.source.nick)
|
|
for i, player in enumerate(pl):
|
|
prole = get_role(player)
|
|
if prole in var.WOLFCHAT_ROLES:
|
|
cursed = ""
|
|
if player in var.ROLES["cursed villager"]:
|
|
cursed = "cursed "
|
|
pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole)
|
|
elif player in var.ROLES["cursed villager"]:
|
|
pl[i] = player + " (cursed)"
|
|
wrapper.pm("Players: " + ", ".join(pl))
|
|
|
|
def role_attribution(self, evt, cli, var, chk_win_conditions, villagers):
|
|
self.chk_win_conditions = chk_win_conditions
|
|
evt.data["addroles"] = self._role_attribution(cli, var, villagers, True)
|
|
|
|
def transition_night_begin(self, evt, cli, var):
|
|
# don't do this n1
|
|
if var.FIRST_NIGHT:
|
|
return
|
|
villagers = list_players()
|
|
lpl = len(villagers)
|
|
addroles = self._role_attribution(cli, var, villagers, False)
|
|
|
|
# shameless copy/paste of regular role attribution
|
|
for role, count in addroles.items():
|
|
selected = random.sample(villagers, count)
|
|
var.ROLES[role] = set(selected)
|
|
for x in selected:
|
|
villagers.remove(x)
|
|
|
|
# Handle roles that need extra help
|
|
for doctor in var.ROLES["doctor"]:
|
|
var.DOCTORS[doctor] = math.ceil(var.DOCTOR_IMMUNIZATION_MULTIPLIER * lpl)
|
|
|
|
# Clear totem tracking; this would let someone that gets shaman twice in a row to give
|
|
# out a totem to the same person twice in a row, but oh well
|
|
var.LASTGIVEN = {}
|
|
|
|
# for end of game stats to show what everyone ended up as on game end
|
|
for role, pl in var.ROLES.items():
|
|
if role in var.TEMPLATE_RESTRICTIONS.keys():
|
|
continue
|
|
for p in pl:
|
|
# discard them from all non-template roles, we don't have a reliable
|
|
# means of tracking their previous role (due to traitor turning, exchange
|
|
# totem, etc.), so we need to iterate through everything.
|
|
for r in var.ORIGINAL_ROLES.keys():
|
|
if r in var.TEMPLATE_RESTRICTIONS.keys():
|
|
continue
|
|
var.ORIGINAL_ROLES[r].discard(p)
|
|
var.ORIGINAL_ROLES[role].add(p)
|
|
var.FINAL_ROLES[p] = role
|
|
var.MAIN_ROLES[users._get(p)] = role # FIXME
|
|
|
|
def _role_attribution(self, cli, var, villagers, do_templates):
|
|
lpl = len(villagers) - 1
|
|
addroles = {}
|
|
for role in var.ROLE_GUIDE:
|
|
if role in var.TEMPLATE_RESTRICTIONS.keys() and not do_templates:
|
|
continue
|
|
addroles[role] = 0
|
|
|
|
wolves = var.WOLF_ROLES - {"wolf cub"}
|
|
addroles[random.choice(list(wolves))] += 1 # make sure there's at least one wolf role
|
|
roles = self.roles[:]
|
|
if do_templates:
|
|
# mm only works night 1, do_templates is also only true n1
|
|
roles.append("matchmaker")
|
|
while lpl:
|
|
addroles[random.choice(roles)] += 1
|
|
lpl -= 1
|
|
|
|
if do_templates:
|
|
addroles["gunner"] = random.randrange(4)
|
|
addroles["sharpshooter"] = random.randrange(addroles["gunner"] + 1)
|
|
addroles["assassin"] = random.randrange(3)
|
|
addroles["cursed villager"] = random.randrange(3)
|
|
addroles["mayor"] = random.randrange(2)
|
|
if random.randrange(100) == 0 and addroles.get("villager", 0) > 0:
|
|
addroles["blessed villager"] = 1
|
|
|
|
rolemap = defaultdict(set)
|
|
mainroles = {}
|
|
i = 0
|
|
for role, count in addroles.items():
|
|
if count > 0:
|
|
for j in range(count):
|
|
u = users.FakeUser.from_nick(str(i + j))
|
|
rolemap[role].add(u.nick)
|
|
if role not in var.TEMPLATE_RESTRICTIONS:
|
|
mainroles[u] = role
|
|
i += count
|
|
|
|
if self.chk_win_conditions(cli, rolemap, mainroles, end_game=False):
|
|
return self._role_attribution(cli, var, villagers, do_templates)
|
|
|
|
return addroles
|
|
|
|
# vim: set sw=4 expandtab:
|