diff --git a/modules/wolfgame.py b/modules/wolfgame.py index f3ac0c1..cce50c8 100644 --- a/modules/wolfgame.py +++ b/modules/wolfgame.py @@ -69,10 +69,7 @@ var.PLAYERS = {} var.DCED_PLAYERS = {} var.ADMIN_TO_PING = None var.AFTER_FLASTGAME = None -var.PHASE = "none" # "join", "day", or "night" var.TIMERS = {} -var.DEAD = [] -var.NO_LYNCH = [] var.ORIGINAL_SETTINGS = {} @@ -81,15 +78,12 @@ var.LAST_SAID_TIME = {} var.GAME_START_TIME = datetime.now() # for idle checker only var.CAN_START_TIME = 0 var.GRAVEYARD_LOCK = threading.RLock() -var.GAME_ID = 0 var.STARTED_DAY_PLAYERS = 0 var.DISCONNECTED = {} # players who got disconnected var.LOGGER = WolfgameLogger(var.LOG_FILENAME, var.BARE_LOG_FILENAME) -var.JOINED_THIS_GAME = [] # keeps track of who already joined this game at least once (cloaks) - var.OPPED = False # Keeps track of whether the bot is opped if botconfig.DEBUG_MODE: @@ -207,13 +201,16 @@ def reset_modes_timers(cli): cmodes.append(("-q", deadguy+"!*@*")) mass_mode(cli, cmodes) -def reset(cli): - var.PHASE = "none" +def reset(): + var.PHASE = "none" # "join", "day", or "night" var.GAME_ID = 0 var.DEAD = [] var.ROLES = {"person" : []} - var.JOINED_THIS_GAME = [] + var.JOINED_THIS_GAME = [] # keeps track of who already joined this game at least once (cloaks) var.NO_LYNCH = [] + var.FGAMED = False + var.CURRENT_GAMEMODE = "default" + var.GAMEMODE_VOTES = {} #list of players who have used !game reset_settings() @@ -221,6 +218,7 @@ def reset(cli): dict.clear(var.PLAYERS) dict.clear(var.DCED_PLAYERS) dict.clear(var.DISCONNECTED) +reset() def make_stasis(nick, penalty): try: @@ -244,7 +242,7 @@ def forced_exit(cli, nick, *rest): # Admin Only stop_game(cli) else: reset_modes_timers(cli) - reset(cli) + reset() cli.quit("Forced quit from "+nick) @@ -259,7 +257,7 @@ def restart_program(cli, nick, *rest): stop_game(cli) else: reset_modes_timers(cli) - reset(cli) + reset() cli.quit("Forced restart from "+nick) raise SystemExit @@ -531,7 +529,7 @@ def kill_join(cli, chan): pl.sort(key=lambda x: x.lower()) msg = 'PING! {0}'.format(", ".join(pl)) reset_modes_timers(cli) - reset(cli) + reset() cli.msg(chan, msg) cli.msg(chan, 'The current game took too long to start and ' + 'has been canceled. If you are still active, ' + @@ -1130,123 +1128,128 @@ def stop_game(cli, winner = ""): elif len(lovers) > 2: cli.msg(chan, "The lovers were {0}, and {1}".format(", ".join(lovers[0:-1]), lovers[-1])) - plrl = {} - winners = [] - for role,ppl in var.ORIGINAL_ROLES.items(): - if role in var.TEMPLATE_RESTRICTIONS.keys(): - continue - for x in ppl: - if x != None: - if role == "amnesiac" and x in var.AMNESIACS: - plrl[x] = var.FINAL_ROLES[x] - elif role != "amnesiac" and x in var.FINAL_ROLES: # role swap - plrl[x] = var.FINAL_ROLES[x] - else: - plrl[x] = role - for plr, rol in plrl.items(): - orol = rol # original role, since we overwrite rol in case of clone - splr = plr # plr stripped of the (dced) bit at the front, since other dicts don't have that - if plr.startswith("(dced)") and plr[6:] in var.DCED_PLAYERS.keys(): - acc = var.DCED_PLAYERS[plr[6:]]["account"] - splr = plr[6:] - elif plr in var.PLAYERS.keys(): - acc = var.PLAYERS[plr]["account"] - else: - acc = "*" #probably fjoin'd fake - - if rol == "clone": - # see if they became a different role - if splr in var.FINAL_ROLES: - rol = var.FINAL_ROLES[splr] - - won = False - iwon = False - # determine if this player's team won - if rol in var.WOLFTEAM_ROLES: # the player was wolf-aligned - if winner == "wolves": - won = True - elif rol in var.TRUE_NEUTRAL_ROLES: - # true neutral roles never have a team win (with exception of monsters), only individual wins - if winner == "monsters" and rol == "monster": - won = True - elif rol in ("amnesiac", "vengeful ghost"): - if var.DEFAULT_ROLE == "villager" and winner == "villagers": - won = True - elif var.DEFAULT_ROLE == "cultist" and winner == "wolves": - won = True - else: - if winner == "villagers": - won = True - - survived = var.list_players() - if plr.startswith("(dced)"): - # You get NOTHING! You LOSE! Good DAY, sir! - won = False - iwon = False - elif rol == "fool" and "@" + splr == winner: - iwon = True - elif rol == "monster" and splr in survived and winner == "monsters": - iwon = True - elif splr in var.LOVERS and splr in survived: - for lvr in var.LOVERS[splr]: - if lvr in plrl: - lvrrol = plrl[lvr] - elif ("(dced)" + lvr) in plrl: - lvrrol = plrl["(dced)" + lvr] - if lvrrol == "clone" and lvr in var.FINAL_ROLES: - lvrrol = var.FINAL_ROLES[lvr] - - if lvr in survived and not winner.startswith("@") and winner != "monsters": - iwon = True - break - elif lvr in survived and winner.startswith("@") and winner == "@" + lvr and var.LOVER_WINS_WITH_FOOL: - iwon = True - break - elif lvr in survived and winner == "monsters" and lvrrol == "monster": - iwon = True - break - - if plr.startswith("(dced)"): - won = False - iwon = False - elif rol == "crazed shaman" or rol == "clone": - # For clone, this means they ended game while being clone and not some other role - if splr in survived and not winner.startswith("@") and winner != "monsters": - iwon = True - elif rol == "vengeful ghost": - if not winner.startswith("@") and winner != "monsters": - if won and splr in survived: - iwon = True - elif splr in var.VENGEFUL_GHOSTS and var.VENGEFUL_GHOSTS[splr] == "villagers" and winner == "wolves": - won = True - iwon = True - elif splr in var.VENGEFUL_GHOSTS and var.VENGEFUL_GHOSTS[splr] == "wolves" and winner == "villagers": - won = True - iwon = True - elif rol == "lycan" or splr in var.LYCANS: - if splr in var.LYCANS and winner == "wolves": - won = True - elif splr not in var.LYCANS and winner == "villagers": - won = True - else: - won = False - if not iwon: - iwon = won and splr in survived - elif rol == "jester" and splr in var.JESTERS: - iwon = True - elif not iwon: - iwon = won and splr in survived # survived, team won = individual win - - if acc != "*": - var.update_role_stats(acc, orol, won, iwon) - - if won or iwon: - winners.append(splr) - - size = len(survived) + len(var.DEAD) # Only update if someone actually won, "" indicates everyone died or abnormal game stop if winner != "": - var.update_game_stats(size, winner) + plrl = {} + winners = [] + for role,ppl in var.ORIGINAL_ROLES.items(): + if role in var.TEMPLATE_RESTRICTIONS.keys(): + continue + for x in ppl: + if x != None: + if role == "amnesiac" and x in var.AMNESIACS: + plrl[x] = var.FINAL_ROLES[x] + elif role != "amnesiac" and x in var.FINAL_ROLES: # role swap + plrl[x] = var.FINAL_ROLES[x] + else: + plrl[x] = role + for plr, rol in plrl.items(): + orol = rol # original role, since we overwrite rol in case of clone + splr = plr # plr stripped of the (dced) bit at the front, since other dicts don't have that + if plr.startswith("(dced)") and plr[6:] in var.DCED_PLAYERS.keys(): + acc = var.DCED_PLAYERS[plr[6:]]["account"] + splr = plr[6:] + elif plr in var.PLAYERS.keys(): + acc = var.PLAYERS[plr]["account"] + else: + acc = "*" #probably fjoin'd fake + + if rol == "clone": + # see if they became a different role + if splr in var.FINAL_ROLES: + rol = var.FINAL_ROLES[splr] + + won = False + iwon = False + # determine if this player's team won + if rol in var.WOLFTEAM_ROLES: # the player was wolf-aligned + if winner == "wolves": + won = True + elif rol in var.TRUE_NEUTRAL_ROLES: + # true neutral roles never have a team win (with exception of monsters), only individual wins + if winner == "monsters" and rol == "monster": + won = True + elif rol in ("amnesiac", "vengeful ghost"): + if var.DEFAULT_ROLE == "villager" and winner == "villagers": + won = True + elif var.DEFAULT_ROLE == "cultist" and winner == "wolves": + won = True + else: + if winner == "villagers": + won = True + + survived = var.list_players() + if plr.startswith("(dced)"): + # You get NOTHING! You LOSE! Good DAY, sir! + won = False + iwon = False + elif rol == "fool" and "@" + splr == winner: + iwon = True + elif rol == "monster" and splr in survived and winner == "monsters": + iwon = True + elif splr in var.LOVERS and splr in survived: + for lvr in var.LOVERS[splr]: + lvrrol = "" #somehow lvrrol wasn't set and caused a crash once + if lvr in plrl: + lvrrol = plrl[lvr] + elif ("(dced)" + lvr) in plrl: + lvrrol = plrl["(dced)" + lvr] + if lvrrol == "clone" and lvr in var.FINAL_ROLES: + lvrrol = var.FINAL_ROLES[lvr] + + if lvr in survived and not winner.startswith("@") and winner != "monsters": + iwon = True + break + elif lvr in survived and winner.startswith("@") and winner == "@" + lvr and var.LOVER_WINS_WITH_FOOL: + iwon = True + break + elif lvr in survived and winner == "monsters" and lvrrol == "monster": + iwon = True + break + + if plr.startswith("(dced)"): + won = False + iwon = False + elif rol == "crazed shaman" or rol == "clone": + # For clone, this means they ended game while being clone and not some other role + if splr in survived and not winner.startswith("@") and winner != "monsters": + iwon = True + elif rol == "vengeful ghost": + if not winner.startswith("@") and winner != "monsters": + if won and splr in survived: + iwon = True + elif splr in var.VENGEFUL_GHOSTS and var.VENGEFUL_GHOSTS[splr] == "villagers" and winner == "wolves": + won = True + iwon = True + elif splr in var.VENGEFUL_GHOSTS and var.VENGEFUL_GHOSTS[splr] == "wolves" and winner == "villagers": + won = True + iwon = True + elif rol == "lycan" or splr in var.LYCANS: + if splr in var.LYCANS and winner == "wolves": + won = True + elif splr not in var.LYCANS and winner == "villagers": + won = True + else: + won = False + if not iwon: + iwon = won and splr in survived + elif rol == "jester" and splr in var.JESTERS: + iwon = True + elif not iwon: + iwon = won and splr in survived # survived, team won = individual win + + if acc != "*": + var.update_role_stats(acc, orol, won, iwon) + for role in var.TEMPLATE_RESTRICTIONS.keys(): + if plr in var.ORIGINAL_ROLES[role]: + var.update_role_stats(acc, role, won, iwon) + if splr in var.LOVERS: + var.update_role_stats(acc, "lover", won, iwon) + + if won or iwon: + winners.append(splr) + + var.update_game_stats(var.CURRENT_GAMEMODE, len(survived) + len(var.DEAD), winner) # spit out the list of winners winners.sort() @@ -1264,9 +1267,9 @@ def stop_game(cli, winner = ""): var.PHASE = "writing files" var.LOGGER.saveToFile() - reset(cli) + reset() - # This must be after reset(cli) + # This must be after reset() if var.AFTER_FLASTGAME: var.AFTER_FLASTGAME() var.AFTER_FLASTGAME = None @@ -1285,7 +1288,7 @@ def chk_win(cli, end_game = True): if lpl == 0: #cli.msg(chan, "No more players remaining. Game ended.") reset_modes_timers(cli) - reset(cli) + reset() return True return False @@ -1313,7 +1316,7 @@ def chk_win(cli, end_game = True): if lpl < 1: message = "Game over! There are no players remaining. Nobody wins." - winner = "" + winner = "none" elif lwolves == lpl / 2: if len(var.ROLES["monster"]) > 0: plural = "s" if len(var.ROLES["monster"]) > 1 else "" @@ -1674,6 +1677,10 @@ def del_player(cli, nick, forced_death = False, devoice = True, end_game = True, var.WOUNDED.remove(nick) if nick in var.ASLEEP: var.ASLEEP.remove(nick) + if nick in var.PLAYERS: + cloak = var.PLAYERS[nick]["cloak"] + if cloak in var.GAMEMODE_VOTES: + del var.GAMEMODE_VOTES[cloak] chk_decision(cli) elif var.PHASE == "night" and ret: chk_nightdone(cli) @@ -2202,6 +2209,8 @@ def leave_game(cli, nick, chan, rest): var.ORIGINAL_ROLES[r].remove(nick) var.ORIGINAL_ROLES[r].append("(dced)"+nick) make_stasis(nick, var.LEAVE_STASIS_PENALTY) + if nick in var.PLAYERS: + var.DCED_PLAYERS[nick] = var.PLAYERS.pop(nick) del_player(cli, nick, death_triggers = False) @@ -4826,13 +4835,14 @@ def cgamemode(cli, arg): if modeargs[0] in var.GAME_MODES.keys(): md = modeargs.pop(0) try: - gm = var.GAME_MODES[md](*modeargs) + gm = var.GAME_MODES[md][0](*modeargs) for attr in dir(gm): val = getattr(gm, attr) if (hasattr(var, attr) and not callable(val) and not attr.startswith("_")): var.ORIGINAL_SETTINGS[attr] = getattr(var, attr) setattr(var, attr, val) + var.CURRENT_GAMEMODE = md return True except var.InvalidModeException as e: cli.msg(botconfig.CHANNEL, "Invalid mode: "+str(e)) @@ -4875,9 +4885,24 @@ def start(cli, nick, forced = False): return if len(villagers) > var.MAX_PLAYERS: - cli.msg(chan, "{0}: At most \u0002{1}\u0002 players may play in this game mode.".format(nick, var.MAX_PLAYERS)) + cli.msg(chan, "{0}: At most \u0002{1}\u0002 players may play.".format(nick, var.MAX_PLAYERS)) return + if not var.FGAMED: + votes = {} #key = gamemode, not cloak + for gamemode in var.GAMEMODE_VOTES.values(): + if len(villagers) >= var.GAME_MODES[gamemode][1] and len(villagers) <= var.GAME_MODES[gamemode][2]: + votes[gamemode] = votes.get(gamemode, 0) + 1 + voted = [gamemode for gamemode in votes if votes[gamemode] == max(votes.values()) and votes[gamemode] >= len(villagers)/2] + if len(voted): + cgamemode(cli, random.choice(voted)) + else: + possiblegamemodes = [] + for gamemode in var.GAME_MODES.keys(): + if len(villagers) >= var.GAME_MODES[gamemode][1] and len(villagers) <= var.GAME_MODES[gamemode][2] and var.GAME_MODES[gamemode][3] > 0: + possiblegamemodes += [gamemode]*(var.GAME_MODES[gamemode][3]+votes.get(gamemode, 0)*15) + cgamemode(cli, random.choice(possiblegamemodes)) + for index in range(len(var.ROLE_INDEX) - 1, -1, -1): if var.ROLE_INDEX[index] <= len(villagers): addroles = {k:v[index] for k,v in var.ROLE_GUIDE.items()} @@ -5032,7 +5057,7 @@ def start(cli, nick, forced = False): var.SPECIAL_ROLES["goat herder"] = [ nick ] cli.msg(chan, ("{0}: Welcome to Werewolf, the popular detective/social party "+ - "game (a theme of Mafia).").format(", ".join(pl))) + "game (a theme of Mafia). Using the \002{1}\002 game mode.").format(", ".join(pl), var.CURRENT_GAMEMODE)) cli.mode(chan, "+m") var.ORIGINAL_ROLES = copy.deepcopy(var.ROLES) # Make a copy @@ -5382,7 +5407,7 @@ def reset_game(cli, nick, chan, rest): stop_game(cli) else: reset_modes_timers(cli) - reset(cli) + reset() @pmcmd("rules") @@ -5616,23 +5641,25 @@ def listroles(cli, nick, chan, rest): #prepend player count if called without any arguments if not len(rest[0]) and pl > 0: txt += " {0}: There {1} \u0002{2}\u0002 playing.".format(nick, "is" if pl == 1 else "are", pl) + if var.PHASE in ["night", "day"]: + txt += " Using the {0} game mode.".format(var.CURRENT_GAMEMODE) #read game mode to get roles for if len(rest[0]) and not rest[0].isdigit(): - #check for valid roleset ("roles" roleset is treated as invalid) + #check for valid game mode ("roles" gamemode is treated as invalid) if rest[0] != "roles" and rest[0] in var.GAME_MODES.keys(): - mode = var.GAME_MODES[rest[0]]() + mode = var.GAME_MODES[rest[0]][0]() if hasattr(mode, "ROLE_INDEX"): roleindex = getattr(mode, "ROLE_INDEX") if hasattr(mode, "ROLE_GUIDE"): roleguide = getattr(mode, "ROLE_GUIDE") rest.pop(0) else: - txt += " {0}: {1} is not a valid roleset.".format(nick, rest[0]) + txt += " {0}: {1} is not a valid game mode.".format(nick, rest[0]) rest = [] roleindex = {} - #number of players to print the roleset for + #number of players to print the game mode for if len(rest) and rest[0].isdigit(): index = int(rest[0]) for i in range(len(roleindex)-1, -1, -1): @@ -5824,38 +5851,45 @@ def game_stats(cli, nick, chan, rest): if (chan != nick and var.LAST_GSTATS and var.GSTATS_RATE_LIMIT and var.LAST_GSTATS + timedelta(seconds=var.GSTATS_RATE_LIMIT) > datetime.now()): - cli.notice(nick, ('This command is rate-limited. Please wait a while ' - 'before using it again.')) + cli.notice(nick, ("This command is rate-limited. Please wait a while " + "before using it again.")) return if chan != nick: var.LAST_GSTATS = datetime.now() if var.PHASE not in ('none', 'join'): - cli.notice(nick, 'Wait until the game is over to view stats.') + cli.notice(nick, "Wait until the game is over to view stats.") + return + + gamemode = var.CURRENT_GAMEMODE + rest = rest.strip().split() + # Check for gamemode + if len(rest) and not rest[0].isdigit(): + gamemode = rest[0] + if gamemode not in var.GAME_MODES.keys(): + cli.notice(nick, "{0} is not a valid game mode".format(gamemode)) + return + rest.pop(0) + # Check for invalid input + if len(rest) and rest[0].isdigit() and ( + int(rest[0]) > var.GAME_MODES[gamemode][2] or int(rest[0]) < var.GAME_MODES[gamemode][1]): + cli.notice(nick, "Please enter an integer between "+\ + "{0} and {1}.".format(var.GAME_MODES[gamemode][1], var.GAME_MODES[gamemode][2])) return # List all games sizes and totals if no size is given - if not rest: + if not len(rest): if chan == nick: - pm(cli, nick, var.get_game_totals()) + pm(cli, nick, var.get_game_totals(gamemode)) else: - cli.msg(chan, var.get_game_totals()) - - return - - # Check for invalid input - rest = rest.strip() - if not rest.isdigit() or int(rest) > var.MAX_PLAYERS or int(rest) < var.MIN_PLAYERS: - cli.notice(nick, ('Please enter an integer between {} and ' - '{}.').format(var.MIN_PLAYERS, var.MAX_PLAYERS)) - return - - # Attempt to find game stats for the given game size - if chan == nick: - pm(cli, nick, var.get_game_stats(int(rest))) + cli.msg(chan, var.get_game_totals(gamemode)) else: - cli.msg(chan, var.get_game_stats(int(rest))) + # Attempt to find game stats for the given game size + if chan == nick: + pm(cli, nick, var.get_game_stats(gamemode, int(rest[0]))) + else: + cli.msg(chan, var.get_game_stats(gamemode, int(rest[0]))) @pmcmd('gamestats', 'gstats') @@ -5923,6 +5957,52 @@ def player_stats(cli, nick, chan, rest): def player_stats_pm(cli, nick, rest): player_stats(cli, nick, nick, rest) +@cmd('game', raw_nick = True) +def game(cli, nick, chan, rest): + nick, _, __, cloak = parse_nick(nick) + if var.PHASE == "none": + cli.notice(nick, "No game is currently running.") + return + if var.PHASE != "join": + cli.notice(nick, "Werewolf is already in play.") + return + if nick not in var.list_players(): + cli.notice(nick, "You're currently not playing.") + return + + if rest: + gamemode = rest.lower().split()[0] + else: + gamemodes = ", ".join(["\002{}\002".format(gamemode) if len(var.list_players()) in range(var.GAME_MODES[gamemode][1], + var.GAME_MODES[gamemode][2]+1) else gamemode for gamemode in var.GAME_MODES.keys() if gamemode != "roles"]) + cli.notice(nick, "No game mode specified. Available game modes: " + gamemodes) + return + + if gamemode not in var.GAME_MODES.keys(): + #players can vote by only using partial name + matches = 0 + possiblegamemode = gamemode + for mode in var.GAME_MODES.keys(): + if mode.startswith(gamemode) and mode != "roles": + possiblegamemode = mode + matches += 1 + if matches != 1: + cli.notice(nick, "\002{0}\002 is not a valid game mode.".format(gamemode)) + return + else: + gamemode = possiblegamemode + + if gamemode != "roles": + var.GAMEMODE_VOTES[cloak] = gamemode + cli.msg(chan, "\002{0}\002 votes for the \002{1}\002 game mode.".format(nick, gamemode)) + else: + cli.notice(nick, "You can't vote for that game mode.") + +def game_help(args=''): + return "Votes to make a specific game mode more likely. Available game mode setters: " +\ + ", ".join(["\002{}\002".format(gamemode) if len(var.list_players()) in range(var.GAME_MODES[gamemode][1], var.GAME_MODES[gamemode][2]+1) + else gamemode for gamemode in var.GAME_MODES.keys() if gamemode != "roles"]) +game.__doc__ = game_help @cmd("fpull", admin_only=True) def fpull(cli, nick, chan, rest): @@ -6076,13 +6156,26 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: cli.notice(nick, 'You\'re currently not playing.') return - rest = rest.strip().lower() - if rest: - if cgamemode(cli, rest): + rest = mode = rest.strip().lower() + if rest not in var.GAME_MODES.keys() and not rest.startswith("roles"): + rest = rest.split()[0] + #players can vote by only using partial name + matches = 0 + for gamemode in var.GAME_MODES.keys(): + if gamemode.startswith(rest): + mode = gamemode + matches += 1 + if matches != 1: + cli.notice(nick, "\002{0}\002 is not a valid game mode.".format(rest)) + return + + if cgamemode(cli, mode): cli.msg(chan, ('\u0002{}\u0002 has changed the game settings ' 'successfully.').format(nick)) - + var.FGAMED = True + else: + cli.notice(nick, fgame.__doc__()) def fgame_help(args=''): args = args.strip() @@ -6090,7 +6183,10 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS: if not args: return 'Available game mode setters: ' + ', '.join(var.GAME_MODES.keys()) elif args in var.GAME_MODES.keys(): - return var.GAME_MODES[args].__doc__ + if hasattr(var.GAME_MODES[args][0], "__doc__"): + return var.GAME_MODES[args][0].__doc__ + else: + return "Game mode {0} has no doc string".format(args) else: return 'Game mode setter \u0002{}\u0002 not found.'.format(args) diff --git a/settings/wolfgame.py b/settings/wolfgame.py index e4b117e..c55d1e2 100644 --- a/settings/wolfgame.py +++ b/settings/wolfgame.py @@ -283,9 +283,9 @@ def break_long_message(phrases, joinstr = " "): return message class InvalidModeException(Exception): pass -def game_mode(name): +def game_mode(name, minp, maxp, likelihood = 0): def decor(c): - GAME_MODES[name] = c + GAME_MODES[name] = (c, minp, maxp, likelihood) return c return decor @@ -296,11 +296,12 @@ def reset_roles(index): return newguide # TODO: implement more game modes -@game_mode("roles") +@game_mode("roles", minp = 4, maxp = 35) class ChangedRolesMode(object): """Example: !fgame roles=wolf:1,seer:0,guardian angel:1""" def __init__(self, arg = ""): + self.MAX_PLAYERS = 35 self.ROLE_GUIDE = ROLE_GUIDE.copy() self.ROLE_INDEX = (MIN_PLAYERS,) pairs = arg.split(",") @@ -338,19 +339,76 @@ class ChangedRolesMode(object): except ValueError: raise InvalidModeException("A bad value was used in mode roles.") -@game_mode("default") +@game_mode("default", minp = 4, maxp = 24, likelihood = 15) class DefaultMode(object): + """Default game mode.""" def __init__(self): # No extra settings, just an explicit way to revert to default settings pass +@game_mode("foolish", minp = 8,maxp = 24, likelihood = 7) +class FoolishMode(object): + """Contains the fool, be careful not to lynch them!""" + def __init__(self): + 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 = 7) +class MadMode(object): + """This game mode has mad scientist and many things that may kill you.""" + def __init__(self): + 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 ), + }) # evilvillage is broken, disable for now -#@game_mode("evilvillage") +#@game_mode("evilvillage", minp = 6, maxp = 18) class EvilVillageMode(object): + """Majority of the village is wolf aligned, safes must secretly try to kill the wolves.""" def __init__(self): - self.MIN_PLAYERS = 6 - self.MAX_PLAYERS = 18 self.DEFAULT_ROLE = "cultist" self.ROLE_INDEX = ( 6 , 10 , 15 ) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) @@ -368,11 +426,10 @@ class EvilVillageMode(object): "fool" : ( 0 , 1 , 1 ), }) -@game_mode("classic") +@game_mode("classic", minp = 4, maxp = 21, likelihood = 4) class ClassicMode(object): + """Classic game mode from before all the changes.""" def __init__(self): - self.MIN_PLAYERS = 4 - self.MAX_PLAYERS = 21 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 @@ -390,11 +447,10 @@ class ClassicMode(object): "gunner" : ( 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 ), }) -@game_mode("rapidfire") +@game_mode("rapidfire", minp = 6, maxp = 24, likelihood = 0) class RapidFireMode(object): + """Many roles that lead to multiple chain deaths.""" def __init__(self): - self.MIN_PLAYERS = 6 - self.MAX_PLAYERS = 25 self.SHARPSHOOTER_CHANCE = 1 self.DAY_TIME_LIMIT = 480 self.DAY_TIME_WARN = 360 @@ -423,11 +479,41 @@ class RapidFireMode(object): "sharpshooter" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 ), }) -@game_mode("noreveal") -class NoRevealMode(object): +@game_mode("drunkfire", minp = 8, maxp = 17, likelihood = 0) +class DrunkFireMode(object): + """Most players get a gun, quickly shoot all the wolves!""" + def __init__(self): + 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 = 0) +class NoRevealMode(object): + """Roles are not revealed when players die.""" def __init__(self): - self.MIN_PLAYERS = 4 - self.MAX_PLAYERS = 21 self.ROLE_REVEAL = False self.ROLE_INDEX = ( 4 , 6 , 8 , 10 , 12 , 15 , 17 , 19 ) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) @@ -452,14 +538,38 @@ class NoRevealMode(object): "cursed villager" : ( 0 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ), }) -@game_mode("amnesia") -class AmnesiaMode(object): +@game_mode("lycan", minp = 7, maxp = 21, likelihood = 1) +class LycanMode(object): + """Many lycans will turn into wolves. Hunt them down before the wolves overpower the village.""" + def __init__(self): + self.ROLE_INDEX = ( 7 , 9 , 10 , 12 , 15 , 17 , 20 ) + self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) + self.ROLE_GUIDE.update({# village roles + "seer" : ( 1 , 1 , 1 , 1 , 1 , 1 , 1 ), + "guardian angel" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 ), + "detective" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 ), + "matchmaker" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 ), + "hunter" : ( 1 , 1 , 2 , 2 , 2 , 2 , 2 ), + # wolf roles + "wolf" : ( 1 , 2 , 2 , 2 , 2 , 2 , 2 ), + "traitor" : ( 0 , 0 , 0 , 0 , 1 , 1 , 1 ), + # neutral roles + "clone" : ( 0 , 1 , 1 , 1 , 1 , 2 , 2 ), + "lycan" : ( 1 , 2 , 2 , 3 , 4 , 4 , 5 ), + # templates + "cursed villager" : ( 1 , 1 , 1 , 2 , 2 , 2 , 2 ), + "gunner" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 ), + "sharpshooter" : ( 0 , 0 , 0 , 0 , 0 , 1 , 1 ), + "mayor" : ( 0 , 0 , 1 , 1 , 1 , 1 , 1 ), + }) + +@game_mode("amnesia", minp = 10, maxp = 24, likelihood = 0) +class AmnesiaMode(object): + """Everyone gets assigned a random role on night 3.""" def __init__(self): - self.MIN_PLAYERS = 10 - self.MAX_PLAYERS = 24 self.DEFAULT_ROLE = "cultist" self.HIDDEN_AMNESIAC = False - self.ROLE_INDEX = range(self.MIN_PLAYERS, self.MAX_PLAYERS + 1) + self.ROLE_INDEX = range(10, 25) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) self.ROLE_GUIDE.update({ "wolf" : [2 for i in self.ROLE_INDEX], @@ -469,13 +579,12 @@ class AmnesiaMode(object): # Credits to Metacity for designing and current name # Blame arkiwitect for the original name of KrabbyPatty -@game_mode("aleatoire") +@game_mode("aleatoire", minp = 4, maxp = 24, likelihood = 3) class AleatoireMode(object): + """Game mode created by Metacity and balanced by woffle.""" def __init__(self): - self.MIN_PLAYERS = 4 - self.MAX_PLAYERS = 24 self.SHARPSHOOTER_CHANCE = 1 - # SHAMAN CRAZED SHAMAN + # SHAMAN , CRAZED SHAMAN self.TOTEM_CHANCES = { "death": ( 4/20 , 1/15 ), "protection": ( 8/20 , 1/15 ), "silence": ( 2/20 , 1/15 ), @@ -584,8 +693,8 @@ with conn: 'UNIQUE(player, role))')) - c.execute(('CREATE TABLE IF NOT EXISTS gamestats (size SMALLINT, villagewins SMALLINT, ' + - 'wolfwins SMALLINT, totalgames SMALLINT, UNIQUE(size))')) + c.execute(('CREATE TABLE IF NOT EXISTS gamestats (gamemode TEXT, size SMALLINT, villagewins SMALLINT, ' + + 'wolfwins SMALLINT, monsterwins SMALLINT, foolwins SMALLINT, totalgames SMALLINT, UNIQUE(gamemode, size))')) if OPT_IN_PING: @@ -671,27 +780,31 @@ def update_role_stats(acc, role, won, iwon): c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)", (acc, role, wins, iwins, total)) -def update_game_stats(size, winner): +def update_game_stats(gamemode, size, winner): with conn: - vwins, wwins, total = 0, 0, 0 + vwins, wwins, mwins, fwins, total = 0, 0, 0, 0, 0 - c.execute("SELECT villagewins, wolfwins, totalgames FROM gamestats "+ - "WHERE size=?", (size,)) + c.execute("SELECT villagewins, wolfwins, monsterwins, foolwins, totalgames "+ + "FROM gamestats WHERE gamemode=? AND size=?", (gamemode, size)) row = c.fetchone() if row: - vwins, wwins, total = row + vwins, wwins, mwins, fwins, total = row if winner == "wolves": wwins += 1 elif winner == "villagers": vwins += 1 + elif winner == "monsters": + mwins += 1 + elif winner.startswith("@"): + fwins += 1 total += 1 - c.execute("INSERT OR REPLACE INTO gamestats VALUES (?,?,?,?)", - (size, vwins, wwins, total)) + c.execute("INSERT OR REPLACE INTO gamestats VALUES (?,?,?,?,?,?,?)", + (gamemode, size, vwins, wwins, mwins, fwins, total)) def get_player_stats(acc, role): - if role.lower() not in [k.lower() for k in ROLE_GUIDE.keys()]: + if role.lower() not in [k.lower() for k in ROLE_GUIDE.keys()] and role != "lover": return "No such role: {0}".format(role) with conn: c.execute("SELECT player FROM rolestats WHERE player=? COLLATE NOCASE", (acc,)) @@ -723,27 +836,31 @@ def get_player_totals(acc): else: return "\u0002{0}\u0002 has not played any games.".format(acc) -def get_game_stats(size): +def get_game_stats(gamemode, size): with conn: - for row in c.execute("SELECT * FROM gamestats WHERE size=?", (size,)): - msg = "\u0002{0}\u0002 player games | Village wins: {1} (%d%%), Wolf wins: {2} (%d%%), Total games: {3}".format(*row) - return msg % (round(row[1]/row[3] * 100), round(row[2]/row[3] * 100)) + for row in c.execute("SELECT * FROM gamestats WHERE gamemode=? AND size=?", (gamemode, size)): + msg = "\u0002%d\u0002 player games | Village wins: %d (%d%%), Wolf wins: %d (%d%%)" % (row[1], row[2], round(row[2]/row[6] * 100), row[3], round(row[3]/row[6] * 100)) + if row[4] > 0: + msg += ", Monster wins: %d (%d%%)" % (row[4], round(row[4]/row[6] * 100)) + if row[5] > 0: + msg += ", Fool wins: %d (%d%%)" % (row[5], round(row[5]/row[6] * 100)) + return msg + ", Total games: {0}".format(row[6]) else: return "No stats for \u0002{0}\u0002 player games.".format(size) -def get_game_totals(): +def get_game_totals(gamemode): size_totals = [] total = 0 with conn: for size in range(MIN_PLAYERS, MAX_PLAYERS + 1): - c.execute("SELECT size, totalgames FROM gamestats WHERE size=?", (size,)) + c.execute("SELECT size, totalgames FROM gamestats WHERE gamemode=? AND size=?", (gamemode, size)) row = c.fetchone() if row: size_totals.append("\u0002{0}p\u0002: {1}".format(*row)) total += row[1] if len(size_totals) == 0: - return "No games have been played." + return "No games have been played in the {0} game mode.".format(gamemode) else: return "Total games ({0}) | {1}".format(total, ", ".join(size_totals))