1045 lines
59 KiB
Python
1045 lines
59 KiB
Python
from collections import OrderedDict
|
|
|
|
import random
|
|
import math
|
|
import threading
|
|
|
|
import src.settings as var
|
|
from src.utilities import *
|
|
import botconfig
|
|
|
|
from src import events, utilities
|
|
|
|
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 = var.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 var.InvalidModeException("Invalid syntax for mode arguments. arg={0}".format(arg))
|
|
|
|
key, val = change
|
|
if key in ("role reveal", "reveal roles"):
|
|
if val not in ("on", "off", "team"):
|
|
raise var.InvalidModeException(("Did not recognize value \u0002{0}\u0002 for role reveal. "+
|
|
"Allowed values: on, off, team").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 var.InvalidModeException(("Did not recognize value \u0002{0}\u0002 for stats type. "+
|
|
"Allowed values: default, accurate, team, disabled").format(val))
|
|
self.STATS_TYPE = val
|
|
elif key == "abstain":
|
|
if val not in ("enabled", "restricted", "disabled"):
|
|
raise var.InvalidModeException(("Did not recognize value \u0002{0}\u0002 for abstain. "+
|
|
"Allowed values: enabled, restricted, disabled").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
|
|
|
|
def startup(self):
|
|
pass
|
|
|
|
def teardown(self):
|
|
pass
|
|
|
|
# Here so any game mode can use it
|
|
def lovers_chk_win(self, evt, var, 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"] = ("Game over! The remaining villagers through their inseparable "
|
|
"love for each other have agreed to stop all of this senseless "
|
|
"violence and coexist in peace forever more. All remaining players win.")
|
|
|
|
@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 var.InvalidModeException("Invalid syntax for mode roles. arg={0}".format(arg))
|
|
role, num = change
|
|
try:
|
|
if role.lower() in var.DISABLED_ROLES:
|
|
raise var.InvalidModeException("The role \u0002{0}\u0002 has been 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"):
|
|
# handled in parent constructor
|
|
pass
|
|
else:
|
|
raise var.InvalidModeException(("The role \u0002{0}\u0002 "+
|
|
"is not valid.").format(role))
|
|
except ValueError:
|
|
raise var.InvalidModeException("A bad value was used in mode roles.")
|
|
|
|
@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("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 = 8)
|
|
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 ),
|
|
# templates
|
|
"cursed villager" : ( 0 , 1 , 1 , 1 , 1 ),
|
|
"mayor" : ( 0 , 0 , 0 , 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, var, lpl, lwolves, lrealwolves):
|
|
lsafes = len(var.list_players(["oracle", "seer", "guardian angel", "shaman", "hunter", "villager"]))
|
|
lcultists = len(var.list_players(["cultist"]))
|
|
evt.stop_processing = True
|
|
|
|
if lrealwolves == 0 and lsafes == 0:
|
|
evt.data["winner"] = "none"
|
|
evt.data["message"] = ("Game over! All the villagers are dead, but the cult needed to sacrifice " +
|
|
"the wolves to accomplish that. The cult disperses shortly thereafter, " +
|
|
"and nobody wins.")
|
|
elif lrealwolves == 0:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = ("Game over! All the wolves are dead! The villagers " +
|
|
"round up the remaining cultists, hang them, and live " +
|
|
"happily ever after.")
|
|
elif lsafes == 0:
|
|
evt.data["winner"] = "wolves"
|
|
evt.data["message"] = ("Game over! All the villagers are dead! The cultists rejoice " +
|
|
"with their wolf buddies and start plotting to take over the " +
|
|
"next village.")
|
|
elif lcultists == 0:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = ("Game over! All the cultists are dead! The now-exposed wolves " +
|
|
"are captured and killed by the remaining villagers. A BBQ party " +
|
|
"commences shortly thereafter.")
|
|
elif lsafes >= lpl / 2:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = ("Game over! There are {0} villagers {1} cultists. They " +
|
|
"manage to regain control of the village and dispose of the remaining " +
|
|
"cultists.").format("more" if lsafes > lpl / 2 else "the same number of",
|
|
"than" if lsafes > lpl / 2 else "as")
|
|
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.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 ),
|
|
})
|
|
|
|
@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 ),
|
|
})
|
|
|
|
@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 ),
|
|
"guardian angel" : ( 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 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ),
|
|
"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 , 2 , 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 , 1 , 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.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"
|
|
super().__init__(arg)
|
|
self.LOVER_WINS_WITH_FOOL = True
|
|
self.MAD_SCIENTIST_SKIPS_DEAD_PLAYERS = 0 # always make it happen
|
|
self.ALPHA_WOLF_NIGHTS = 2
|
|
self.TEMPLATE_RESTRICTIONS = {template: frozenset() for template in var.TEMPLATE_RESTRICTIONS}
|
|
|
|
self.TOTEM_CHANCES = { # shaman , crazed
|
|
"death": ( 8 , 1 ),
|
|
"protection": ( 6 , 1 ),
|
|
"silence": ( 4 , 1 ),
|
|
"revealing": ( 2 , 1 ),
|
|
"desperation": ( 4 , 1 ),
|
|
"impatience": ( 7 , 1 ),
|
|
"pacifism": ( 7 , 1 ),
|
|
"influence": ( 7 , 1 ),
|
|
"narcolepsy": ( 4 , 1 ),
|
|
"exchange": ( 1 , 1 ),
|
|
"lycanthropy": ( 1 , 1 ),
|
|
"luck": ( 6 , 1 ),
|
|
"pestilence": ( 3 , 1 ),
|
|
"retribution": ( 5 , 1 ),
|
|
"misdirection": ( 6 , 1 ),
|
|
}
|
|
|
|
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, chk_win_conditions, var, 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", "succubus", "doomsayer", "demoniac"})
|
|
while lpl:
|
|
addroles[random.choice(roles)] += 1
|
|
lpl -= 1
|
|
|
|
addroles["gunner"] = random.randrange(int(len(villagers) ** 1.2 / 4))
|
|
addroles["assassin"] = random.randrange(int(len(villagers) ** 1.2 / 8))
|
|
|
|
lpl = len(villagers)
|
|
lwolves = sum(addroles[r] for r in var.WOLFCHAT_ROLES)
|
|
lcubs = addroles["wolf cub"]
|
|
lrealwolves = sum(addroles[r] for r in var.WOLF_ROLES - {"wolf cub"})
|
|
lmonsters = addroles["monster"]
|
|
ldemoniacs = addroles["demoniac"]
|
|
ltraitors = addroles["traitor"]
|
|
lpipers = addroles["piper"]
|
|
lsuccubi = addroles["succubus"]
|
|
|
|
if chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, 0, cli, end_game=False):
|
|
return self.role_attribution(evt, cli, chk_win_conditions, var, 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
|
|
self.TOTEM_CHANCES = { "death": ( 4 , 1 ),
|
|
"protection": ( 8 , 1 ),
|
|
"silence": ( 2 , 1 ),
|
|
"revealing": ( 0 , 1 ),
|
|
"desperation": ( 1 , 1 ),
|
|
"impatience": ( 0 , 1 ),
|
|
"pacifism": ( 0 , 1 ),
|
|
"influence": ( 0 , 1 ),
|
|
"narcolepsy": ( 0 , 1 ),
|
|
"exchange": ( 0 , 1 ),
|
|
"lycanthropy": ( 0 , 1 ),
|
|
"luck": ( 0 , 1 ),
|
|
"pestilence": ( 1 , 1 ),
|
|
"retribution": ( 4 , 1 ),
|
|
"misdirection": ( 0 , 1 ),
|
|
}
|
|
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 , 0 , 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 ),
|
|
"lycan" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ),
|
|
"assassin" : ( 0 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ),
|
|
"gunner" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"bureaucrat" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"mayor" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
@game_mode("alpha", minp = 7, 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 = ( 7 , 8 , 10 , 11 , 12 , 14 , 15 , 17 , 18 , 20 , 21 , 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 ),
|
|
"matchmaker" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"village drunk" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"guardian angel" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"doctor" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"harlot" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"augur" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 3 , 3 , 4 , 5 ),
|
|
"alpha wolf" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"traitor" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"werecrow" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"lycan" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 ),
|
|
"clone" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 3 , 3 , 3 , 4 ),
|
|
})
|
|
|
|
# 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
|
|
"death": ( 4 , 1 ),
|
|
"protection": ( 8 , 1 ),
|
|
"silence": ( 2 , 1 ),
|
|
"revealing": ( 0 , 1 ),
|
|
"desperation": ( 0 , 1 ),
|
|
"impatience": ( 0 , 1 ),
|
|
"pacifism": ( 0 , 1 ),
|
|
"influence": ( 0 , 1 ),
|
|
"narcolepsy": ( 0 , 1 ),
|
|
"exchange": ( 0 , 1 ),
|
|
"lycanthropy": ( 0 , 1 ),
|
|
"luck": ( 3 , 1 ),
|
|
"pestilence": ( 0 , 1 ),
|
|
"retribution": ( 6 , 1 ),
|
|
"misdirection": ( 4 , 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, var, lpl, lwolves, lrealwolves):
|
|
lguardians = len(var.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"] = ("Game over! There are more wolves than uninjured villagers. With the ancestral " +
|
|
"guardians dead, the wolves overpower the defenseless villagers and win.")
|
|
elif not lguardians and lwolves == lpl / 2:
|
|
evt.data["winner"] = "wolves"
|
|
evt.data["message"] = ("Game over! There are the same number of wolves as uninjured villagers. With the ancestral " +
|
|
"guardians dead, the wolves overpower the defenseless villagers and win.")
|
|
elif not lrealwolves and lguardians:
|
|
evt.data["winner"] = "villagers"
|
|
evt.data["message"] = ("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.")
|
|
elif not lrealwolves and not lguardians:
|
|
evt.data["winner"] = "none"
|
|
evt.data["message"] = ("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. Nobody wins.")
|
|
elif lwolves == lguardians and lpl - lwolves - lguardians == 0:
|
|
evt.data["winner"] = "none"
|
|
evt.data["message"] = ("Game over! The guardians, angered by the loss of everyone they were meant to guard, " +
|
|
"engage the wolves in battle and mutually assured destruction. After the dust settles " +
|
|
"the village is completely dead, and nobody wins.")
|
|
else:
|
|
evt.data["winner"] = None
|
|
|
|
@game_mode("charming", minp = 5, 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 = ( 5 , 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 , 1 ),
|
|
"harlot" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"shaman" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
|
|
"detective" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"bodyguard" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 2 , 2 , 2 , 2 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 3 , 3 ),
|
|
"traitor" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"werekitten" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"warlock" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"sorcerer" : ( 0 , 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 , 1 ),
|
|
"vengeful ghost" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
# templates
|
|
"cursed villager" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"gunner" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 ),
|
|
"sharpshooter" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 ),
|
|
"mayor" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
})
|
|
|
|
@game_mode("sleepy", minp=8, maxp=24, likelihood=0)
|
|
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 = ( 8 , 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 , 1 ),
|
|
"priest" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
"harlot" : ( 0 , 0 , 0 , 0 , 1 , 1 ),
|
|
"detective" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"vigilante" : ( 0 , 0 , 1 , 1 , 1 , 1 ),
|
|
"village drunk" : ( 0 , 0 , 0 , 0 , 0 , 1 ),
|
|
# wolf roles
|
|
"wolf" : ( 1 , 1 , 2 , 3 , 4 , 5 ),
|
|
"werecrow" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
"traitor" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"cultist" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
# neutral roles
|
|
"dullahan" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"vengeful ghost" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
|
"monster" : ( 0 , 0 , 0 , 0 , 1 , 2 ),
|
|
# templates
|
|
"cursed villager" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
|
"blessed villager" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
"prophet" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
|
"gunner" : ( 0 , 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)
|
|
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)
|
|
decorators.COMMANDS["north"].remove(self.north_cmd)
|
|
decorators.COMMANDS["n"].remove(self.north_cmd)
|
|
decorators.COMMANDS["east"].remove(self.east_cmd)
|
|
decorators.COMMANDS["e"].remove(self.east_cmd)
|
|
decorators.COMMANDS["south"].remove(self.south_cmd)
|
|
decorators.COMMANDS["s"].remove(self.south_cmd)
|
|
decorators.COMMANDS["west"].remove(self.west_cmd)
|
|
decorators.COMMANDS["w"].remove(self.west_cmd)
|
|
|
|
def dullahan_targets(self, evt, cli, var, dullahans, max_targets):
|
|
for dull in dullahans:
|
|
evt.data["targets"][dull] = set(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, random.choice(var.list_players()), var.NIGHT_COUNT))
|
|
t.daemon = True
|
|
t.start()
|
|
else:
|
|
self.having_nightmare = None
|
|
|
|
def do_nightmare(self, cli, target, night):
|
|
if var.PHASE != "night" or var.NIGHT_COUNT != night:
|
|
return
|
|
self.having_nightmare = target
|
|
pm(cli, self.having_nightmare, ("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."))
|
|
pm(cli, self.having_nightmare, 'You can pm me "north", "east", "south", and "west", or their abbreviations "n", "e", "s", and "w" to 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:
|
|
coordir.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, ("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 are very twisty, and it's easy to wind up going in " +
|
|
"circles if one is not careful. Directions are {0}.").format(directions))
|
|
elif self.step == 1:
|
|
pm(cli, self.having_nightmare, ("You come across a small creek, the water babbling softly in the night as if nothing is amiss. " +
|
|
"As you approach, a flock of ravens bathing there disperses into all directions. " +
|
|
"Directions are {0}.").format(directions))
|
|
elif self.step == 2:
|
|
pm(cli, self.having_nightmare, ("The treeline starts thinning and you start feeling fresh air for the first time in a while, you " +
|
|
"must be getting close to the edge of the woods! Directions are {0}.").format(directions))
|
|
elif self.step == 3:
|
|
if "correct" in self.on_path:
|
|
pm(cli, self.having_nightmare, ("You break clear of the woods and see a roaring river ahead with a rope bridge going over it. " +
|
|
"You sprint to the bridge with the beast hot on your tail, your adrenaline overcoming your tired " +
|
|
"legs as you push yourself for one final burst. You make it across the bridge, and not a moment too " +
|
|
"soon as the sun starts rising up, causing you to wake from your dream in a cold sweat."))
|
|
self.having_nightmare = None
|
|
utilities.chk_nightdone(cli)
|
|
elif "fake1" in self.on_path:
|
|
pm(cli, self.having_nightmare, ("You break clear of the woods and see a roaring river ahead. However, look as you may you are unable " +
|
|
"to find any means of crossing it. Knowing how expansive the river is, and how fast the beast can chase " +
|
|
"you if it isn't being slowed down by the foliage, you think it's best to look for the correct side of the " +
|
|
"woods again by going back in. Cursing your bad luck, you head back into the woods."))
|
|
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, ("You break clear of the woods only to find an expansive plains ahead of you, with no river in sight. " +
|
|
"You must have found your way out through the wrong side of the woods! Attempting to circle around the " +
|
|
"woods would result in the beast catching you in short order, so you softly curse at your bad luck as you " +
|
|
"head back into the woods to find the correct path."))
|
|
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, "That way lies madness and certain death.")
|
|
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
|
|
if ("fake1" in self.on_path or self.step == 0) and self.fake1[self.step] == "n":
|
|
self.on_path.add("fake1")
|
|
advance = True
|
|
if ("fake2" in self.on_path or self.step == 0) and self.fake2[self.step] == "n":
|
|
self.on_path.add("fake2")
|
|
advance = True
|
|
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, "You find yourself back where you started...")
|
|
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, "That way lies madness and certain death.")
|
|
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
|
|
if ("fake1" in self.on_path or self.step == 0) and self.fake1[self.step] == "e":
|
|
self.on_path.add("fake1")
|
|
advance = True
|
|
if ("fake2" in self.on_path or self.step == 0) and self.fake2[self.step] == "e":
|
|
self.on_path.add("fake2")
|
|
advance = True
|
|
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, "You find yourself back where you started...")
|
|
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, "That way lies madness and certain death.")
|
|
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
|
|
if ("fake1" in self.on_path or self.step == 0) and self.fake1[self.step] == "s":
|
|
self.on_path.add("fake1")
|
|
advance = True
|
|
if ("fake2" in self.on_path or self.step == 0) and self.fake2[self.step] == "s":
|
|
self.on_path.add("fake2")
|
|
advance = True
|
|
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, "You find yourself back where you started...")
|
|
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, "That way lies madness and certain death.")
|
|
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
|
|
if ("fake1" in self.on_path or self.step == 0) and self.fake1[self.step] == "w":
|
|
self.on_path.add("fake1")
|
|
advance = True
|
|
if ("fake2" in self.on_path or self.step == 0) and self.fake2[self.step] == "w":
|
|
self.on_path.add("fake2")
|
|
advance = True
|
|
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, "You find yourself back where you started...")
|
|
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:
|
|
var.DYING.add(self.having_nightmare)
|
|
pm(cli, self.having_nightmare, ("As the sun starts rising, your legs give out, causing the beast to descend upon you and snuff out your life."))
|
|
|
|
def happy_fun_times(self, evt, cli, var, nick, nickrole, nicktpls, forced_death, end_game, death_triggers, killer_role, deadlist, original, ismain, refresh_pl):
|
|
if death_triggers:
|
|
if nickrole == "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]
|
|
total = sum(map(len, (seers, harlots, cultists)))
|
|
if total > 0:
|
|
cli.msg(botconfig.CHANNEL, ("The sky suddenly darkens as a thunderstorm appears from nowhere. The bell on the newly-abandoned church starts ringing " +
|
|
"in sinister tones, managing to perform \u0002{0}\u0002 {1} before the building is struck repeatedly by lightning, " +
|
|
"setting it alight in a raging inferno...").format(total, var.plural("toll", total)))
|
|
for seer in seers:
|
|
var.ROLES["seer"].remove(seer)
|
|
var.ROLES["doomsayer"].add(seer)
|
|
var.FINAL_ROLES[seer] = "doomsayer"
|
|
pm(cli, seer, ("You feel something rushing into you and taking control over your mind and body. It causes you to rapidly " +
|
|
"start transforming into a werewolf, and you realize your vision powers can now be used to inflict malady " +
|
|
"on the unwary. You are now a \u0002doomsayer\u0002."))
|
|
relay_wolfchat_command(cli, seer, "\u0002{0}\u0002 is now a \u0002doomsayer\u0002.".format(seer), var.WOLF_ROLES, is_wolf_command=True, is_kill_command=True)
|
|
for harlot in harlots:
|
|
var.ROLES["harlot"].remove(harlot)
|
|
var.ROLES["succubus"].add(harlot)
|
|
var.FINAL_ROLES[harlot] = "succubus"
|
|
pm(cli, harlot, ("You feel something rushing into you and taking control over your mind and body. You are now a " +
|
|
"\u0002succubus\u0002. Your job is to entrance the village, bringing them all under your absolute " +
|
|
"control."))
|
|
for cultist in cultists:
|
|
var.ROLES["cultist"].remove(cultist)
|
|
var.ROLES["demoniac"].add(cultist)
|
|
var.FINAL_ROLES[cultist] = "demoniac"
|
|
pm(cli, cultist, ("You feel something rushing into you and taking control over your mind and body, showing you your new purpose in life. " +
|
|
"There are far greater evils than the wolves lurking in the shadows, and by sacrificing all of the wolves, you can " +
|
|
"unleash those evils upon the world. You are now a \u0002demoniac\u0002."))
|
|
# NOTE: chk_win is called by del_player, don't need to call it here even though this has a chance of ending game
|
|
|
|
# vim: set expandtab:sw=4:ts=4:
|