Bring the event system to Evennia-style compliance

This commit is contained in:
Vincent Le Goff 2017-03-20 12:29:04 -07:00 committed by Griatch
parent 629ac73f2b
commit 81f4c590bd
5 changed files with 174 additions and 171 deletions

View file

@ -6,12 +6,12 @@ from datetime import datetime
from django.conf import settings from django.conf import settings
from evennia import Command from evennia import Command
from evennia.contrib.events.custom import get_event_handler from evennia.utils.ansi import raw
from evennia.utils.eveditor import EvEditor from evennia.utils.eveditor import EvEditor
from evennia.utils.evtable import EvTable from evennia.utils.evtable import EvTable
from evennia.utils.utils import class_from_module, time_format from evennia.utils.utils import class_from_module, time_format
from evennia.contrib.events.custom import get_event_handler
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
# Permissions # Permissions
@ -20,26 +20,26 @@ WITHOUT_VALIDATION = getattr(settings, "EVENTS_WITHOUT_VALIDATION",
"immortals") "immortals")
VALIDATING = getattr(settings, "EVENTS_VALIDATING", "immortals") VALIDATING = getattr(settings, "EVENTS_VALIDATING", "immortals")
# Split help file # Split help text
BASIC_HELP = "Add, edit or delete events." BASIC_HELP = "Add, edit or delete events."
BASIC_USAGES = [ BASIC_USAGES = [
"@event object name [= event name]", "@event <object name> [= <event name>]",
"@event/add object name = event name [parameters]", "@event/add <object name> = <event name> [parameters]",
"@event/edit object name = event name [event number]", "@event/edit <object name> = <event name> [event number]",
"@event/del object name = event name [event number]", "@event/del <object name> = <event name> [event number]",
"@event/tasks [object name [= event name [event number]]]", "@event/tasks [object name [= <event name>]]",
] ]
BASIC_SWITCHES = [ BASIC_SWITCHES = [
"add - add and edit a new event", "add - add and edit a new event",
"edit - edit an existing event", "edit - edit an existing event",
"del - delete an existing event", "del - delete an existing event",
"tasks - show the list of differed tasks", "tasks - show the list of differed tasks",
] ]
VALIDATOR_USAGES = [ VALIDATOR_USAGES = [
"@event/accept [object name = event name [event number]]", "@event/accept [object name = <event name> [event number]]",
] ]
VALIDATOR_SWITCHES = [ VALIDATOR_SWITCHES = [
@ -50,12 +50,12 @@ BASIC_TEXT = """
This command is used to manipulate events. An event can be linked to This command is used to manipulate events. An event can be linked to
an object, to fire at a specific moment. You can use the command without an object, to fire at a specific moment. You can use the command without
switches to see what event are active on an object: switches to see what event are active on an object:
@event self @event self
You can also specify an event name if you want the list of events associated You can also specify an event name if you want the list of events associated
with this object of this name: with this object of this name:
@event north = can_traverse @event north = can_traverse
You might need to specify a number after the event if there are more than one: You can also add a number after the event name to see details on one event:
@event here = say 2 @event here = say 2
You can also add, edit or remove events using the add, edit or del switches. You can also add, edit or remove events using the add, edit or del switches.
Additionally, you can see the list of differed tasks created by events Additionally, you can see the list of differed tasks created by events
(chained events to be called) using the /tasks switch. (chained events to be called) using the /tasks switch.
@ -66,27 +66,26 @@ You can also use this command to validate events. Depending on your game
setting, some users might be allowed to add new events, but these events setting, some users might be allowed to add new events, but these events
will not be fired until you accept them. To see the events needing will not be fired until you accept them. To see the events needing
validation, enter the /accept switch without argument: validation, enter the /accept switch without argument:
@event/accept @event/accept
A table will show you the events that are not validated yet, who created A table will show you the events that are not validated yet, who created
it and when. You can then accept a specific event: them and when. You can then accept a specific event:
@event here = enter @event here = enter 1
Or, if more than one events are connected here, specify the number:
@event here = enter 3
Use the /del switch to remove events that should not be connected. Use the /del switch to remove events that should not be connected.
""" """
class CmdEvent(COMMAND_DEFAULT_CLASS): class CmdEvent(COMMAND_DEFAULT_CLASS):
"""Command to edit events.""" """
Command to edit events.
"""
key = "@event" key = "@event"
locks = "cmd:perm({})".format(VALIDATING)
aliases = ["@events", "@ev"] aliases = ["@events", "@ev"]
locks = "cmd:perm({})".format(VALIDATING)
if WITH_VALIDATION: if WITH_VALIDATION:
locks += " or perm({})".format(WITH_VALIDATION) locks += " or perm({})".format(WITH_VALIDATION)
help_category = "Building" help_category = "Building"
def get_help(self, caller, cmdset): def get_help(self, caller, cmdset):
""" """
Return the help message for this command and this caller. Return the help message for this command and this caller.
@ -104,18 +103,18 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
""" """
lock = "perm({}) or perm(events_validating)".format(VALIDATING) lock = "perm({}) or perm(events_validating)".format(VALIDATING)
validator = caller.locks.check_lockstring(caller, lock) validator = caller.locks.check_lockstring(caller, lock)
text = "\n" + BASIC_HELP + "\n\nUsages:\n " text = "\n" + BASIC_HELP + "\n\nUsages:\n "
# Usages # Usages
text += "\n ".join(BASIC_USAGES) text += "\n ".join(BASIC_USAGES)
if validator: if validator:
text += "\n " + "\n ".join(VALIDATOR_USAGES) text += "\n " + "\n ".join(VALIDATOR_USAGES)
# Switches # Switches
text += "\n\nSwitches:\n " text += "\n\nSwitches:\n "
text += "\n ".join(BASIC_SWITCHES) text += "\n ".join(BASIC_SWITCHES)
if validator: if validator:
text += "\n " + "\n".join(VALIDATOR_SWITCHES) text += "\n " + "\n ".join(VALIDATOR_SWITCHES)
# Text # Text
text += "\n" + BASIC_TEXT text += "\n" + BASIC_TEXT
@ -146,37 +145,25 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
"access the event system.") "access the event system.")
return return
# Before the equal sign is always an object name # Before the equal sign, there is an object name or nothing
if self.args.strip(): if self.lhs:
self.obj = caller.search(self.lhs) self.obj = caller.search(self.lhs)
if not self.obj: if not self.obj:
return return
# Switches are mutually exclusive # Switches are mutually exclusive
switch = self.switches and self.switches[0] or "" switch = self.switches and self.switches[0] or ""
if switch == "": if switch in ("", "add", "edit", "del") and self.obj is None:
if not self.obj: caller.msg("Specify an object's name or #ID.")
caller.msg("Specify an object's name or #ID.") return
return
if switch == "":
self.list_events() self.list_events()
elif switch == "add": elif switch == "add":
if not self.obj:
caller.msg("Specify an object's name or #ID.")
return
self.add_event() self.add_event()
elif switch == "edit": elif switch == "edit":
if not self.obj:
caller.msg("Specify an object's name or #ID.")
return
self.edit_event() self.edit_event()
elif switch == "del": elif switch == "del":
if not self.obj:
caller.msg("Specify an object's name or #ID.")
return
self.del_event() self.del_event()
elif switch == "accept" and validator: elif switch == "accept" and validator:
self.accept_event() self.accept_event()
@ -198,16 +185,17 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
# Check that the event name can be found in this object # Check that the event name can be found in this object
created = events.get(event_name) created = events.get(event_name)
if created is None: if created is None:
self.msg("No event {} has been set on {}.".format(event_name, obj)) self.msg("No event {} has been set on {}.".format(event_name,
obj))
return return
if parameters: if parameters:
# Check that the parameter points to an existing event # Check that the parameter points to an existing event
try: try:
parameters = int(parameters) - 1 number = int(parameters) - 1
assert parameters >= 0 assert number >= 0
event = events[event_name][parameters] event = events[event_name][number]
except (AssertionError, ValueError): except (ValueError, AssertionError, IndexError):
self.msg("The event {} {} cannot be found in {}.".format( self.msg("The event {} {} cannot be found in {}.".format(
event_name, parameters, obj)) event_name, parameters, obj))
return return
@ -223,10 +211,9 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
updated_on = event.get("updated_on") updated_on = event.get("updated_on")
updated_on = updated_on.strftime("%Y-%m-%d %H:%M:%S") \ updated_on = updated_on.strftime("%Y-%m-%d %H:%M:%S") \
if updated_on else "|gUnknown|n" if updated_on else "|gUnknown|n"
number = parameters + 1 msg = "Event {} {} of {}:".format(event_name, parameters, obj)
msg = "Event {} {} of {}:".format(event_name, number, obj) msg += "\nCreated by {} on {}.".format(author, created_on)
msg += "\nCreated by {} at {}.".format(author, created_on) msg += "\nUpdated by {} on {}".format(updated_by, updated_on)
msg += "\nUpdated by {} at {}".format(updated_by, updated_on)
if self.is_validator: if self.is_validator:
if event.get("valid"): if event.get("valid"):
@ -235,7 +222,7 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
msg += "\nThis event |rhasn't been|n accepted yet." msg += "\nThis event |rhasn't been|n accepted yet."
msg += "\nEvent code:\n" msg += "\nEvent code:\n"
msg += "\n".join([l for l in event["code"].splitlines()]) msg += raw(event["code"])
self.msg(msg) self.msg(msg)
return return
@ -255,8 +242,9 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
updated_on = event.get("created_on") updated_on = event.get("created_on")
if updated_on: if updated_on:
updated_on = time_format( updated_on = "{} ago".format(time_format(
(now - updated_on).total_seconds(), 1) (now - updated_on).total_seconds(),
4).capitalize())
else: else:
updated_on = "|gUnknown|n" updated_on = "|gUnknown|n"
parameters = event.get("parameters", "") parameters = event.get("parameters", "")
@ -330,7 +318,7 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
# If there's only one event, just edit it # If there's only one event, just edit it
if len(events[event_name]) == 1: if len(events[event_name]) == 1:
parameters = 0 number = 0
event = events[event_name][0] event = events[event_name][0]
else: else:
if not parameters: if not parameters:
@ -340,10 +328,10 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
# Check that the parameter points to an existing event # Check that the parameter points to an existing event
try: try:
parameters = int(parameters) - 1 number = int(parameters) - 1
assert parameters >= 0 assert number >= 0
event = events[event_name][parameters] event = events[event_name][number]
except (AssertionError, ValueError): except (ValueError, AssertionError, IndexError):
self.msg("The event {} {} cannot be found in {}.".format( self.msg("The event {} {} cannot be found in {}.".format(
event_name, parameters, obj)) event_name, parameters, obj))
return return
@ -355,10 +343,10 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
return return
# If the event is locked (edited by someone else) # If the event is locked (edited by someone else)
if (obj, event_name, parameters) in self.handler.db.locked: if (obj, event_name, number) in self.handler.db.locked:
self.msg("This event is locked, you cannot edit it.") self.msg("This event is locked, you cannot edit it.")
return return
self.handler.db.locked.append((obj, event_name, parameters)) self.handler.db.locked.append((obj, event_name, number))
# Check the definition of the event # Check the definition of the event
definition = types.get(event_name, (None, "Chained event")) definition = types.get(event_name, (None, "Chained event"))
@ -369,7 +357,7 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
event = dict(event) event = dict(event)
event["obj"] = obj event["obj"] = obj
event["name"] = event_name event["name"] = event_name
event["number"] = parameters event["number"] = number
self.caller.db._event = event self.caller.db._event = event
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save, EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save,
quitfunc=_ev_quit, key="Event {} of {}".format( quitfunc=_ev_quit, key="Event {} of {}".format(
@ -396,7 +384,7 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
# If there's only one event, just delete it # If there's only one event, just delete it
if len(events[event_name]) == 1: if len(events[event_name]) == 1:
parameters = 0 number = 0
event = events[event_name][0] event = events[event_name][0]
else: else:
if not parameters: if not parameters:
@ -407,10 +395,10 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
# Check that the parameter points to an existing event # Check that the parameter points to an existing event
try: try:
parameters = int(parameters) - 1 number = int(parameters) - 1
assert parameters >= 0 assert number >= 0
event = events[event_name][parameters] event = events[event_name][number]
except (AssertionError, ValueError): except (ValueError, AssertionError, IndexError):
self.msg("The event {} {} cannot be found in {}.".format( self.msg("The event {} {} cannot be found in {}.".format(
event_name, parameters, obj)) event_name, parameters, obj))
return return
@ -422,14 +410,14 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
return return
# If the event is locked (edited by someone else) # If the event is locked (edited by someone else)
if (obj, event_name, parameters) in self.handler.db.locked: if (obj, event_name, number) in self.handler.db.locked:
self.msg("This event is locked, you cannot delete it.") self.msg("This event is locked, you cannot delete it.")
return return
# Delete the event # Delete the event
self.handler.del_event(obj, event_name, parameters) self.handler.del_event(obj, event_name, number)
self.msg("The event {} {} of {} was deleted.".format( self.msg("The event {} {} of {} was deleted.".format(
obj, event_name, parameters + 1)) obj, event_name, parameters))
def accept_event(self): def accept_event(self):
"""Accept an event.""" """Accept an event."""
@ -461,8 +449,9 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
updated_on = event.get("created_on") updated_on = event.get("created_on")
if updated_on: if updated_on:
updated_on = time_format( updated_on = "{} ago".format(time_format(
(now - updated_on).total_seconds(), 1) (now - updated_on).total_seconds(),
4).capitalize())
else: else:
updated_on = "|gUnknown|n" updated_on = "|gUnknown|n"
@ -492,10 +481,10 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
# Check that the parameter points to an existing event # Check that the parameter points to an existing event
try: try:
parameters = int(parameters) - 1 number = int(parameters) - 1
assert parameters >= 0 assert number >= 0
event = events[event_name][parameters] event = events[event_name][number]
except (AssertionError, ValueError): except (ValueError, AssertionError, IndexError):
self.msg("The event {} {} cannot be found in {}.".format( self.msg("The event {} {} cannot be found in {}.".format(
event_name, parameters, obj)) event_name, parameters, obj))
return return
@ -504,7 +493,7 @@ class CmdEvent(COMMAND_DEFAULT_CLASS):
if event["valid"]: if event["valid"]:
self.msg("This event has already been accepted.") self.msg("This event has already been accepted.")
else: else:
self.handler.accept_event(obj, event_name, parameters) self.handler.accept_event(obj, event_name, number)
self.msg("The event {} {} of {} has been accepted.".format( self.msg("The event {} {} of {} has been accepted.".format(
event_name, parameters, obj)) event_name, parameters, obj))

View file

@ -11,10 +11,10 @@ from textwrap import dedent
from django.conf import settings from django.conf import settings
from evennia import logger from evennia import logger
from evennia import ScriptDB 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.create import create_script
from evennia.utils.gametime import real_seconds_until as standard_rsu from evennia.utils.gametime import real_seconds_until as standard_rsu
from evennia.contrib.custom_gametime import UNITS
from evennia.contrib.custom_gametime import real_seconds_until as custom_rsu
hooks = [] hooks = []
event_types = [] event_types = []
@ -24,7 +24,7 @@ def get_event_handler():
try: try:
script = ScriptDB.objects.get(db_key="event_handler") script = ScriptDB.objects.get(db_key="event_handler")
except ScriptDB.DoesNotExist: except ScriptDB.DoesNotExist:
logger.log_err("Can't get the event handler.") logger.log_trace("Can't get the event handler.")
script = None script = None
return script return script
@ -39,10 +39,10 @@ def create_event_type(typeclass, event_name, variables, help_text,
event_name (str): the name of the event to be added. event_name (str): the name of the event to be added.
variables (list of str): a list of variable names. variables (list of str): a list of variable names.
help_text (str): a help text of the event. help_text (str): a help text of the event.
custom_add (function, default None): a callback to call when adding custom_add (function, optional): a callback to call when adding
the new event. the new event.
custom_xcall (function, default None): a callback to call when custom_call (function, optional): a callback to call when
preparing to call the events. preparing to call the event.
Events obey the inheritance hierarchy: if you set an event on Events obey the inheritance hierarchy: if you set an event on
DefaultRoom, for instance, and if your Room typeclass inherits DefaultRoom, for instance, and if your Room typeclass inherits
@ -50,42 +50,23 @@ def create_event_type(typeclass, event_name, variables, help_text,
all rooms. Objects of the typeclass set in argument will be all rooms. Objects of the typeclass set in argument will be
able to set one or more events of that name. able to set one or more events of that name.
If the event already exists in the typeclass, replace it. If the event type already exists in the typeclass, replace it.
""" """
typeclass_name = typeclass.__module__ + "." + typeclass.__name__ 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, custom_call)) custom_add, custom_call))
def del_event_type(typeclass, event_name):
"""
Delete the event type for this typeclass.
Args:
typeclass (type): the class defining the typeclass.
event_name (str): the name of the event to be deleted.
If you want to delete an event type, you need to remove it from
the typeclass that defined it: other typeclasses in the inheritance
hierarchy are not affected. This method doesn't remove the
already-created events associated with individual objects.
"""
typeclass_name = typeclass.__module__ + "." + typeclass.__name__
try:
script = ScriptDB.objects.get(db_key="event_handler")
except ScriptDB.DoesNotExist:
logger.log_err("Can't create event {} in typeclass {}, the " \
"script handler isn't defined".format(name, typeclass_name))
return
# Get the event types for this typeclass
event_types = script.ndb.event_types.get(typeclass_name, {})
if event_name in event_types:
del event_types[event_name]
def patch_hook(typeclass, method_name): def patch_hook(typeclass, method_name):
"""Decorator to softly patch a hook in a typeclass.""" """
Decorator to softly patch a hook in a typeclass.
This decorator should not be used, unless for good reasons, outside
of this contrib. The advantage of using decorated soft patchs is
in allowing users to customize typeclasses without changing the
inheritance tree for a couple of methods.
"""
hook = getattr(typeclass, method_name) hook = getattr(typeclass, method_name)
def wrapper(method): def wrapper(method):
"""Wrapper around the hook.""" """Wrapper around the hook."""
@ -119,13 +100,14 @@ def connect_event_types():
Connect the event types when the script runs. Connect the event types when the script runs.
This method should be called automatically by the event handler This method should be called automatically by the event handler
(the script). (the script). It might be useful, however, to call it after adding
new event types in typeclasses.
""" """
try: try:
script = ScriptDB.objects.get(db_key="event_handler") script = ScriptDB.objects.get(db_key="event_handler")
except ScriptDB.DoesNotExist: except ScriptDB.DoesNotExist:
logger.log_err("Can't connect event types, the event handler " \ logger.log_trace("Can't connect event types, the event handler " \
"cannot be found.") "cannot be found.")
return return
@ -146,7 +128,7 @@ def connect_event_types():
types[event_name] = (variables, help_text, custom_add, custom_call) types[event_name] = (variables, help_text, custom_add, custom_call)
del event_types[0] del event_types[0]
# Custom callbacks for specific events # Custom callbacks for specific event types
def get_next_wait(format): def get_next_wait(format):
""" """
Get the length of time in seconds before format. Get the length of time in seconds before format.
@ -188,7 +170,7 @@ def get_next_wait(format):
break break
if not piece.isdigit(): if not piece.isdigit():
logger.log_err("The time specified '{}' in {} isn't " \ logger.log_trace("The time specified '{}' in {} isn't " \
"a valid number".format(piece, format)) "a valid number".format(piece, format))
return return
@ -205,13 +187,13 @@ def get_next_wait(format):
def create_time_event(obj, event_name, number, parameters): def create_time_event(obj, event_name, number, parameters):
""" """
Create an time-related event. Create a time-related event.
args: Args:
obj (Object): the object on which stands the event. obj (Object): the object on which stands the event.
event_name (str): the event's name. event_name (str): the event's name.
number (int): the number of the event. number (int): the number of the event.
parameter (str): the parameter of the event. parameters (str): the parameter of the event.
""" """
seconds, key = get_next_wait(parameters) seconds, key = get_next_wait(parameters)
@ -229,7 +211,7 @@ def keyword_event(events, parameters):
parameter to add the event type when the event supports keywords parameter to add the event type when the event supports keywords
as parameters. Keywords in parameters are one or more words as parameters. Keywords in parameters are one or more words
separated by a comma. For instance, a 'push 1, one' event can separated by a comma. For instance, a 'push 1, one' event can
be triggered to trigger when the player 'push 1' or 'push one'. be set to trigger when the player 'push 1' or 'push one'.
Args: Args:
events (list of dict): the list of events to be called. events (list of dict): the list of events to be called.

View file

@ -1,8 +1,7 @@
""" """
Module defining basic helpers for the event system. Module defining basic helpers for the event system.
Hlpers are just Python functions that can be used inside of events. They
Hlpers are just Python function that can be used inside of events. They
""" """
@ -70,9 +69,10 @@ def call(obj, event_name, seconds=0):
seconds (int or float): the number of seconds to wait before calling seconds (int or float): the number of seconds to wait before calling
the event. the event.
Notice that chained events are designed for this very purpose: they Note:
are never called automatically by the game, rather, they need to be Chained events are designed for this very purpose: they
called from inside another event. are never called automatically by the game, rather, they need
to be called from inside another event.
""" """
try: try:

View file

@ -8,16 +8,24 @@ from Queue import Queue
from django.conf import settings from django.conf import settings
from evennia import DefaultScript, ScriptDB from evennia import DefaultScript, ScriptDB
from evennia import logger 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, \ from evennia.contrib.events.custom import connect_event_types, \
get_next_wait, patch_hooks get_next_wait, patch_hooks
from evennia.contrib.events.exceptions import InterruptEvent from evennia.contrib.events.exceptions import InterruptEvent
from evennia.contrib.events import typeclasses from evennia.contrib.events import typeclasses
from evennia.utils.dbserialize import dbserialize
from evennia.utils.utils import all_from_module, delay
class EventHandler(DefaultScript): class EventHandler(DefaultScript):
"""Event handler that contains all events in a global script.""" """
The event handler that contains all events in a global script.
This script shouldn't be created more than once. It contains
event types (in a non-persistent attribute) and events (in a
persistent attribute). The script method would help adding,
editing and deleting these events.
"""
def at_script_creation(self): def at_script_creation(self):
self.key = "event_handler" self.key = "event_handler"
@ -41,8 +49,9 @@ class EventHandler(DefaultScript):
# Generate locals # Generate locals
self.ndb.current_locals = {} self.ndb.current_locals = {}
addresses = ["evennia.contrib.events.helpers"]
self.ndb.fresh_locals = {} self.ndb.fresh_locals = {}
addresses = ["evennia.contrib.events.helpers"]
addresses.extend(getattr(settings, "EVENTS_HELPERS_LOCATIONS", []))
for address in addresses: for address in addresses:
self.ndb.fresh_locals.update(all_from_module(address)) self.ndb.fresh_locals.update(all_from_module(address))
@ -56,7 +65,6 @@ class EventHandler(DefaultScript):
delay(seconds, complete_task, task_id) delay(seconds, complete_task, task_id)
def get_events(self, obj): def get_events(self, obj):
""" """
Return a dictionary of the object's events. Return a dictionary of the object's events.
@ -64,6 +72,13 @@ class EventHandler(DefaultScript):
Args: Args:
obj (Object): the connected objects. obj (Object): the connected objects.
Returns:
A dictionary of the object's events.
Note:
This method can be useful to override in some contexts,
when several objects would share events.
""" """
return self.db.events.get(obj, {}) return self.db.events.get(obj, {})
@ -74,6 +89,14 @@ class EventHandler(DefaultScript):
Args: Args:
obj (Object): the connected object. obj (Object): the connected object.
Returns:
A dictionary of the object's event types.
Note:
Event types would define what the object can have as
events. Note, however, that chained events will not
appear in event types and are handled separately.
""" """
types = {} types = {}
event_types = self.ndb.event_types event_types = self.ndb.event_types
@ -96,11 +119,11 @@ class EventHandler(DefaultScript):
Add the specified event. Add the specified event.
Args: Args:
obj (Object): the Evennia typeclassed object to be modified. obj (Object): the Evennia typeclassed object to be extended.
event_name (str): the name of the event to add. event_name (str): the name of the event to add.
code (str): the Python code associated with this event. code (str): the Python code associated with this event.
author (optional, Character, Player): the author of the event. author (Character or Player, optional): the author of the event.
valid (optional, bool): should the event be connected? valid (bool, optional): should the event be connected?
parameters (str, optional): optional parameters. parameters (str, optional): optional parameters.
This method doesn't check that the event type exists. This method doesn't check that the event type exists.
@ -148,12 +171,15 @@ class EventHandler(DefaultScript):
Edit the specified event. Edit the specified event.
Args: Args:
obj (Object): the Evennia typeclassed object to be modified. obj (Object): the Evennia typeclassed object to be edited.
event_name (str): the name of the event to add. event_name (str): the name of the event to edit.
number (int): the event number to be changed. number (int): the event number to be changed.
code (str): the Python code associated with this event. code (str): the Python code associated with this event.
author (optional, Character, Player): the author of the event. author (Character or Player, optional): the author of the event.
valid (optional, bool): should the event be connected? valid (bool, optional): should the event be connected?
Raises:
RuntimeError if the event is locked.
This method doesn't check that the event type exists. This method doesn't check that the event type exists.
@ -170,7 +196,7 @@ class EventHandler(DefaultScript):
# If locked, don't edit it # If locked, don't edit it
if (obj, event_name, number) in self.db.locked: if (obj, event_name, number) in self.db.locked:
raise RunTimeError("this event is locked.") raise RuntimeError("this event is locked.")
# Edit the event # Edit the event
events[number].update({ events[number].update({
@ -186,7 +212,6 @@ class EventHandler(DefaultScript):
elif valid and (obj, event_name, number) in self.db.to_valid: elif valid and (obj, event_name, number) in self.db.to_valid:
self.db.to_valid.remove((obj, event_name, number)) self.db.to_valid.remove((obj, event_name, number))
def del_event(self, obj, event_name, number): def del_event(self, obj, event_name, number):
""" """
Delete the specified event. Delete the specified event.
@ -196,13 +221,16 @@ class EventHandler(DefaultScript):
event_name (str): the name of the event to delete. event_name (str): the name of the event to delete.
number (int): the number of the event to delete. number (int): the number of the event to delete.
Raises:
RuntimeError if the event is locked.
""" """
obj_events = self.db.events.get(obj, {}) obj_events = self.db.events.get(obj, {})
events = obj_events.get(event_name, []) events = obj_events.get(event_name, [])
# If locked, don't edit it # If locked, don't edit it
if (obj, event_name, number) in self.db.locked: if (obj, event_name, number) in self.db.locked:
raise RunTimeError("this event is locked.") raise RuntimeError("this event is locked.")
# Delete the event itself # Delete the event itself
try: try:
@ -274,9 +302,9 @@ class EventHandler(DefaultScript):
*args: additional variables for this event. *args: additional variables for this event.
Kwargs: Kwargs:
number (int, default None): call just a specific event. number (int, optional): call just a specific event.
parameters (str, default ""): call an event with parameters. parameters (str, optional): call an event with parameters.
locals (dict): a locals replacement. locals (dict, optional): a locals replacement.
Returns: Returns:
True to report the event was called without interruption, True to report the event was called without interruption,
@ -307,7 +335,7 @@ class EventHandler(DefaultScript):
try: try:
locals[variable] = args[i] locals[variable] = args[i]
except IndexError: except IndexError:
logger.log_err("event {} of {} ({}): need variable " \ logger.log_trace("event {} of {} ({}): need variable " \
"{} in position {}".format(event_name, obj, "{} in position {}".format(event_name, obj,
type(obj), variable, i)) type(obj), variable, i))
return False return False
@ -348,15 +376,16 @@ class EventHandler(DefaultScript):
the differed delay is called again. the differed delay is called again.
Args: Args:
seconds (int/float): the delay in seconds from now. seconds (int, float): the delay in seconds from now.
obj (Object): the typecalssed object connected to the event. obj (Object): the typecalssed object connected to the event.
event_name (str): the event's name. event_name (str): the event's name.
Note that the dictionary of locals is frozen and will be Note:
available again when the task runs. This feature, however, The dictionary of locals is frozen and will be available
is limited by the database: all data cannot be saved. Lambda again when the task runs. This feature, however, is limited
functions, class methods, objects inside an instance and so by the database: all data cannot be saved. Lambda functions,
on will not be kept in the locals dictionary. class methods, objects inside an instance and so on will
not be kept in the locals dictionary.
""" """
now = datetime.now() now = datetime.now()
@ -399,7 +428,7 @@ class TimeEventScript(DefaultScript):
try: try:
script = ScriptDB.objects.get(db_key="event_handler") script = ScriptDB.objects.get(db_key="event_handler")
except ScriptDB.DoesNotExist: except ScriptDB.DoesNotExist:
logger.log_err("Can't get the event handler.") logger.log_trace("Can't get the event handler.")
return return
if self.db.event_name and self.db.number is not None: if self.db.event_name and self.db.number is not None:
@ -434,13 +463,13 @@ def complete_task(task_id):
This function should be called automatically for individual tasks. This function should be called automatically for individual tasks.
Args: Args:
task_id (int): the task id. task_id (int): the task ID.
""" """
try: try:
script = ScriptDB.objects.get(db_key="event_handler") script = ScriptDB.objects.get(db_key="event_handler")
except ScriptDB.DoesNotExist: except ScriptDB.DoesNotExist:
logger.log_err("Can't get the event handler.") logger.log_trace("Can't get the event handler.")
return return
if task_id not in script.db.tasks: if task_id not in script.db.tasks:

View file

@ -4,9 +4,9 @@ Patched typeclasses for Evennia.
from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
from evennia import ScriptDB from evennia import ScriptDB
from evennia.utils.utils import inherits_from
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 create_time_event
from evennia.utils.utils import inherits_from
class PatchedExit(object): class PatchedExit(object):
@ -52,10 +52,12 @@ create_event_type(DefaultExit, "can_traverse", ["character", "exit", "room"],
Can the character traverse through this exit? Can the character traverse through this exit?
This event is called when a character is about to traverse this This event is called when a character is about to traverse this
exit. You can use the deny() function to deny the character from exit. You can use the deny() function to deny the character from
using this exit for the time being. The 'character' variable exitting for this time.
contains the character who wants to traverse through this exit.
The 'exit' variable contains the exit, the 'room' variable Variables you can use in this event:
contains the room in which the character and exit are. character: the character that wants to traverse this exit.
exit: the exit to be traversed.
room: the room in which stands the character before moving.
""") """)
create_event_type(DefaultExit, "traverse", ["character", "exit", create_event_type(DefaultExit, "traverse", ["character", "exit",
"origin", "destination"], """ "origin", "destination"], """
@ -64,11 +66,12 @@ create_event_type(DefaultExit, "traverse", ["character", "exit",
exit. Traversing cannot be prevented using 'deny()' at this exit. Traversing cannot be prevented using 'deny()' at this
point. The character will be in a different room and she will point. The character will be in a different room and she will
have received the room's description when this event is called. have received the room's description when this event is called.
The 'character' variable contains the character who has traversed
through this exit. The 'exit' variable contains the exit, the Variables you can use in this event:
'origin' variable contains the room in which the character was character: the character who has traversed through this exit.
before traversing, while 'destination' contains the room in which exit: the exit that was just traversed through.
the character now is. origin: the exit's location (where the character was before moving).
destination: the character's location after moving.
""") """)
# Room events # Room events
@ -82,7 +85,7 @@ create_event_type(DefaultRoom, "time", ["room"], """
spaces, colons or dashes. Keep it as close from a recognizable spaces, colons or dashes. Keep it as close from a recognizable
date format, like this: date format, like this:
@event/add here = time 06-15 12:20 @event/add here = time 06-15 12:20
This event will fire every year on June 15th at 12 PM (still This event will fire every year on June the 15th at 12 PM (still
game time). Units have to be specified depending on your set calendar game time). Units have to be specified depending on your set calendar
(ask a developer for more details). (ask a developer for more details).