diff --git a/evennia/__init__.py b/evennia/__init__.py index 7586bf2d87..0a5db98372 100644 --- a/evennia/__init__.py +++ b/evennia/__init__.py @@ -102,6 +102,7 @@ EvForm = None EvEditor = None EvMore = None ANSIString = None +signals = None # Handlers SESSION_HANDLER = None @@ -168,6 +169,7 @@ def _init(): global search_help, search_tag, search_message global create_object, create_script, create_account, create_channel global create_message, create_help_entry + global signals global settings, lockfuncs, logger, utils, gametime, ansi, spawn, managers global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER global CHANNEL_HANDLER, TASK_HANDLER @@ -179,6 +181,7 @@ def _init(): global BASE_ROOM_TYPECLASS, BASE_EXIT_TYPECLASS, BASE_CHANNEL_TYPECLASS global BASE_SCRIPT_TYPECLASS, BASE_GUEST_TYPECLASS + # Parent typeclasses from .accounts.accounts import DefaultAccount from .accounts.accounts import DefaultGuest from .objects.objects import DefaultObject @@ -229,6 +232,7 @@ def _init(): from .utils.evform import EvForm from .utils.eveditor import EvEditor from .utils.ansi import ANSIString + from .server import signals # handlers from .scripts.tickerhandler import TICKER_HANDLER diff --git a/evennia/commands/default/muxcommand.py b/evennia/commands/default/muxcommand.py index 53f64b9a20..23eac2a526 100644 --- a/evennia/commands/default/muxcommand.py +++ b/evennia/commands/default/muxcommand.py @@ -206,7 +206,6 @@ Command {self} has no defined `func()` - showing on-command variables: No child {variables} """ self.caller.msg(string) - return # a simple test command to show the available properties string = "-" * 50 string += "\n|w%s|n - Command variables from evennia:\n" % self.key diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 73118e847b..cdb9a1ab1d 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -22,7 +22,7 @@ from evennia.utils.logger import log_trace from evennia.utils.utils import (variable_from_module, is_iter, make_iter, delay, callables_from_module) from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT -from evennia.server.signals import SIGNAL_ACCOUNT_POST_CONNECT, SIGNAL_ACCOUNT_POST_DISCONNECT +from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT from evennia.utils.inlinefuncs import parse_inlinefunc from codecs import decode as codecs_decode @@ -520,8 +520,8 @@ class ServerSessionHandler(SessionHandler): "uid": session.uid}) account.at_post_login(session=session) if nsess < 2: - SIGNAL_ACCOUNT_POST_LOGIN.send(sender=account, session=session) - SIGNAL_ACCOUNT_POST_CONNECT.send(sender=account, session=session) + SIGNAL_ACCOUNT_POST_FIRST_LOGIN.send(sender=account, session=session) + SIGNAL_ACCOUNT_POST_LOGIN.send(sender=account, session=session) def disconnect(self, session, reason="", sync_portal=True): """ @@ -550,10 +550,10 @@ class ServerSessionHandler(SessionHandler): session.log(string) if nsess == 0: - SIGNAL_ACCOUNT_POST_LOGOUT.send(sender=session.account, session=session) + SIGNAL_ACCOUNT_POST_LAST_LOGOUT.send(sender=session.account, session=session) session.at_disconnect(reason) - SIGNAL_ACCOUNT_POST_DISCONNECT.send(sender=session.account, session=session) + SIGNAL_ACCOUNT_POST_LOGOUT.send(sender=session.account, session=session) sessid = session.sessid if sessid in self and not hasattr(self, "_disconnect_all"): del self[sessid] diff --git a/evennia/server/signals.py b/evennia/server/signals.py index 975b82ee5c..3154898ba4 100644 --- a/evennia/server/signals.py +++ b/evennia/server/signals.py @@ -20,8 +20,10 @@ that may be in high demand. """ from django.dispatch import Signal -# The sender is the created Account. This is triggered at the very end of Account.create() -# after the Account is created. +# The sender is the created Account. This is triggered at the very end of +# Account.create() after the Account is created. Note that this will *not* fire +# if calling create.create_account alone, since going through the Account.create() +# is the most expected route. SIGNAL_ACCOUNT_POST_CREATE = Signal(providing_args=['ip', ]) # The Sender is the renamed Account. This is triggered by the username setter in AccountDB. @@ -29,22 +31,25 @@ SIGNAL_ACCOUNT_POST_RENAME = Signal(providing_args=['old_name', 'new_name']) # The Sender is the connecting Account. This is triggered when an Account connects cold; # that is, it had no other sessions connected. +SIGNAL_ACCOUNT_POST_FIRST_LOGIN = Signal(providing_args=['session', ]) + +# The sender is the connecting Account. This is triggered whenever a session authenticates +# to an Account regardless of existing sessions. It then firest after FIRST_LOGIN signal SIGNAL_ACCOUNT_POST_LOGIN = Signal(providing_args=['session', ]) # The Sender is the Account attempting to authenticate. This is triggered whenever a # session tries to login to an Account but fails. SIGNAL_ACCOUNT_POST_LOGIN_FAIL = Signal(providing_args=['session', ]) -# The sender is the connecting Account. This is triggered whenever a session authenticates -# to an Account regardless of existing sessions. -SIGNAL_ACCOUNT_POST_CONNECT = Signal(providing_args=['session', ]) - -# The sender is the Account. This is triggered when an Account's final session disconnects. -SIGNAL_ACCOUNT_POST_LOGOUT = Signal(providing_args=['session', ]) - # The sender is the disconnecting Account. This is triggered whenever a session disconnects # from the account, regardless of how many it started with or remain. -SIGNAL_ACCOUNT_POST_DISCONNECT = Signal(providing_args=['session', ]) +SIGNAL_ACCOUNT_POST_LOGOUT = Signal(providing_args=['session', ]) + +# The sender is the Account. This is triggered when an Account's final session disconnects. +SIGNAL_ACCOUNT_POST_LAST_LOGOUT = Signal(providing_args=['session', ]) + +# The sender is an Object. This is triggered when Object has been created, after all hooks. +SIGNAL_OBJECT_POST_CREATE = Signal() # The sender is the Object being puppeted. This is triggered after all puppeting hooks have # been called. The Object has already been puppeted by this point. @@ -54,11 +59,21 @@ SIGNAL_OBJECT_POST_PUPPET = Signal(providing_args=['session', 'account']) # The Object is no longer puppeted by this point. SIGNAL_OBJECT_POST_UNPUPPET = Signal(providing_args=['session', 'account']) -# The sender is the Typed Object being released. This isn't necessarily an Object; +# The sender is the Typed Object being renamed. This isn't necessarily an Object; # it could be a script. It fires whenever the value of the Typed object's 'key' # changes. Will need to use isinstance() or other filtering on things that use this. SIGNAL_TYPED_OBJECT_POST_RENAME = Signal(providing_args=['old_key', 'new_key']) +# The sender is the created Script. This is called after the Script was first created, +# after all hooks. +SIGNAL_SCRIPT_POST_CREATE = Signal() + +# The sender is a newly created help entry. This called after the entry was first created. +SIGNAL_HELPENTRY_POST_CREATE = Signal() + +# The sender is a newly created Channel. This is called after the Channel was +# first created, after all hooks. +SIGNAL_CHANNEL_POST_CREATE = Signal() # Django default signals (https://docs.djangoproject.com/en/2.2/topics/signals/) @@ -69,9 +84,9 @@ from django.db.models.signals import ( post_delete, # after " m2m_changed, # Sent when a ManyToManyField changes. pre_migrate, # Sent before migration starts - post_migrate, # after " + post_migrate, # after " pre_init, # Sent at start of typeclass __init__ (before at_init) - post_init, # end + post_init, # end ) from django.core.signals import ( request_started, # Sent when HTTP request begins. diff --git a/evennia/utils/create.py b/evennia/utils/create.py index d245ab7a5f..36729a62eb 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -25,6 +25,7 @@ from django.conf import settings from django.db import IntegrityError from django.utils import timezone from evennia.utils import logger +from evennia.server import signals from evennia.utils.utils import make_iter, class_from_module, dbid_to_obj # delayed imports @@ -135,6 +136,9 @@ def create_object(typeclass=None, key=None, location=None, home=None, # at_first_save hook on the typeclass, where the _createdict can be # used. new_object.save() + + signals.SIGNAL_OBJECT_POST_CREATE.send(sender=new_object) + return new_object @@ -235,6 +239,8 @@ def create_script(typeclass=None, key=None, obj=None, account=None, locks=None, # `start_delay=False` - the script will run once and immediately stop before save is over. return None + signals.SIGNAL_SCRIPT_POST_CREATE.send(sender=new_script) + return new_script @@ -288,6 +294,8 @@ def create_help_entry(key, entrytext, category="General", locks=None, aliases=No logger.log_trace() return None + signals.SIGNAL_HELPENTRY_POST_CREATE.send(sender=new_help) + # alias help_entry = create_help_entry @@ -387,6 +395,9 @@ def create_channel(key, aliases=None, desc=None, # at_first_save hook on the typeclass, where the _createdict can be # used. new_channel.save() + + signals.SIGNAL_CHANNEL_POST_CREATE.send(sender=new_channel) + return new_channel @@ -483,6 +494,10 @@ def create_account(key, email, password, # at_first_save hook on the typeclass, where the _createdict # can be used. new_account.save() + + # note that we don't send a signal here, that is sent from the Account.create helper method + # instead. + return new_account