diff --git a/evennia/contrib/events/custom.py b/evennia/contrib/events/custom.py index 7c35c5b2ea..5a66c63690 100644 --- a/evennia/contrib/events/custom.py +++ b/evennia/contrib/events/custom.py @@ -67,7 +67,7 @@ def patch_hook(typeclass, method_name): inheritance tree for a couple of methods. """ - hook = getattr(typeclass, method_name) + hook = getattr(typeclass, method_name, None) def wrapper(method): """Wrapper around the hook.""" def overridden_hook(*args, **kwargs): diff --git a/evennia/contrib/events/handler.py b/evennia/contrib/events/handler.py new file mode 100644 index 0000000000..8d7f06e33d --- /dev/null +++ b/evennia/contrib/events/handler.py @@ -0,0 +1,182 @@ +""" +Module containing the EventHandler for individual objects. +""" + +from collections import namedtuple + +class EventsHandler(object): + + """ + The event handler for a specific object. + + The script that contains all events will be reached through this + handler. This handler is therefore a shortcut to be used by + developers. This handler (accessible through `obj.events`) is a + shortcut to manipulating events within this object, getting, + adding, editing, deleting and calling them. + + """ + + script = None + + def __init__(self, obj): + self.obj = obj + + def all(self): + """ + Return all events linked to this object. + + Returns: + All events in a dictionary event_name: event}. The event + is returned as a namedtuple to simply manipulation. + + """ + events = {} + handler = type(self).script + if handler: + dicts = handler.get_events(self.obj) + for event_name, in_list in dicts.items(): + new_list = [] + for event in in_list: + event = self.format_event(event) + new_list.append(event) + + if new_list: + events[event_name] = new_list + + return events + + def get(self, event_name): + """ + Return the events associated with this name. + + Args: + event_name (str): the name of the event. + + This method returns a list of Event objects (namedtuple + representations). If the event name cannot be found in the + object's events, return an empty list. + + """ + return self.all().get(event_name, []) + + def add(self, event_name, code, author=None, valid=False, parameters=""): + """ + Add a new event for this object. + + Args: + event_name (str): the name of the event to add. + code (str): the Python code associated with this event. + author (Character or Player, optional): the author of the event. + valid (bool, optional): should the event be connected? + parameters (str, optional): optional parameters. + + Returns: + The event definition that was added or None. + + """ + handler = type(self).script + if handler: + return self.format_event(handler.add_event(self.obj, event_name, code, + author=author, valid=valid, parameters=parameters)) + + def edit(self, event_name, number, code, author=None, valid=False): + """ + Edit an existing event bound to this object. + + Args: + event_name (str): the name of the event to edit. + number (int): the event number to be changed. + code (str): the Python code associated with this event. + author (Character or Player, optional): the author of the event. + valid (bool, optional): should the event be connected? + + Returns: + The event definition that was edited or None. + + Raises: + RuntimeError if the event is locked. + + """ + handler = type(self).script + if handler: + return self.format_event(handler.edit_event(self.obj, event_name, + number, code, author=author, valid=valid)) + + def remove(self, event_name, number): + """ + Delete the specified event bound to this object. + + Args: + event_name (str): the name of the event to delete. + number (int): the number of the event to delete. + + Raises: + RuntimeError if the event is locked. + + """ + handler = type(self).script + if handler: + handler.del_event(self.obj, event_name, number) + + def call(self, event_name, *args, **kwargs): + """ + Call the specified event(s) bound to this object. + + Args: + event_name (str): the event name to call. + *args: additional variables for this event. + + Kwargs: + number (int, optional): call just a specific event. + parameters (str, optional): call an event with parameters. + locals (dict, optional): a locals replacement. + + Returns: + True to report the event was called without interruption, + False otherwise. + + """ + handler = type(self).script + if handler: + return handler.call_event(self.obj, event_name, *args, **kwargs) + + return False + + @staticmethod + def format_event(event): + """ + Return the Event namedtuple to represent the specified event. + + Args: + event (dict): the event definition. + + The event given in argument should be a dictionary containing + the expected fields for an event (code, author, valid...). + + """ + if "obj" not in event: + event["obj"] = None + if "name" not in event: + event["name"] = "unknown" + if "number" not in event: + event["number"] = -1 + if "code" not in event: + event["code"] = "" + if "author" not in event: + event["author"] = None + if "valid" not in event: + event["valid"] = False + if "parameters" not in event: + event["parameters"] = "" + if "created_on" not in event: + event["created_on"] = None + if "updated_by" not in event: + event["updated_by"] = None + if "updated_on" not in event: + event["updated_on"] = None + + return Event(**event) + +Event = namedtuple("Event", ("obj", "name", "number", "code", "author", + "valid", "parameters", "created_on", "updated_by", "updated_on")) diff --git a/evennia/contrib/events/scripts.py b/evennia/contrib/events/scripts.py index d0abdc1dc2..c3f34e4627 100644 --- a/evennia/contrib/events/scripts.py +++ b/evennia/contrib/events/scripts.py @@ -6,13 +6,14 @@ from datetime import datetime, timedelta from Queue import Queue from django.conf import settings -from evennia import DefaultScript, ScriptDB +from evennia import DefaultObject, DefaultScript, ScriptDB from evennia import logger from evennia.utils.dbserialize import dbserialize from evennia.utils.utils import all_from_module, delay -from evennia.contrib.events.custom import connect_event_types, \ - get_next_wait, patch_hooks +from evennia.contrib.events.custom import ( + connect_event_types, get_next_wait, patch_hooks) from evennia.contrib.events.exceptions import InterruptEvent +from evennia.contrib.events.handler import EventsHandler as Handler from evennia.contrib.events import typeclasses class EventHandler(DefaultScript): @@ -65,6 +66,10 @@ class EventHandler(DefaultScript): delay(seconds, complete_task, task_id) + # Place the script in the EventsHandler + Handler.script = self + DefaultObject.events = typeclasses.PatchedObject.events + def get_events(self, obj): """ Return a dictionary of the object's events. @@ -80,7 +85,21 @@ class EventHandler(DefaultScript): when several objects would share events. """ - return self.db.events.get(obj, {}) + obj_events = self.db.events.get(obj, {}) + events = {} + for event_name, event_list in obj_events.items(): + new_list = [] + for i, event in enumerate(event_list): + event = dict(event) + event["obj"] = obj + event["name"] = event_name + event["number"] = i + new_list.append(event) + + if new_list: + events[event_name] = new_list + + return events def get_event_types(self, obj): """ @@ -212,6 +231,13 @@ class EventHandler(DefaultScript): elif valid and (obj, event_name, number) in self.db.to_valid: self.db.to_valid.remove((obj, event_name, number)) + # Build the definition to return (a dictionary) + definition = dict(events[number]) + definition["obj"] = obj + definition["name"] = event_name + definition["number"] = number + return definition + def del_event(self, obj, event_name, number): """ Delete the specified event. @@ -259,11 +285,11 @@ class EventHandler(DefaultScript): i += 1 # Update locked event - for line in self.db.locked: + for i, line in enumerate(self.db.locked): t_obj, t_event_name, t_number = line if obj is t_obj and event_name == t_event_name: - if number > t_number: - line[2] -= 1 + if number < t_number: + self.db.locked[i] = (t_obj, t_event_name, t_number - 1) # Delete time-related events associated with this object for script in list(obj.scripts.all()): diff --git a/evennia/contrib/events/typeclasses.py b/evennia/contrib/events/typeclasses.py index 191d66f641..016c879927 100644 --- a/evennia/contrib/events/typeclasses.py +++ b/evennia/contrib/events/typeclasses.py @@ -4,9 +4,16 @@ Patched typeclasses for Evennia. from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom from evennia import ScriptDB -from evennia.utils.utils import inherits_from -from evennia.contrib.events.custom import create_event_type, patch_hook, \ - create_time_event +from evennia.utils.utils import inherits_from, lazy_property +from evennia.contrib.events.custom import ( + create_event_type, patch_hook, create_time_event) +from evennia.contrib.events.handler import EventsHandler + +class PatchedObject(object): + @lazy_property + def events(self): + """Return the EventsHandler.""" + return EventsHandler(self) class PatchedExit(object):