From 4bdee14adb93031392fbdb0640fc7aa2ad36972b Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Sun, 12 Mar 2017 19:26:19 -0700 Subject: [PATCH] Add the /add and /edit switches to the @event command --- evennia/contrib/events/commands.py | 196 +++++++++++++++++++++----- evennia/contrib/events/scripts.py | 53 ++++++- evennia/contrib/events/typeclasses.py | 14 +- 3 files changed, 222 insertions(+), 41 deletions(-) diff --git a/evennia/contrib/events/commands.py b/evennia/contrib/events/commands.py index e180f5f36a..61809a27a7 100644 --- a/evennia/contrib/events/commands.py +++ b/evennia/contrib/events/commands.py @@ -2,12 +2,16 @@ Module containing the commands of the event system. """ +from datetime import datetime + from django.conf import settings from evennia import Command from evennia.contrib.events.extend import get_event_handler +from evennia.utils.eveditor import EvEditor from evennia.utils.evtable import EvTable -from evennia.utils.utils import class_from_module +from evennia.utils.utils import class_from_module, time_format +COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) # Permissions @@ -120,81 +124,203 @@ class CmdEvent(COMMAND_DEFAULT_CLASS): lock = "perm({}) or perm(events_validating)".format(VALIDATING) validator = caller.locks.check_lockstring(caller, lock) - # First and foremost, get the event handler + # First and foremost, get the event handler and set other variables self.handler = get_event_handler() + self.obj = None + rhs = self.rhs or "" + self.event_name, sep, self.parameters = rhs.partition(" ") + self.event_name = self.event_name.lower() + self.is_validator = validator if self.handler is None: caller.msg("The event handler is not running, can't " \ "access the event system.") return # Before the equal sign is always an object name - obj = None if self.args.strip(): - obj = caller.search(self.lhs) - if not obj: + self.obj = caller.search(self.lhs) + if not self.obj: return # Switches are mutually exclusive switch = self.switches and self.switches[0] or "" if switch == "": - if not obj: + if not self.obj: caller.msg("Specify an object's name or #ID.") return - self.list_events(obj) + self.list_events() elif switch == "add": - if not obj: + if not self.obj: caller.msg("Specify an object's name or #ID.") return - self.add_event(obj) + self.add_event() elif switch == "edit": - if not obj: + if not self.obj: caller.msg("Specify an object's name or #ID.") return - self.edit_event(obj) + self.edit_event() elif switch == "del": - if not obj: + if not self.obj: caller.msg("Specify an object's name or #ID.") return - self.del_event(obj) + self.del_event() elif switch == "accept" and validator: - self.accept_event(obj) + self.accept_event() else: caller.msg("Mutually exclusive or invalid switches were " \ "used, cannot proceed.") - def list_events(self, obj): + def list_events(self): """Display the list of events connected to the object.""" + obj = self.obj + event_name = self.event_name events = self.handler.get_events(obj) types = self.handler.get_event_types(obj) - table = EvTable("Event name", "Number", "Lines", "Description", - width=78) - for name, infos in sorted(types.items()): - number = len(events.get(name, [])) - lines = sum(len(e["code"].splitlines()) for e in \ - events.get(name, [])) - description = infos[1].splitlines()[0] - table.add_row(name, number, lines, description) - table.reformat_column(1, align="r") - table.reformat_column(2, align="r") - self.msg(table) + if event_name: + # Check that the event name can be found in this object + created = events.get(event_name) + if created is None: + self.msg("No event {} has been set on {}.".format(event_name, obj)) + return - def add_event(self, obj): + # Create the table + cols = ["Number", "Author", "Updated"] + if self.is_validator: + cols.append("Valid") + + table = EvTable(*cols, width=78) + now = datetime.now() + for i, event in enumerate(created): + author = event.get("author") + author = author.key if author else "|gUnknown|n" + updated_on = event.get("updated_on") + if updated_on is None: + updated_on = event.get("created_on") + + if updated_on: + updated_on = time_format( + (now - updated_on).total_seconds(), 1) + else: + updated_on = "|gUnknown|n" + + row = [str(i + 1), author, updated_on] + if self.is_validator: + row.append("Yes" if event.get("valid") else "no") + table.add_row(*row) + + table.reformat_column(0, align="r") + self.msg(table) + else: + table = EvTable("Event name", "Number", "Lines", "Description", + width=78) + for name, infos in sorted(types.items()): + number = len(events.get(name, [])) + lines = sum(len(e["code"].splitlines()) for e in \ + events.get(name, [])) + description = infos[1].splitlines()[0] + table.add_row(name, number, lines, description) + + table.reformat_column(1, align="r") + table.reformat_column(2, align="r") + self.msg(table) + + def add_event(self): """Add an event.""" - self.msg("Calling add.") + obj = self.obj + event_name = self.event_name + types = self.handler.get_event_types(obj) - def edit_event(self, obj): - """Add an event.""" - self.msg("Calling edit.") + # Check that the event exists + if not event_name in types: + self.msg("The event name {} can't be found in {} of " \ + "typeclass {}.".format(event_name, obj, type(obj))) + return - def del_event(self, obj): - """Add an event.""" + definition = types[event_name] + description = definition[1] + self.msg(description) + + # Open the editor + event = self.handler.add_event(obj, event_name, "", + self.caller, False) + self.caller.db._event = event + EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save, + quitfunc=_ev_quit, key="Event {} of {}".format( + event_name, obj), persistent=True, codefunc=_ev_save) + + def edit_event(self): + """Edit an event.""" + obj = self.obj + event_name = self.event_name + parameters = self.parameters + events = self.handler.get_events(obj) + types = self.handler.get_event_types(obj) + + # Check that the event exists + if not event_name in events: + self.msg("The event name {} can't be found in {}.".format( + event_name, obj)) + return + + # Check that the parameter points to an existing event + try: + parameters = int(parameters) - 1 + assert parameters >= 0 + event = events[event_name][parameters] + except (AssertionError, ValueError): + self.msg("The event {} {} cannot be found in {}.".format( + event_name, parameters, obj)) + return + + definition = types[event_name] + description = definition[1] + self.msg(description) + + # Open the editor + event = dict(event) + event["obj"] = obj + event["name"] = event_name + event["number"] = parameters + self.caller.db._event = event + EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save, + quitfunc=_ev_quit, key="Event {} of {}".format( + event_name, obj), persistent=True, codefunc=_ev_save) + + def del_event(self): + """Delete an event.""" + obj = self.obj self.msg("Calling del.") - def accept_event(self, obj): - """Add an event.""" + def accept_event(self): + """Accept an event.""" + obj = self.obj self.msg("Calling accept.") + +# Private functions to handle editing +def _ev_load(caller): + return caller.db._event and caller.db._event.get("code", "") or "" + +def _ev_save(caller, buf): + """Save and add the event.""" + lock = "perm({}) or perm(events_without_validation)".format( + WITHOUT_VALIDATION) + autovalid = caller.locks.check_lockstring(caller, lock) + event = caller.db._event + handler = get_event_handler() + if not handler or not event or not all(key in event for key in \ + ("obj", "name", "number", "valid")): + caller.msg("Couldn't save this event.") + return False + + handler.edit_event(event["obj"], event["name"], event["number"], buf, + caller, valid=autovalid) + return True + +def _ev_quit(caller): + del caller.db._event + caller.msg("Exited the code editor.") diff --git a/evennia/contrib/events/scripts.py b/evennia/contrib/events/scripts.py index 2ad087e3a7..46a50e1feb 100644 --- a/evennia/contrib/events/scripts.py +++ b/evennia/contrib/events/scripts.py @@ -7,8 +7,10 @@ from Queue import Queue from evennia import DefaultScript from evennia import logger +from evennia.contrib.events.exceptions import InterruptEvent from evennia.contrib.events.extend import patch_hooks from evennia.contrib.events import typeclasses +from evennia.utils.utils import all_from_module class EventHandler(DefaultScript): @@ -60,7 +62,7 @@ class EventHandler(DefaultScript): return types - def add_event(self, obj, event_name, code, author=None, valid=True): + def add_event(self, obj, event_name, code, author=None, valid=False): """ Add the specified event. @@ -92,6 +94,46 @@ class EventHandler(DefaultScript): "code": code, }) + definition = dict(events[-1]) + definition["obj"] = obj + definition["name"] = event_name + definition["number"] = len(events) - 1 + return definition + + def edit_event(self, obj, event_name, number, code, author=None, + valid=False): + """ + Edit the specified event. + + Args: + obj (Object): the Evennia typeclassed object to be modified. + event_name (str): the name of the event to add. + number (int): the event number to be changed. + code (str): the Python code associated with this event. + author (optional, Character, Player): the author of the event. + valid (optional, bool): should the event be connected? + + This method doesn't check that the event type exists. + + """ + obj_events = self.db.events.get(obj, {}) + if not obj_events: + self.db.events[obj] = {} + obj_events = self.db.events[obj] + + events = obj_events.get(event_name, []) + if not events: + obj_events[event_name] = [] + events = obj_events[event_name] + + # Edit the event + events[number].update({ + "updated_on": datetime.now(), + "updated_by": author, + "valid": valid, + "code": code, + }) + def call_event(self, obj, event_name, *args): """ Call the event. @@ -115,7 +157,7 @@ class EventHandler(DefaultScript): return False # Prepare the locals - locals = {} + locals = all_from_module("evennia.contrib.events.helpers") for i, variable in enumerate(event_type[0]): try: locals[variable] = args[i] @@ -131,4 +173,9 @@ class EventHandler(DefaultScript): if not event["valid"]: continue - exec(event["code"], locals, locals) + try: + exec(event["code"], locals, locals) + except InterruptEvent: + return False + + return True diff --git a/evennia/contrib/events/typeclasses.py b/evennia/contrib/events/typeclasses.py index 94ed67e764..c66df484f2 100644 --- a/evennia/contrib/events/typeclasses.py +++ b/evennia/contrib/events/typeclasses.py @@ -30,11 +30,19 @@ class PatchedExit(object): """ if inherits_from(traversing_object, DefaultCharacter): script = ScriptDB.objects.get(db_key="event_handler") - script.call_event(exit, "at_traverse", traversing_object, + allow = script.call_event(exit, "can_traverse", traversing_object, exit, exit.location) + if not allow: + return hook(exit, traversing_object, target_location) # Default events -create_event_type(DefaultExit, "at_traverse", ["character", "exit", "room"], - """When traversing""") +create_event_type(DefaultExit, "can_traverse", ["character", "exit", "room"], + """Can the character traverse through this exit? + This event is called when a character is about to traverse this + exit. You can use the deny() function to deny the character from + using this exit for the time being. The 'character' variable + contains the character who wants to traverse through this exit. + The 'exit' variable contains the exit, the 'room' variable + contains the room in which the character and exit are.""")