From e898ee0ec2582cd2cb3bc9091f12cc396e5a717c Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Tue, 14 Mar 2017 20:56:55 -0700 Subject: [PATCH] Add the time-related events and events with parameters --- evennia/contrib/events/commands.py | 2 +- evennia/contrib/events/custom.py | 94 +++++++++++++++++++++++++-- evennia/contrib/events/scripts.py | 73 +++++++++++++++++++-- evennia/contrib/events/typeclasses.py | 13 ++-- 4 files changed, 165 insertions(+), 17 deletions(-) diff --git a/evennia/contrib/events/commands.py b/evennia/contrib/events/commands.py index 42b032aba6..44453f3060 100644 --- a/evennia/contrib/events/commands.py +++ b/evennia/contrib/events/commands.py @@ -292,7 +292,7 @@ class CmdEvent(COMMAND_DEFAULT_CLASS): # Open the editor event = self.handler.add_event(obj, event_name, "", - self.caller, False) + self.caller, False, parameters=self.parameters) self.caller.db._event = event EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save, quitfunc=_ev_quit, key="Event {} of {}".format( diff --git a/evennia/contrib/events/custom.py b/evennia/contrib/events/custom.py index a0fa28475f..e5d86aafc9 100644 --- a/evennia/contrib/events/custom.py +++ b/evennia/contrib/events/custom.py @@ -8,8 +8,13 @@ and are designed to be used more by developers to add event types. from textwrap import dedent +from django.conf import settings from evennia import logger from evennia import ScriptDB +from evennia.contrib.custom_gametime import UNITS +from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu +from evennia.utils.create import create_script +from evennia.utils.gametime import real_seconds_until as standard_rsu hooks = [] event_types = [] @@ -24,7 +29,8 @@ def get_event_handler(): return script -def create_event_type(typeclass, event_name, variables, help_text): +def create_event_type(typeclass, event_name, variables, help_text, + custom_add=None): """ Create a new event type for a specific typeclass. @@ -33,6 +39,8 @@ def create_event_type(typeclass, event_name, variables, help_text): event_name (str): the name of the event to be added. variables (list of str): a list of variable names. help_text (str): a help text of the event. + custom_add (function, default None): a callback to call when adding + the new event. Events obey the inheritance hierarchy: if you set an event on DefaultRoom, for instance, and if your Room typeclass inherits @@ -44,7 +52,8 @@ def create_event_type(typeclass, event_name, variables, help_text): """ typeclass_name = typeclass.__module__ + "." + typeclass.__name__ - event_types.append((typeclass_name, event_name, variables, help_text)) + event_types.append((typeclass_name, event_name, variables, help_text, + custom_add)) def del_event_type(typeclass, event_name): """ @@ -118,7 +127,8 @@ def connect_event_types(): "cannot be found.") return - for typeclass_name, event_name, variables, help_text in event_types: + for typeclass_name, event_name, variables, help_text, \ + custom_add in event_types: # Get the event types for this typeclass if typeclass_name not in script.ndb.event_types: script.ndb.event_types[typeclass_name] = {} @@ -126,4 +136,80 @@ def connect_event_types(): # Add or replace the event help_text = dedent(help_text.strip("\n")) - types[event_name] = (variables, help_text) + types[event_name] = (variables, help_text, custom_add) + +# Custom callbacks for specific events +def get_next_wait(format): + """ + Get the length of time in seconds before format. + + Args: + format (str): a time format matching the set calendar. + + The time format could be something like "2018-01-08 12:00". The + number of units set in the calendar affects the way seconds are + calculated. + + """ + calendar = getattr(settings, "EVENTS_CALENDAR", None) + if calendar is None: + logger.log_err("A time-related event has been set whereas " \ + "the gametime calendar has not been set in the settings.") + return + elif calendar == "standard": + rsu = standard_rsu + units = ["min", "hour", "day", "month", "year"] + elif calendar == "custom": + rsu = custom_rsu + back = dict([(value, name) for name, value in UNITS.items()]) + sorted_units = sorted(back.items()) + del sorted_units[0] + units = [n for v, n in sorted_units] + + params = {} + for delimiter in ("-", ":"): + format = format.replace(delimiter, " ") + + pieces = list(reversed(format.split())) + details = [] + i = 0 + for uname in units: + try: + piece = pieces[i] + except IndexError: + break + + if not piece.isdigit(): + logger.log_err("The time specified '{}' in {} isn't " \ + "a valid number".format(piece, format)) + return + + # Convert the piece to int + piece = int(piece) + params[uname] = piece + details.append("{}={}".format(uname, piece)) + i += 1 + + params["sec"] = 0 + details = " ".join(details) + seconds = rsu(**params) + return seconds, details + +def create_time_event(obj, event_name, number, parameters): + """ + Create an time-related event. + + args: + obj (Object): the object on which stands the event. + event_name (str): the event's name. + number (int): the number of the event. + parameter (str): the parameter of the event. + + """ + print "parameters", repr(parameters) + seconds, key = get_next_wait(parameters) + script = create_script("evennia.contrib.events.scripts.TimeEventScript", interval=seconds, obj=obj) + script.key = key + script.desc = "time event called regularly on {}".format(key) + script.db.time_format = parameters + script.db.number = number diff --git a/evennia/contrib/events/scripts.py b/evennia/contrib/events/scripts.py index 058dc17fd6..3cc18890db 100644 --- a/evennia/contrib/events/scripts.py +++ b/evennia/contrib/events/scripts.py @@ -5,10 +5,12 @@ Scripts for the event system. from datetime import datetime from Queue import Queue -from evennia import DefaultScript +from django.conf import settings +from evennia import DefaultScript, ScriptDB from evennia import logger +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.custom import connect_event_types, patch_hooks from evennia.contrib.events import typeclasses from evennia.utils.utils import all_from_module @@ -64,7 +66,8 @@ class EventHandler(DefaultScript): return types - def add_event(self, obj, event_name, code, author=None, valid=False): + def add_event(self, obj, event_name, code, author=None, valid=False, + parameters=""): """ Add the specified event. @@ -74,6 +77,7 @@ class EventHandler(DefaultScript): 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? + parameters (str, optional): optional parameters. This method doesn't check that the event type exists. @@ -100,6 +104,13 @@ class EventHandler(DefaultScript): if not valid: self.db.to_valid.append((obj, event_name, len(events) - 1)) + # Call the custom_add if needed + custom_add = self.get_event_types(obj).get( + event_name, [None, None, None])[2] + print "custom_add", custom_add + if custom_add: + custom_add(obj, event_name, len(events) - 1, parameters) + # Build the definition to return (a dictionary) definition = dict(events[-1]) definition["obj"] = obj @@ -163,7 +174,7 @@ class EventHandler(DefaultScript): if (obj, event_name, number) in self.db.to_valid: self.db.to_valid.remove((obj, event_name, number)) - def call_event(self, obj, event_name, *args): + def call_event(self, obj, event_name, number=None, *args): """ Call the event. @@ -171,6 +182,7 @@ class EventHandler(DefaultScript): obj (Object): the Evennia typeclassed object. event_name (str): the event name to call. *args: additional variables for this event. + number (int, default None): call just a specific event. Returns: True to report the event was called without interruption, @@ -198,13 +210,64 @@ class EventHandler(DefaultScript): # Now execute all the valid events linked at this address events = self.db.events.get(obj, {}).get(event_name, []) - for event in events: + for i, event in enumerate(events): if not event["valid"]: continue + if number is not None and i != number: + continue + try: exec(event["code"], locals, locals) except InterruptEvent: return False return True + + +# Script to call time-related events +class TimeEventScript(DefaultScript): + + """Gametime-sensitive script.""" + + def at_script_creation(self): + """The script is created.""" + self.start_delay = True + self.persistent = True + + # Script attributes + self.db.time_format = None + self.db.event_name = "time" + self.db.number = None + + def at_repeat(self): + """Call the event and reset interval.""" + # Get the event handler and call the script + try: + script = ScriptDB.objects.get(db_key="event_handler") + except ScriptDB.DoesNotExist: + logger.log_err("Can't get the event handler.") + return + + if self.db.event_name and self.db.number is not None: + obj = self.obj + event_name = self.db.event_name + number = self.db.number + events = script.db.events.get(obj, {}).get(event_name) + if events is None: + logger.log_err("Cannot find the event {} on {}".format( + event_name, obj)) + return + + try: + event = events[number] + except IndexError: + logger.log_err("Cannot find the event {} {} on {}".format( + event_name, number, obj)) + return + + script.call_event(obj, event_name, number, obj) + + if self.db.time_format: + seconds, details = get_next_wait(self.db.time_format) + self.restart(interval=seconds) diff --git a/evennia/contrib/events/typeclasses.py b/evennia/contrib/events/typeclasses.py index cefa1a6b79..286da1ec9f 100644 --- a/evennia/contrib/events/typeclasses.py +++ b/evennia/contrib/events/typeclasses.py @@ -4,7 +4,8 @@ Patched typeclasses for Evennia. from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom from evennia import ScriptDB -from evennia.contrib.events.custom import create_event_type, patch_hook +from evennia.contrib.events.custom import create_event_type, patch_hook, \ + create_time_event from evennia.utils.utils import inherits_from class PatchedExit(object): @@ -31,7 +32,7 @@ class PatchedExit(object): is_character = inherits_from(traversing_object, DefaultCharacter) script = ScriptDB.objects.get(db_key="event_handler") if is_character: - allow = script.call_event(exit, "can_traverse", traversing_object, + allow = script.call_event(exit, "can_traverse", None, traversing_object, exit, exit.location) if not allow: return @@ -40,7 +41,7 @@ class PatchedExit(object): # After traversing if is_character: - script.call_event(exit, "traverse", traversing_object, + script.call_event(exit, "traverse", None, traversing_object, exit, exit.location, exit.destination) @@ -71,7 +72,7 @@ create_event_type(DefaultExit, "traverse", ["character", "exit", """) # Room events -create_event_type(DefaultRoom, "time", ["room", "time"], """ +create_event_type(DefaultRoom, "time", ["room"], """ A repeated event to be called regularly. This event is scheduled to repeat at different times, specified as parameters. You can set it to run every day at 8:00 AM (game @@ -87,6 +88,4 @@ create_event_type(DefaultRoom, "time", ["room", "time"], """ Variables you can use in this event: room: the room connected to this event. - time: a string containing the current time. -""") - +""", create_time_event)