Merge pull request #131 from lykoss/events

Add event system
This commit is contained in:
jacob1 2015-05-09 20:43:38 -04:00
commit 77262cea75
3 changed files with 110 additions and 23 deletions

36
src/events.py Normal file
View File

@ -0,0 +1,36 @@
# event system
EVENT_CALLBACKS = {}
def add_listener(event, callback, priority = 5):
if event not in EVENT_CALLBACKS:
EVENT_CALLBACKS[event] = []
if (priority, callback) not in EVENT_CALLBACKS[event]:
EVENT_CALLBACKS[event].append((priority, callback))
EVENT_CALLBACKS[event].sort(key = lambda x: x[0])
def remove_listener(event, callback, priority = 5):
if event in EVENT_CALLBACKS and (priority, callback) in EVENT_CALLBACKS[event]:
EVENT_CALLBACKS[event].remove((priority, callback))
class Event:
def __init__(self, name, data):
self.stop_processing = False
self.prevent_default = False
self.name = name
self.data = data
def dispatch(self, *args):
if self.name not in EVENT_CALLBACKS:
return
for item in list(EVENT_CALLBACKS[self.name]):
item[1](self, *args)
if self.stop_processing:
break
return not self.prevent_default
# vim: set expandtab:sw=4:ts=4:

View File

@ -1,5 +1,6 @@
from collections import defaultdict from collections import defaultdict
import math import math
from src import events
PING_WAIT = 300 # Seconds PING_WAIT = 300 # Seconds
PING_MIN_WAIT = 30 # How long !start has to wait after a !ping PING_MIN_WAIT = 30 # How long !start has to wait after a !ping
@ -521,28 +522,58 @@ class MadMode(object):
"assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ), "assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ),
}) })
# evilvillage is broken, disable for now @game_mode("evilvillage", minp = 6, maxp = 18, likelihood = 1)
#@game_mode("evilvillage", minp = 6, maxp = 18)
class EvilVillageMode(object): class EvilVillageMode(object):
"""Majority of the village is wolf aligned, safes must secretly try to kill the wolves.""" """Majority of the village is wolf aligned, safes must secretly try to kill the wolves."""
def __init__(self): def __init__(self):
self.DEFAULT_ROLE = "cultist" self.DEFAULT_ROLE = "cultist"
self.ROLE_INDEX = ( 6 , 10 , 15 ) self.DEFAULT_SEEN_AS_VILL = False
self.ABSTAIN_ENABLED = False
self.ROLE_INDEX = ( 6 , 8 , 10 , 15 )
self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX)
self.ROLE_GUIDE.update({# village roles self.ROLE_GUIDE.update({# village roles
"oracle" : ( 1 , 1 , 0 ), "oracle" : ( 0 , 1 , 1 , 0 ),
"seer" : ( 0 , 0 , 1 ), "seer" : ( 0 , 0 , 0 , 1 ),
"guardian angel" : ( 0 , 1 , 1 ), "guardian angel" : ( 0 , 0 , 1 , 1 ),
"shaman" : ( 1 , 1 , 1 ), "shaman" : ( 0 , 0 , 0 , 1 ),
"hunter" : ( 0 , 0 , 1 ), "hunter" : ( 1 , 1 , 1 , 1 ),
"villager" : ( 0 , 0 , 1 ), "villager" : ( 0 , 0 , 0 , 1 ),
# wolf roles # wolf roles
"wolf" : ( 1 , 1 , 2 ), "wolf" : ( 1 , 1 , 1 , 2 ),
"minion" : ( 0 , 1 , 1 ), "minion" : ( 0 , 0 , 1 , 1 ),
# neutral roles # neutral roles
"fool" : ( 0 , 1 , 1 ), "fool" : ( 0 , 0 , 1 , 1 ),
# templates
"cursed villager" : ( 0 , 1 , 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"]))
evt.stop_processing = True
try:
if 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 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) @game_mode("classic", minp = 7, maxp = 21, likelihood = 4)
class ClassicMode(object): class ClassicMode(object):
"""Classic game mode from before all the changes.""" """Classic game mode from before all the changes."""

View File

@ -40,6 +40,10 @@ from src import logger
import urllib.request import urllib.request
import sqlite3 import sqlite3
# done this way so that events is accessible in !eval (useful for debugging)
from src import events
Event = events.Event
debuglog = logger("debug.log", write=False, display=False) # will be True if in debug mode debuglog = logger("debug.log", write=False, display=False) # will be True if in debug mode
errlog = logger("errors.log") errlog = logger("errors.log")
plog = logger(None) #use this instead of print so that logs have timestamps plog = logger(None) #use this instead of print so that logs have timestamps
@ -80,6 +84,7 @@ var.PINGING_IFS = False
var.TIMERS = {} var.TIMERS = {}
var.ORIGINAL_SETTINGS = {} var.ORIGINAL_SETTINGS = {}
var.CURRENT_GAMEMODE = {"name": "default"}
var.LAST_SAID_TIME = {} var.LAST_SAID_TIME = {}
@ -371,6 +376,10 @@ def pm(cli, target, message): # message either privmsg or notice, depending on
cli.msg(target, message) cli.msg(target, message)
def reset_settings(): def reset_settings():
if hasattr(var.CURRENT_GAMEMODE, "teardown") and callable(var.CURRENT_GAMEMODE.teardown):
var.CURRENT_GAMEMODE.teardown()
var.CURRENT_GAMEMODE = {"name": "default"}
for attr in list(var.ORIGINAL_SETTINGS.keys()): for attr in list(var.ORIGINAL_SETTINGS.keys()):
setattr(var, attr, var.ORIGINAL_SETTINGS[attr]) setattr(var, attr, var.ORIGINAL_SETTINGS[attr])
dict.clear(var.ORIGINAL_SETTINGS) dict.clear(var.ORIGINAL_SETTINGS)
@ -412,7 +421,6 @@ def reset():
var.PINGED_ALREADY_ACCS = [] var.PINGED_ALREADY_ACCS = []
var.NO_LYNCH = [] var.NO_LYNCH = []
var.FGAMED = False var.FGAMED = False
var.CURRENT_GAMEMODE = "default"
var.GAMEMODE_VOTES = {} #list of players who have used !game var.GAMEMODE_VOTES = {} #list of players who have used !game
reset_settings() reset_settings()
@ -1516,7 +1524,7 @@ def stats(cli, nick, chan, rest):
else: else:
cli.notice(nick, msg) cli.notice(nick, msg)
if var.PHASE == "join" or not var.ROLE_REVEAL or var.GAME_MODES[var.CURRENT_GAMEMODE][4]: if var.PHASE == "join" or not var.ROLE_REVEAL or var.GAME_MODES[var.CURRENT_GAMEMODE.name][4]:
return return
message = [] message = []
@ -2148,7 +2156,7 @@ def stop_game(cli, winner = "", abort = False):
if won or iwon: if won or iwon:
winners.append(splr) winners.append(splr)
var.update_game_stats(var.CURRENT_GAMEMODE, len(survived) + len(var.DEAD), winner) var.update_game_stats(var.CURRENT_GAMEMODE.name, len(survived) + len(var.DEAD), winner)
# spit out the list of winners # spit out the list of winners
winners.sort() winners.sort()
@ -2226,6 +2234,8 @@ def chk_win(cli, end_game = True):
except KeyError: except KeyError:
pass pass
winner = None
message = ""
if lpl < 1: if lpl < 1:
message = "Game over! There are no players remaining." message = "Game over! There are no players remaining."
winner = "none" winner = "none"
@ -2265,8 +2275,15 @@ def chk_win(cli, end_game = True):
elif lrealwolves == 0: elif lrealwolves == 0:
chk_traitor(cli) chk_traitor(cli)
return chk_win(cli, end_game) return chk_win(cli, end_game)
else:
event = Event("chk_win", {"winner": winner, "message": message})
event.dispatch(var, lpl, lwolves, lrealwolves)
winner = event.data["winner"]
message = event.data["message"]
if winner is None:
return False return False
if end_game: if end_game:
players = [] players = []
if winner == "monsters": if winner == "monsters":
@ -5353,7 +5370,7 @@ def transition_night(cli):
debuglog(elder, "ELDER DEATH") debuglog(elder, "ELDER DEATH")
if var.FIRST_NIGHT and chk_win(cli, end_game=False): # prevent game from ending as soon as it begins (useful for the random game mode) if var.FIRST_NIGHT and chk_win(cli, end_game=False): # prevent game from ending as soon as it begins (useful for the random game mode)
start(cli, botconfig.NICK, botconfig.CHANNEL, restart=var.CURRENT_GAMEMODE) start(cli, botconfig.NICK, botconfig.CHANNEL, restart=var.CURRENT_GAMEMODE.name)
return return
# game ended from bitten / amnesiac turning, narcolepsy totem expiring, or other weirdness # game ended from bitten / amnesiac turning, narcolepsy totem expiring, or other weirdness
@ -5864,13 +5881,16 @@ def cgamemode(cli, arg):
md = modeargs.pop(0) md = modeargs.pop(0)
try: try:
gm = var.GAME_MODES[md][0](*modeargs) gm = var.GAME_MODES[md][0](*modeargs)
if hasattr(gm, "startup") and callable(gm.startup):
gm.startup()
for attr in dir(gm): for attr in dir(gm):
val = getattr(gm, attr) val = getattr(gm, attr)
if (hasattr(var, attr) and not callable(val) if (hasattr(var, attr) and not callable(val)
and not attr.startswith("_")): and not attr.startswith("_")):
var.ORIGINAL_SETTINGS[attr] = getattr(var, attr) var.ORIGINAL_SETTINGS[attr] = getattr(var, attr)
setattr(var, attr, val) setattr(var, attr, val)
var.CURRENT_GAMEMODE = md gm.name = md
var.CURRENT_GAMEMODE = gm
return True return True
except var.InvalidModeException as e: except var.InvalidModeException as e:
cli.msg(botconfig.CHANNEL, "Invalid mode: "+str(e)) cli.msg(botconfig.CHANNEL, "Invalid mode: "+str(e))
@ -6119,7 +6139,7 @@ def start(cli, nick, chan, forced = False, restart = ""):
if not restart: if not restart:
cli.msg(chan, ("{0}: Welcome to Werewolf, the popular detective/social party "+ cli.msg(chan, ("{0}: Welcome to Werewolf, the popular detective/social party "+
"game (a theme of Mafia). Using the \002{1}\002 game mode.").format(", ".join(pl), var.CURRENT_GAMEMODE)) "game (a theme of Mafia). Using the \002{1}\002 game mode.").format(", ".join(pl), var.CURRENT_GAMEMODE.name))
cli.mode(chan, "+m") cli.mode(chan, "+m")
var.ORIGINAL_ROLES = copy.deepcopy(var.ROLES) # Make a copy var.ORIGINAL_ROLES = copy.deepcopy(var.ROLES) # Make a copy
@ -6876,15 +6896,15 @@ def listroles(cli, nick, chan, rest):
rest = re.split(" +", rest.strip(), 1) rest = re.split(" +", rest.strip(), 1)
#message if this game mode has been disabled #message if this game mode has been disabled
if (not len(rest[0]) or rest[0].isdigit()) and var.GAME_MODES[var.CURRENT_GAMEMODE][4]: if (not len(rest[0]) or rest[0].isdigit()) and var.GAME_MODES[var.CURRENT_GAMEMODE.name][4]:
txt += " {0}: {1}roles was disabled for the {2} game mode.".format(nick, botconfig.CMD_CHAR, var.CURRENT_GAMEMODE) txt += " {0}: {1}roles was disabled for the {2} game mode.".format(nick, botconfig.CMD_CHAR, var.CURRENT_GAMEMODE.name)
rest = [] rest = []
roleindex = {} roleindex = {}
#prepend player count if called without any arguments #prepend player count if called without any arguments
elif not len(rest[0]) and pl > 0: elif not len(rest[0]) and pl > 0:
txt += " {0}: There {1} \u0002{2}\u0002 playing.".format(nick, "is" if pl == 1 else "are", pl) txt += " {0}: There {1} \u0002{2}\u0002 playing.".format(nick, "is" if pl == 1 else "are", pl)
if var.PHASE in ["night", "day"]: if var.PHASE in ["night", "day"]:
txt += " Using the {0} game mode.".format(var.CURRENT_GAMEMODE) txt += " Using the {0} game mode.".format(var.CURRENT_GAMEMODE.name)
#read game mode to get roles for #read game mode to get roles for
elif len(rest[0]) and not rest[0].isdigit(): elif len(rest[0]) and not rest[0].isdigit():
@ -7091,7 +7111,7 @@ def game_stats(cli, nick, chan, rest):
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 return
gamemode = var.CURRENT_GAMEMODE gamemode = var.CURRENT_GAMEMODE.name
gamesize = None gamesize = None
rest = rest.split() rest = rest.split()
# Check for gamemode # Check for gamemode