Moved Players over to the new proxy system, made the start-hook called by the save-signal system into at_first_save()

This commit is contained in:
Griatch 2014-12-25 14:43:43 +01:00
parent db512cbbf5
commit 9af9f94fa0
9 changed files with 465 additions and 584 deletions

View file

@ -17,15 +17,23 @@ from src.typeclasses.models import TypeclassBase
from src.players.manager import PlayerManager
from src.players.models import PlayerDB
from src.comms.models import ChannelDB
from src.commands import cmdhandler
from src.scripts.models import ScriptDB
from src.utils import logger
from src.utils.utils import lazy_property, to_str, make_iter
from src.utils.utils import (lazy_property, to_str,
make_iter, to_unicode,
variable_from_module)
from src.typeclasses.attributes import NickHandler
from src.scripts.scripthandler import ScriptHandler
from src.commands.cmdsethandler import CmdSetHandler
from django.utils.translation import ugettext as _
__all__ = ("DefaultPlayer",)
_SESSIONS = None
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
_MULTISESSION_MODE = settings.MULTISESSION_MODE
_CMDSET_PLAYER = settings.CMDSET_PLAYER
_CONNECT_CHANNEL = None
@ -74,7 +82,7 @@ class DefaultPlayer(PlayerDB):
* Helper methods
msg(outgoing_string, from_obj=None, **kwargs)
swap_character(new_character, delete_old_character=False)
#swap_character(new_character, delete_old_character=False)
execute_cmd(raw_string)
search(ostring, global_search=False, attribute_name=None,
use_nicks=False, location=None,
@ -122,6 +130,171 @@ class DefaultPlayer(PlayerDB):
return NickHandler(self)
# session-related methods
def get_session(self, sessid):
"""
Return session with given sessid connected to this player.
note that the sessionhandler also accepts sessid as an iterable.
"""
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.session_from_player(self, sessid)
def get_all_sessions(self):
"Return all sessions connected to this player"
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.sessions_from_player(self)
sessions = property(get_all_sessions) # alias shortcut
def disconnect_session_from_player(self, sessid):
"""
Access method for disconnecting a given session from the player
(connection happens automatically in the sessionhandler)
"""
# this should only be one value, loop just to make sure to
# clean everything
sessions = (session for session in self.get_all_sessions()
if session.sessid == sessid)
for session in sessions:
# this will also trigger unpuppeting
session.sessionhandler.disconnect(session)
# puppeting operations
def puppet_object(self, sessid, obj, normal_mode=True):
"""
Use the given session to control (puppet) the given object (usually
a Character type). Note that we make no puppet checks here, that must
have been done before calling this method.
sessid - session id of session to connect
obj - the object to connect to
normal_mode - trigger hooks and extra checks - this is turned off when
the server reloads, to quickly re-connect puppets.
returns True if successful, False otherwise
"""
session = self.get_session(sessid)
if not session:
return False
if normal_mode and session.puppet:
# cleanly unpuppet eventual previous object puppeted by this session
self.unpuppet_object(sessid)
if obj.player and obj.player.is_connected and obj.player != self:
# we don't allow to puppet an object already controlled by an active
# player. To kick a player, call unpuppet_object on them explicitly.
return
# if we get to this point the character is ready to puppet or it
# was left with a lingering player/sessid reference from an unclean
# server kill or similar
if normal_mode:
obj.at_pre_puppet(self, sessid=sessid)
# do the connection
obj.sessid.add(sessid)
obj.player = self
session.puid = obj.id
session.puppet = obj
# validate/start persistent scripts on object
ScriptDB.objects.validate(obj=obj)
if normal_mode:
obj.at_post_puppet()
return True
def unpuppet_object(self, sessid):
"""
Disengage control over an object
sessid - the session id to disengage
returns True if successful
"""
session = self.get_session(sessid)
if not session:
return False
obj = hasattr(session, "puppet") and session.puppet or None
if not obj:
return False
# do the disconnect, but only if we are the last session to puppet
obj.at_pre_unpuppet()
obj.dbobj.sessid.remove(sessid)
if not obj.dbobj.sessid.count():
del obj.dbobj.player
obj.at_post_unpuppet(self, sessid=sessid)
session.puppet = None
session.puid = None
return True
def unpuppet_all(self):
"""
Disconnect all puppets. This is called by server
before a reset/shutdown.
"""
for session in self.get_all_sessions():
self.unpuppet_object(session.sessid)
def get_puppet(self, sessid, return_dbobj=False):
"""
Get an object puppeted by this session through this player. This is
the main method for retrieving the puppeted object from the
player's end.
sessid - return character connected to this sessid,
character - return character if connected to this player, else None.
"""
session = self.get_session(sessid)
if not session:
return None
if return_dbobj:
return session.puppet
return session.puppet and session.puppet or None
def get_all_puppets(self, return_dbobj=False):
"""
Get all currently puppeted objects as a list
"""
puppets = [session.puppet for session in self.get_all_sessions()
if session.puppet]
if return_dbobj:
return puppets
return [puppet for puppet in puppets]
def __get_single_puppet(self):
"""
This is a legacy convenience link for users of
MULTISESSION_MODE 0 or 1. It will return
only the first puppet. For mode 2, this returns
a list of all characters.
"""
puppets = self.get_all_puppets()
if _MULTISESSION_MODE in (0, 1):
return puppets and puppets[0] or None
return puppets
character = property(__get_single_puppet)
puppet = property(__get_single_puppet)
# utility methods
def delete(self, *args, **kwargs):
"""
Deletes the player permanently.
"""
for session in self.get_all_sessions():
# unpuppeting all objects and disconnecting the user, if any
# sessions remain (should usually be handled from the
# deleting command)
self.unpuppet_object(session.sessid)
session.sessionhandler.disconnect(session, reason=_("Player being deleted."))
self.scripts.stop()
self.attributes.clear()
self.nicks.clear()
self.aliases.clear()
super(PlayerDB, self).delete(*args, **kwargs)
## methods inherited from database model
def msg(self, text=None, from_obj=None, sessid=None, **kwargs):
@ -167,48 +340,38 @@ class DefaultPlayer(PlayerDB):
for sess in self.get_all_sessions():
sess.msg(text=text, **kwargs)
def swap_character(self, new_character, delete_old_character=False):
"""
Swaps the character controlled by this Player, if possible.
new_character (Object) - character/object to swap to
delete_old_character (bool) - delete the old character when swapping
Returns: True/False depending on if swap suceeded or not.
"""
return super(DefaultPlayer, self).swap_character(new_character, delete_old_character=delete_old_character)
def execute_cmd(self, raw_string, sessid=None, **kwargs):
"""
Do something as this object. This command transparently
lets its typeclass execute the command. This method
is -not- called by Evennia normally, it is here to be
called explicitly in code.
Do something as this player. This method is never called normally,
but only when the player object itself is supposed to execute the
command. It takes player nicks into account, but not nicks of
eventual puppets.
Argument:
raw_string (string) - raw command input
sessid (int) - id of session executing the command. This sets the
sessid property on the command
raw_string - raw command input coming from the command line.
sessid - the optional session id to be responsible for the command-send
**kwargs - other keyword arguments will be added to the found command
object instace as variables before it executes. This is
unused by default Evennia but may be used to set flags and
change operating paramaters for commands at run-time.
Returns Deferred - this is an asynchronous Twisted object that will
not fire until the command has actually finished executing. To
overload this one needs to attach callback functions to it, with
addCallback(function). This function will be called with an
eventual return value from the command execution.
This return is not used at all by Evennia by default, but might
be useful for coders intending to implement some sort of nested
command structure.
"""
return super(DefaultPlayer, self).execute_cmd(raw_string, sessid=sessid, **kwargs)
raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=False)
if not sessid and _MULTISESSION_MODE in (0, 1):
# in this case, we should either have only one sessid, or the sessid
# should not matter (since the return goes to all of them we can
# just use the first one as the source)
try:
sessid = self.get_all_sessions()[0].sessid
except IndexError:
# this can happen for bots
sessid = None
return cmdhandler.cmdhandler(self, raw_string,
callertype="player", sessid=sessid, **kwargs)
def search(self, searchdata, return_puppet=False, **kwargs):
"""
This is similar to the Object search method but will search for
This is similar to the ObjectDB search method but will search for
Players only. Errors will be echoed, and None returned if no Player
is found.
searchdata - search criterion, the Player's key or dbref to search for
@ -224,7 +387,14 @@ class DefaultPlayer(PlayerDB):
# handle wrapping of common terms
if searchdata.lower() in ("me", "*me", "self", "*self",):
return self
return super(DefaultPlayer, self).search(searchdata, return_puppet=return_puppet, **kwargs)
matches = self.__class__.objects.player_search(searchdata)
matches = _AT_SEARCH_RESULT(self, searchdata, matches, global_search=True)
if matches and return_puppet:
try:
return matches.puppet
except AttributeError:
return None
return matches
def is_typeclass(self, typeclass, exact=False):
"""
@ -332,6 +502,7 @@ class DefaultPlayer(PlayerDB):
lockstring = "attrread:perm(Admins);attredit:perm(Admins);attrcreate:perm(Admins)"
self.attributes.add("_playable_characters", [], lockstring=lockstring)
# TODO - handle this in __init__ instead.
def at_init(self):
"""
This is always called whenever this object is initiated --
@ -344,12 +515,35 @@ class DefaultPlayer(PlayerDB):
"""
pass
# Note that the hooks below also exist in the character object's
# typeclass. You can often ignore these and rely on the character
# ones instead, unless you are implementing a multi-character game
# and have some things that should be done regardless of which
# character is currently connected to this player.
def at_first_save(self):
"""
This is a generic hook called by Evennia when this object is
saved to the database the very first time. You generally
don't override this method but the hooks called by it.
"""
self.basetype_setup()
self.at_player_creation()
permissions = settings.PERMISSION_PLAYER_DEFAULT
if hasattr(self, "_createdict"):
# this will only be set if the utils.create_player
# function was used to create the object.
cdict = self._createdict
if "locks" in cdict:
self.locks.add(cdict["locks"])
if "permissions" in cdict:
permissions = cdict["permissions"]
del self._createdict
self.permissions.add(permissions)
def at_access(self, result, accessing_obj, access_type, **kwargs):
"""
This is called with the result of an access call, along with
@ -373,8 +567,7 @@ class DefaultPlayer(PlayerDB):
def at_first_login(self):
"""
Only called once, the very first
time the user logs in.
Called the very first time this player logs into the game.
"""
pass
@ -472,6 +665,7 @@ class DefaultPlayer(PlayerDB):
"""
pass
class Guest(DefaultPlayer):
"""
This class is used for guest logins. Unlike Players, Guests and their