Update handling of game modes
This change moves the game modes into their own `src/gamemodes.py`, and also allows setting game modes via `gamemodes.py`.
This commit is contained in:
parent
adec2362e4
commit
c37631e90c
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ __pycache__/
|
||||
|
||||
# Config files
|
||||
botconfig.py
|
||||
/gamemodes.py
|
||||
|
||||
# Database files
|
||||
*.sqlite3
|
||||
|
16
gamemodes.py.example
Normal file
16
gamemodes.py.example
Normal file
@ -0,0 +1,16 @@
|
||||
# To add new game modes, rename this file to 'gamemodes.py' then add them in
|
||||
# Basic frame for the game modes in src/gamemodes.py
|
||||
|
||||
# *** DO NOT TOUCH ANY OF THE CODE BETWEEN THE LINES 4 AND 16 ***
|
||||
|
||||
import src.settings as var
|
||||
|
||||
from src.gamemodes import (
|
||||
|
||||
GameMode,
|
||||
game_mode,
|
||||
reset_roles,
|
||||
|
||||
)
|
||||
|
||||
# Add custom game modes from this point
|
@ -9,7 +9,15 @@ import io
|
||||
import botconfig
|
||||
import src.settings as var
|
||||
|
||||
# Todo: Allow game modes to be set via config
|
||||
# Import the user-defined game modes
|
||||
# These are not required, so failing to import it doesn't matter
|
||||
# The file then imports our game modes
|
||||
# Fall back to importing our game modes if theirs fail
|
||||
|
||||
try:
|
||||
import gamemodes
|
||||
except ImportError:
|
||||
import src.gamemodes
|
||||
|
||||
# Handle launch parameters
|
||||
|
||||
|
656
src/gamemodes.py
Normal file
656
src/gamemodes.py
Normal file
@ -0,0 +1,656 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
import random
|
||||
import math
|
||||
|
||||
import src.settings as var
|
||||
|
||||
from src import events
|
||||
|
||||
def game_mode(name, minp, maxp, likelihood = 0, conceal_roles = False):
|
||||
def decor(c):
|
||||
c.name = name
|
||||
var.GAME_MODES[name] = (c, minp, maxp, likelihood, conceal_roles)
|
||||
return c
|
||||
return decor
|
||||
|
||||
reset_roles = lambda i: OrderedDict([(role, (0,) * len(i)) for role in var.ROLE_GUIDE])
|
||||
|
||||
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
|
||||
|
||||
@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=""):
|
||||
# No extra settings, just an explicit way to revert to default settings
|
||||
super().__init__(arg)
|
||||
|
||||
@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, 1)
|
||||
|
||||
def teardown(self):
|
||||
events.remove_listener("chk_win", self.chk_win, 1)
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
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")
|
||||
elif evt.data["winner"][0] != "@":
|
||||
evt.data["winner"] = None
|
||||
except TypeError: # means that evt.data["winner"] isn't a string or something else subscriptable
|
||||
evt.data["winner"] = None
|
||||
|
||||
|
||||
@game_mode("classic", minp = 7, maxp = 21, likelihood = 4)
|
||||
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],
|
||||
})
|
||||
|
||||
@game_mode("random", minp = 8, maxp = 24, likelihood = 0, conceal_roles = True)
|
||||
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, 1)
|
||||
|
||||
def teardown(self):
|
||||
events.remove_listener("role_attribution", self.role_attribution, 1)
|
||||
|
||||
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"})
|
||||
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"]
|
||||
ltraitors = addroles["traitor"]
|
||||
lpipers = addroles["piper"]
|
||||
|
||||
if chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ltraitors, lpipers, 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 , 15 , 18 , 21 )
|
||||
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
||||
self.ROLE_GUIDE.update({ # village roles
|
||||
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"shaman" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"matchmaker" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"hunter" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
"augur" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
"time lord" : ( 0 , 0 , 0 , 0 , 0 , 1 ),
|
||||
"guardian angel" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
# wolf roles
|
||||
"wolf" : ( 1 , 2 , 2 , 2 , 2 , 2 ),
|
||||
"wolf cub" : ( 0 , 0 , 0 , 0 , 0 , 1 ),
|
||||
"traitor" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"werecrow" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
"hag" : ( 0 , 0 , 1 , 1 , 1 , 1 ),
|
||||
# neutral roles
|
||||
"vengeful ghost" : ( 0 , 1 , 1 , 1 , 2 , 2 ),
|
||||
"amnesiac" : ( 0 , 0 , 1 , 1 , 1 , 1 ),
|
||||
"lycan" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
# templates
|
||||
"cursed villager" : ( 2 , 2 , 2 , 2 , 2 , 2 ),
|
||||
"assassin" : ( 0 , 1 , 2 , 2 , 2 , 2 ),
|
||||
"gunner" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"sharpshooter" : ( 0 , 0 , 0 , 0 , 0 , 1 ),
|
||||
"bureaucrat" : ( 0 , 0 , 1 , 1 , 1 , 1 ),
|
||||
"mayor" : ( 0 , 0 , 0 , 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, 1)
|
||||
|
||||
def teardown(self):
|
||||
events.remove_listener("chk_win", self.chk_win, 1)
|
||||
|
||||
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 ),
|
||||
})
|
652
src/settings.py
652
src/settings.py
@ -1,12 +1,9 @@
|
||||
import fnmatch
|
||||
import math
|
||||
import random
|
||||
import sqlite3
|
||||
import re
|
||||
from collections import defaultdict, OrderedDict
|
||||
|
||||
import botconfig
|
||||
from src import events
|
||||
|
||||
MINIMUM_WAIT = 60
|
||||
EXTRA_WAIT = 30
|
||||
@ -491,658 +488,9 @@ def break_long_message(phrases, joinstr = " "):
|
||||
return joinstr.join(message)
|
||||
|
||||
class InvalidModeException(Exception): pass
|
||||
def game_mode(name, minp, maxp, likelihood = 0, conceal_roles = False):
|
||||
def decor(c):
|
||||
c.name = name
|
||||
GAME_MODES[name] = (c, minp, maxp, likelihood, conceal_roles)
|
||||
return c
|
||||
return decor
|
||||
|
||||
reset_roles = lambda i: OrderedDict([(role, (0,) * len(i)) for role in ROLE_GUIDE])
|
||||
|
||||
# TODO: move this to src/gamemodes.py
|
||||
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("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 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 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 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
|
||||
|
||||
@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 = ROLE_GUIDE.copy()
|
||||
self.ROLE_INDEX = (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("Invalid syntax for mode roles. arg={0}".format(arg))
|
||||
role, num = change
|
||||
try:
|
||||
if role.lower() in DISABLED_ROLES:
|
||||
raise 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(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 InvalidModeException(("The role \u0002{0}\u0002 "+
|
||||
"is not valid.").format(role))
|
||||
except ValueError:
|
||||
raise 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=""):
|
||||
# No extra settings, just an explicit way to revert to default settings
|
||||
super().__init__(arg)
|
||||
|
||||
@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, 1)
|
||||
|
||||
def teardown(self):
|
||||
events.remove_listener("chk_win", self.chk_win, 1)
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
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")
|
||||
elif evt.data["winner"][0] != "@":
|
||||
evt.data["winner"] = None
|
||||
except TypeError: # means that evt.data["winner"] isn't a string or something else subscriptable
|
||||
evt.data["winner"] = None
|
||||
|
||||
|
||||
@game_mode("classic", minp = 7, maxp = 21, likelihood = 4)
|
||||
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],
|
||||
})
|
||||
|
||||
@game_mode("random", minp = 8, maxp = 24, likelihood = 0, conceal_roles = True)
|
||||
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 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, 1)
|
||||
|
||||
def teardown(self):
|
||||
events.remove_listener("role_attribution", self.role_attribution, 1)
|
||||
|
||||
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"})
|
||||
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"]
|
||||
ltraitors = addroles["traitor"]
|
||||
lpipers = addroles["piper"]
|
||||
|
||||
if chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ltraitors, lpipers, 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 , 15 , 18 , 21 )
|
||||
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
|
||||
self.ROLE_GUIDE.update({ # village roles
|
||||
"seer" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"shaman" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"matchmaker" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"hunter" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
"augur" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
"time lord" : ( 0 , 0 , 0 , 0 , 0 , 1 ),
|
||||
"guardian angel" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
# wolf roles
|
||||
"wolf" : ( 1 , 2 , 2 , 2 , 2 , 2 ),
|
||||
"wolf cub" : ( 0 , 0 , 0 , 0 , 0 , 1 ),
|
||||
"traitor" : ( 1 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"werecrow" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
"hag" : ( 0 , 0 , 1 , 1 , 1 , 1 ),
|
||||
# neutral roles
|
||||
"vengeful ghost" : ( 0 , 1 , 1 , 1 , 2 , 2 ),
|
||||
"amnesiac" : ( 0 , 0 , 1 , 1 , 1 , 1 ),
|
||||
"lycan" : ( 0 , 0 , 0 , 1 , 1 , 1 ),
|
||||
# templates
|
||||
"cursed villager" : ( 2 , 2 , 2 , 2 , 2 , 2 ),
|
||||
"assassin" : ( 0 , 1 , 2 , 2 , 2 , 2 ),
|
||||
"gunner" : ( 0 , 1 , 1 , 1 , 1 , 1 ),
|
||||
"sharpshooter" : ( 0 , 0 , 0 , 0 , 0 , 1 ),
|
||||
"bureaucrat" : ( 0 , 0 , 1 , 1 , 1 , 1 ),
|
||||
"mayor" : ( 0 , 0 , 0 , 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, 1)
|
||||
|
||||
def teardown(self):
|
||||
events.remove_listener("chk_win", self.chk_win, 1)
|
||||
|
||||
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 ),
|
||||
})
|
||||
|
||||
# Persistence
|
||||
|
||||
|
||||
|
||||
conn = sqlite3.connect("data.sqlite3", check_same_thread = False)
|
||||
c = conn.cursor()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user