Initial commit to add a bunch of new roles

This commit is contained in:
Skizzerz 2014-07-12 13:10:44 -05:00
parent 6717f61e6f
commit 23b2e755ca
2 changed files with 403 additions and 384 deletions

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ GSTATS_RATE_LIMIT = 15
PSTATS_RATE_LIMIT = 15 PSTATS_RATE_LIMIT = 15
TIME_RATE_LIMIT = 60 TIME_RATE_LIMIT = 60
SHOTS_MULTIPLIER = .12 # ceil(shots_multiplier * len_players) = bullets given SHOTS_MULTIPLIER = .12 # ceil(shots_multiplier * len_players) = bullets given
SHARPSHOOTER_MULTIPLIER = 0.06
MIN_PLAYERS = 4 MIN_PLAYERS = 4
MAX_PLAYERS = 21 MAX_PLAYERS = 21
DRUNK_SHOTS_MULTIPLIER = 3 DRUNK_SHOTS_MULTIPLIER = 3
@ -24,6 +25,9 @@ JOIN_TIME_LIMIT = 3600
SHORT_DAY_PLAYERS = 6 # Number of players left to have a short day SHORT_DAY_PLAYERS = 6 # Number of players left to have a short day
SHORT_DAY_LIMIT_WARN = 400 SHORT_DAY_LIMIT_WARN = 400
SHORT_DAY_LIMIT_CHANGE = 120 SHORT_DAY_LIMIT_CHANGE = 120
# If time lord is lynched, the day timer gets set to this instead
TIME_LORD_WARN = 60
TIME_LORD_CHANGE = 30
KILL_IDLE_TIME = 300 KILL_IDLE_TIME = 300
WARN_IDLE_TIME = 180 WARN_IDLE_TIME = 180
PART_GRACE_TIME = 30 PART_GRACE_TIME = 30
@ -38,6 +42,12 @@ GOAT_HERDER = True
SELF_LYNCH_ALLOWED = True SELF_LYNCH_ALLOWED = True
HIDDEN_TRAITOR = True HIDDEN_TRAITOR = True
VENGEFUL_GHOST_KNOWS_ROLES = True
WOLF_MAYOR = True
BODYGUARD_CAN_GUARD_SELF = True
START_WITH_DAY = False
WOLF_STEALS_GUN = True # at night, the wolf can steal steal the victim's bullets
ROLE_REVEAL = True
CARE_BOLD = False CARE_BOLD = False
CARE_COLOR = False CARE_COLOR = False
@ -47,48 +57,97 @@ KILL_BOLD = False
LOG_FILENAME = "" LOG_FILENAME = ""
BARE_LOG_FILENAME = "" BARE_LOG_FILENAME = ""
# HIT MISS SUICIDE # HIT MISS SUICIDE HEADSHOT
GUN_CHANCES = ( 5/7 , 1/7 , 1/7 ) GUN_CHANCES = ( 5/7 , 1/7 , 1/7 , 2/5 )
DRUNK_GUN_CHANCES = ( 2/7 , 3/7 , 2/7 ) WOLF_GUN_CHANCES = ( 5/7 , 1/7 , 1/7 , 2/5 )
MANSLAUGHTER_CHANCE = 2/5 # ACCIDENTAL HEADSHOT (FATAL) DRUNK_GUN_CHANCES = ( 2/7 , 3/7 , 2/7 , 2/5 )
SHARPSHOOTER_GUN_CHANCES = ( 1 , 0 , 0 , 1 )
GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE = 1/4 GUNNER_KILLS_WOLF_AT_NIGHT_CHANCE = 1/4
GUARDIAN_ANGEL_DIES_CHANCE = 1/2 GUARDIAN_ANGEL_DIES_CHANCE = 0
BODYGUARD_DIES_CHANCE = 0
DETECTIVE_REVEALED_CHANCE = 2/5 DETECTIVE_REVEALED_CHANCE = 2/5
SHARPSHOOTER_CHANCE = 1/5 # if sharpshooter is enabled, chance that a gunner will become a sharpshooter instead
################################################################################################################# AMNESIAC_NIGHTS = 3 # amnesiac gets to know their actual role on this night
# ROLE INDEX: PLAYERS SEER WOLF CURSED DRUNK HARLOT TRAITOR GUNNER CROW ANGEL DETECTIVE ## BUREAUCRAT_VOTES = 2 # bureaucrat votes count for this many normal votes
#################################################################################################################
ROLES_GUIDE = { 4 : ( 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), ## # DEATH PROTECTION REVEALING NARCOLEPSY SILENCE DESPERATION
6 : ( 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), ## TOTEM_CHANCES = ( 1/6 , 1/6 , 1/6 , 1/6 , 1/6 , 1/6 )
8 : ( 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 ), ##
10 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 ), ##
12 : ( 1 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 ), ##
15 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 1 ), ##
17 : ( 1 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ##
18 : ( 1 , 3 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ##
20 : ( 1 , 4 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ), ##
None : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )} ##
#################################################################################################################
# Notes: ##
#################################################################################################################
GAME_MODES = {} GAME_MODES = {}
AWAY = ['services.', 'services.int'] # cloaks of people who are away. AWAY = ['services.', 'services.int'] # cloaks of people who are away.
SIMPLE_NOTIFY = [] # cloaks of people who !simple, who want everything /notice'd SIMPLE_NOTIFY = [] # cloaks of people who !simple, who want everything /notice'd
ROLE_INDICES = {0 : "seer", # TODO: move this to a game mode called "fixed" once we implement a way to randomize roles (and have that game mode be called "random")
1 : "wolf", DEFAULT_ROLE = "villager"
2 : "cursed villager", ROLES_INDEX = ( 4 , 6 , 8 , 10 , 12 , 15 , 17 , 18 , 20 )
3 : "village drunk", ROLES_GUIDE = {# village roles
4 : "harlot", "villager" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
5 : "traitor", "seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
6 : "gunner", "oracle" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
7 : "werecrow", "village drunk" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
8 : "guardian angel", "harlot" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ),
9 : "detective"} "guardian angel" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
"bodyguard" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"detective" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 ),
"village elder" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"time lord" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"matchmaker" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"mad scientist" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"hunter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"shaman" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
# 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 ),
"cultist" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"minion" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"hag" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"wolf cub" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"sorcerer" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
# neutral roles
"lycan" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"vengeful ghost" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"clone" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"crazed shaman" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"fool" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"monster" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
# templates
"cursed villager" : ( 0 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ),
"gunner" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ),
# NB: for sharpshooter, numbers can't be higher than gunner, since gunners get converted to sharpshooters. This is the MAX number of gunners that can be converted.
"sharpshooter" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"mayor" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"amnesiac" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
"bureaucrat" : ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ),
}
INDEX_OF_ROLE = dict((v,k) for k,v in ROLE_INDICES.items()) # Harlot dies when visiting, gunner kills when shooting, GA and bodyguard have a chance at dying when guarding
WOLF_ROLES = ["wolf", "werecrow", "wolf cub"]
# Access to wolfchat, and counted towards the # of wolves vs villagers when determining if a side has won
WOLFCHAT_ROLES = ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorceror"]
# Wins with the wolves, even if the roles are not necessarily wolves themselves
WOLFTEAM_ROLES = ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorceror", "minion", "cultist"]
# The roles in here are considered templates and will be applied on TOP of other roles. The restrictions are a list of roles that they CANNOT be applied to
# NB: if you want a template to apply to everyone, list it here but make the restrictions an empty list. Templates not listed here are considered full roles instead
TEMPLATE_RESTRICTIONS = {"cursed villager" : ["wolf", "wolf cub", "werecrow", "seer", "fool"],
"gunner" : ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorcerer", "minion", "cultist", "fool", "cursed villager"],
"sharpshooter" : ["wolf", "traitor", "werecrow", "hag", "wolf cub", "sorcerer", "minion", "cultist", "fool", "cursed villager"],
"mayor" : ["fool"],
"assassin" : ["seer", "harlot", "detective", "bodyguard", "guardian angel", "village drunk", "hunter", "shaman", "crazed shaman", "fool", "mayor"],
"amnesiac" : ["villager", "cultist"],
"bureaucrat" : [],
}
TEMPLATE_KEYS = {"cursed villager" : "CURSED",
"gunner" : "GUNNER_LIST",
"mayor" : "MAYORS",
"assassin" : "ASSASSINS",
"amnesiac" : "AMNESIACS",
"bureaucrat" : "BUREAUCRATS",
}
NO_VICTIMS_MESSAGES = ("The body of a young penguin pet is found.", NO_VICTIMS_MESSAGES = ("The body of a young penguin pet is found.",
"A pool of blood and wolf paw prints are found.", "A pool of blood and wolf paw prints are found.",
@ -98,14 +157,17 @@ LYNCH_MESSAGES = ("The villagers, after much debate, finally decide on lynching
"Despite protests, the mob drags their victim to the hanging tree. \u0002{0}\u0002 succumbs to the will of the horde, and is hanged. The villagers have killed a \u0002{1}\u0002.", "Despite protests, the mob drags their victim to the hanging tree. \u0002{0}\u0002 succumbs to the will of the horde, and is hanged. The villagers have killed a \u0002{1}\u0002.",
"Resigned to the inevitable, \u0002{0}\u0002 is led to the gallows. Once the twitching stops, it is discovered that the village lynched a \u0002{1}\u0002.", "Resigned to the inevitable, \u0002{0}\u0002 is led to the gallows. Once the twitching stops, it is discovered that the village lynched a \u0002{1}\u0002.",
"Before the rope is pulled, \u0002{0}\u0002, the \u0002{1}\u0002, throws a grenade at the mob. The grenade explodes early.") "Before the rope is pulled, \u0002{0}\u0002, the \u0002{1}\u0002, throws a grenade at the mob. The grenade explodes early.")
LYNCH_MESSAGES_NO_REVEAL = ("The villagers, after much debate, finally decide on lynching \u0002{0}\u0002.",
"Under a lot of noise, the pitchfork-bearing villagers lynch \u0002{0}\u0002.",
"Despite protests, the mob drags their victim to the hanging tree. \u0002{0}\u0002 succumbs to the will of the horde, and is hanged.",
"Resigned to the inevitable, \u0002{0}\u0002 is led to the gallows.",
"Before the rope is pulled, \u0002{0}\u0002 throws a grenade at the mob. The grenade explodes early.")
import botconfig import botconfig
RULES = (botconfig.CHANNEL + " channel rules: http://wolf.xnrand.com/rules") RULES = (botconfig.CHANNEL + " channel rules: http://wolf.xnrand.com/rules")
# Other settings: # Other settings:
START_WITH_DAY = False
WOLF_STEALS_GUN = True # at night, the wolf can steal steal the victim's bullets
OPT_IN_PING = False # instead of !away/!back, users can opt-in to be pinged OPT_IN_PING = False # instead of !away/!back, users can opt-in to be pinged
PING_IN = [] # cloaks of users who have opted in for ping PING_IN = [] # cloaks of users who have opted in for ping
@ -152,28 +214,13 @@ def game_mode(name):
return decor return decor
CHANGEABLE_ROLES = { "seers" : INDEX_OF_ROLE["seer"], # TODO: implement more game modes
"wolves" : INDEX_OF_ROLE["wolf"],
"cursed" : INDEX_OF_ROLE["cursed villager"],
"drunks" : INDEX_OF_ROLE["village drunk"],
"harlots" : INDEX_OF_ROLE["harlot"],
"traitors" : INDEX_OF_ROLE["traitor"],
"gunners" : INDEX_OF_ROLE["gunner"],
"werecrows" : INDEX_OF_ROLE["werecrow"],
"angels" : INDEX_OF_ROLE["guardian angel"],
"detectives" : INDEX_OF_ROLE["detective"]}
# TODO: implement game modes
@game_mode("roles") @game_mode("roles")
class ChangedRolesMode(object): class ChangedRolesMode(object):
"""Example: !fgame roles=wolves:1,seers:0,angels:1""" """Example: !fgame roles=wolf:1,seer:0,guardian angel:1"""
def __init__(self, arg): def __init__(self, arg = ""):
self.ROLES_GUIDE = ROLES_GUIDE.copy() self.ROLES_GUIDE = ROLES_GUIDE.copy()
lx = list(ROLES_GUIDE[None])
pairs = arg.split(",") pairs = arg.split(",")
if not pairs: if not pairs:
raise InvalidModeException("Invalid syntax for mode roles.") raise InvalidModeException("Invalid syntax for mode roles.")
@ -184,16 +231,29 @@ class ChangedRolesMode(object):
role, num = change role, num = change
try: try:
num = int(num) num = int(num)
try: if role.lower() in self.ROLES_GUIDE:
lx[CHANGEABLE_ROLES[role.lower()]] = num self.ROLES_GUIDE[role.lower()] = tuple([num] * len(ROLES_INDEX))
except KeyError: else:
raise InvalidModeException(("The role \u0002{0}\u0002 "+ raise InvalidModeException(("The role \u0002{0}\u0002 "+
"is not valid.").format(role)) "is not valid.").format(role))
except ValueError: except ValueError:
raise InvalidModeException("A bad value was used in mode roles.") raise InvalidModeException("A bad value was used in mode roles.")
for k in ROLES_GUIDE.keys():
self.ROLES_GUIDE[k] = tuple(lx)
@game_mode("evilvillage")
class EvilVillageMode(object):
def __init__(self):
self.MIN_PLAYERS = 6
self.MAX_PLAYERS = 12
self.DEFAULT_ROLE = "cultist"
self.ROLES_INDEX = ( 6 , 10 )
self.ROLES_GUIDE = {# village roles
"oracle" : ( 1 , 1 ),
"shaman" : ( 1 , 1 ),
"bodyguard" : ( 0 , 1 ),
# wolf roles
"wolf" : ( 1 , 1 ),
"minion" : ( 0 , 1 ),
}
# Persistence # Persistence
@ -221,7 +281,7 @@ with conn:
c.execute('DROP TABLE IF EXISTS roles') c.execute('DROP TABLE IF EXISTS roles')
c.execute('CREATE TABLE roles (id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT)') c.execute('CREATE TABLE roles (id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT)')
for x in ["villager"]+list(ROLE_INDICES.values()): for x in list(ROLES_GUIDE.keys()):
c.execute("INSERT OR REPLACE INTO roles (role) VALUES (?)", (x,)) c.execute("INSERT OR REPLACE INTO roles (role) VALUES (?)", (x,))
@ -229,11 +289,11 @@ with conn:
'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+ 'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+
'UNIQUE(player, role))')) 'UNIQUE(player, role))'))
c.execute(('CREATE TABLE IF NOT EXISTS gamestats (size SMALLINT, villagewins SMALLINT, ' + c.execute(('CREATE TABLE IF NOT EXISTS gamestats (size SMALLINT, villagewins SMALLINT, ' +
'wolfwins SMALLINT, totalgames SMALLINT, UNIQUE(size))')) 'wolfwins SMALLINT, totalgames SMALLINT, UNIQUE(size))'))
if OPT_IN_PING: if OPT_IN_PING:
c.execute('CREATE TABLE IF NOT EXISTS ping (cloak text)') c.execute('CREATE TABLE IF NOT EXISTS ping (cloak text)')
@ -261,7 +321,7 @@ def add_simple_rolemsg(clk):
def remove_ping(clk): def remove_ping(clk):
with conn: with conn:
c.execute('DELETE from ping where cloak=?', (clk,)) c.execute('DELETE from ping where cloak=?', (clk,))
def add_ping(clk): def add_ping(clk):
with conn: with conn:
c.execute('INSERT into ping VALUES (?)', (clk,)) c.execute('INSERT into ping VALUES (?)', (clk,))
@ -289,24 +349,24 @@ def update_role_stats(acc, role, won, iwon):
def update_game_stats(size, winner): def update_game_stats(size, winner):
with conn: with conn:
vwins, wwins, total = 0, 0, 0 vwins, wwins, total = 0, 0, 0
c.execute("SELECT villagewins, wolfwins, totalgames FROM gamestats "+ c.execute("SELECT villagewins, wolfwins, totalgames FROM gamestats "+
"WHERE size=?", (size,)) "WHERE size=?", (size,))
row = c.fetchone() row = c.fetchone()
if row: if row:
vwins, wwins, total = row vwins, wwins, total = row
if winner == "wolves": if winner == "wolves":
wwins += 1 wwins += 1
elif winner == "villagers": elif winner == "villagers":
vwins += 1 vwins += 1
total += 1 total += 1
c.execute("INSERT OR REPLACE INTO gamestats VALUES (?,?,?,?)", c.execute("INSERT OR REPLACE INTO gamestats VALUES (?,?,?,?)",
(size, vwins, wwins, total)) (size, vwins, wwins, total))
def get_player_stats(acc, role): def get_player_stats(acc, role):
if role.lower() not in ["villager"] + [v.lower() for k, v in ROLE_INDICES.items()]: if role.lower() not in [k.lower() for k in ROLES_GUIDE.keys()]:
return "No such role: {0}".format(role) return "No such role: {0}".format(role)
with conn: with conn:
c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,)) c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,))
@ -325,7 +385,7 @@ def get_player_totals(acc):
c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,)) c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,))
player = c.fetchone() player = c.fetchone()
if player: if player:
for role in ["villager"] + [v for k, v in ROLE_INDICES.items()]: for role in [k.lower() for k in ROLES_GUIDE.keys()]:
c.execute("SELECT totalgames FROM rolestats WHERE player=? COLLATE NOCASE AND role=? COLLATE NOCASE", (acc, role)) c.execute("SELECT totalgames FROM rolestats WHERE player=? COLLATE NOCASE AND role=? COLLATE NOCASE", (acc, role))
row = c.fetchone() row = c.fetchone()
if row: if row:
@ -333,7 +393,7 @@ def get_player_totals(acc):
return "\u0002{0}\u0002's totals | {1}".format(player[0], ", ".join(role_totals)) return "\u0002{0}\u0002's totals | {1}".format(player[0], ", ".join(role_totals))
else: else:
return "{0} has not played any games.".format(acc) return "{0} has not played any games.".format(acc)
def get_game_stats(size): def get_game_stats(size):
with conn: with conn:
for row in c.execute("SELECT * FROM gamestats WHERE size=?", (size,)): for row in c.execute("SELECT * FROM gamestats WHERE size=?", (size,)):
@ -352,7 +412,7 @@ def get_game_totals():
if row: if row:
size_totals.append("\u0002{0}p\u0002: {1}".format(*row)) size_totals.append("\u0002{0}p\u0002: {1}".format(*row))
total += row[1] total += row[1]
if len(size_totals) == 0: if len(size_totals) == 0:
return "No games have been played." return "No games have been played."
else: else: