Reworked most of the system to use sessions directly instead of sessids by the introduction of on-object sessionhandlers. No debugging done yet.

This commit is contained in:
Griatch 2015-11-14 18:36:19 +01:00
parent 556a0cc332
commit 709f5ff5b3
5 changed files with 234 additions and 259 deletions

View file

@ -138,8 +138,7 @@ class ErrorReported(Exception):
# Helper function # Helper function
@inlineCallbacks @inlineCallbacks
def get_and_merge_cmdsets(caller, session, player, obj, def get_and_merge_cmdsets(caller, session, player, obj, callertype):
callertype, sessid=None):
""" """
Gather all relevant cmdsets and merge them. Gather all relevant cmdsets and merge them.
@ -154,7 +153,6 @@ def get_and_merge_cmdsets(caller, session, player, obj,
obj (Object or None): The Object associated with caller, if any. obj (Object or None): The Object associated with caller, if any.
callertype (str): This identifies caller as either "player", "object" or "session" callertype (str): This identifies caller as either "player", "object" or "session"
to avoid having to do this check internally. to avoid having to do this check internally.
sessid (int, optional): Session ID. This is not used at the moment.
Returns: Returns:
cmdset (Deferred): This deferred fires with the merged cmdset cmdset (Deferred): This deferred fires with the merged cmdset
@ -335,7 +333,7 @@ def get_and_merge_cmdsets(caller, session, player, obj,
@inlineCallbacks @inlineCallbacks
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sessid=None, **kwargs): def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None, **kwargs):
""" """
This is the main mechanism that handles any string sent to the engine. This is the main mechanism that handles any string sent to the engine.
@ -355,7 +353,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
cmdset and the Objects and so on. Merge order is the same cmdset and the Objects and so on. Merge order is the same
order, so that Object cmdsets are merged in last, giving them order, so that Object cmdsets are merged in last, giving them
precendence for same-name and same-prio commands. precendence for same-name and same-prio commands.
sessid (int, optional): Relevant if callertype is "player" - the session id will help session (int, optional): Relevant if callertype is "player" - the session will help
retrieve the correct cmdsets from puppeted objects. retrieve the correct cmdsets from puppeted objects.
Kwargs: Kwargs:
@ -398,7 +396,6 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
cmd.cmdstring = cmdname cmd.cmdstring = cmdname
cmd.args = args cmd.args = args
cmd.cmdset = cmdset cmd.cmdset = cmdset
cmd.sessid = session.sessid if session else sessid
cmd.session = session cmd.session = session
cmd.player = player cmd.player = player
cmd.raw_string = unformatted_raw_string cmd.raw_string = unformatted_raw_string
@ -460,17 +457,15 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
raw_string = to_unicode(raw_string, force_string=True) raw_string = to_unicode(raw_string, force_string=True)
session, player, obj = None, None, None session, player, obj = session, None, None
if callertype == "session": if callertype == "session":
session = called_by session = called_by
player = session.player player = session.player
if player: obj = session.puppet
obj = yield player.get_puppet(session.sessid)
elif callertype == "player": elif callertype == "player":
player = called_by player = called_by
if sessid: if session:
session = player.get_session(sessid) obj = yield session.puppet
obj = yield player.get_puppet(sessid)
elif callertype == "object": elif callertype == "object":
obj = called_by obj = called_by
else: else:
@ -486,7 +481,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
try: # catch special-type commands try: # catch special-type commands
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj, cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
callertype, sessid) callertype)
if not cmdset: if not cmdset:
# this is bad and shouldn't happen. # this is bad and shouldn't happen.
raise NoCmdSets raise NoCmdSets
@ -553,7 +548,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
if syscmd: if syscmd:
# replace system command with custom version # replace system command with custom version
cmd = syscmd cmd = syscmd
cmd.sessid = session.sessid if session else None cmd.session = session
sysarg = "%s:%s" % (cmdname, args) sysarg = "%s:%s" % (cmdname, args)
raise ExecSystemCommand(cmd, sysarg) raise ExecSystemCommand(cmd, sysarg)

View file

@ -34,7 +34,7 @@ _SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
class SessidHandler(object): class ObjectSessionHandler(object):
""" """
Handles the get/setting of the sessid Handles the get/setting of the sessid
comma-separated integer field comma-separated integer field
@ -52,50 +52,86 @@ class SessidHandler(object):
self._recache() self._recache()
def _recache(self): def _recache(self):
self._cache = list(set(int(val) for val in (self.obj.db_sessid or "").split(",") if val)) self._sessid_cache = list(set(int(val) for val in (self.obj.db_sessid or "").split(",") if val))
def get(self): def get(self, sessid=None):
""" """
Get the session ids. Get the sessions linked to this Object.
Args:
sessid (int, optional): A specific session id.
Returns: Returns:
session (list): A cached list of one or more session ids. sessions (list): The sessions connected to this object. If `sessid` is given,
this is a list of one (or zero) elements.
Notes: Notes:
Aliased to `self.all()`. Aliased to `self.all()`.
""" """
return self._cache global _SESSIONS
all = get # alias if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
if sessid:
return [_SESSIONS[sessid]] if sessid in self._sessid_cache and sessid in _SESSIONS else []
else:
return [_SESSIONS[sessid] for sessid in self._sessid_cache if sessid in _SESSIONS]
def add(self, sessid): def all(self):
""" """
Add sessid to handler. Alias to get(), returning all sessions.
Returns:
sessions (list): All sessions.
"""
return self.get()
def add(self, session):
"""
Add session to handler.
Args: Args:
sessid (int): Session id to add. session (Session or int): Session or session id to add.
Notes:
We will only add a session/sessid if this actually also exists
in the the core sessionhandler.
""" """
_cache = self._cache global _SESSIONS
if sessid not in _cache: if not _SESSIONS:
if len(_cache) >= _SESSID_MAX: from evennia.server.sessionhandler import SESSIONS as _SESSIONS
try:
sessid = session.sessid
except AttributeError:
sessid = session
sessid_cache = self._sessid_cache
if sessid in _SESSIONS and sessid not in sessid_cache:
if len(sessid_cache) >= _SESSID_MAX:
return return
_cache.append(sessid) sessid_cache.append(sessid)
self.obj.db_sessid = ",".join(str(val) for val in _cache) self.obj.db_sessid = ",".join(str(val) for val in sessid_cache)
self.obj.save(update_fields=["db_sessid"]) self.obj.save(update_fields=["db_sessid"])
def remove(self, sessid): def remove(self, session):
""" """
Remove sessid from handler. Remove session from handler.
Args: Args:
sessid (int): Session id to remove. sessid (Session or int): Session or session id to remove.
""" """
_cache = self._cache try:
if sessid in _cache: sessid = session.sessid
_cache.remove(sessid) except AttributeError:
self.obj.db_sessid = ",".join(str(val) for val in _cache) sessid = session
sessid_cache = self._sessid_cache
if sessid in sessid_cache:
sessid_cache.remove(sessid)
self.obj.db_sessid = ",".join(str(val) for val in sessid_cache)
self.obj.save(update_fields=["db_sessid"]) self.obj.save(update_fields=["db_sessid"])
def clear(self): def clear(self):
@ -103,7 +139,7 @@ class SessidHandler(object):
Clear all handled sessids. Clear all handled sessids.
""" """
self._cache = [] self._sessid_cache = []
self.obj.db_sessid = None self.obj.db_sessid = None
self.obj.save(update_fields=["db_sessid"]) self.obj.save(update_fields=["db_sessid"])
@ -152,19 +188,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return NickHandler(self) return NickHandler(self)
@lazy_property @lazy_property
def sessid(self):
return SessidHandler(self)
@property
def sessions(self): def sessions(self):
""" return ObjectSessionHandler(self)
Retrieve sessions connected to this object.
"""
# if the player is not connected, this will simply be an empty list.
if self.db_player:
return self.db_player.get_all_sessions()
return []
@property @property
def has_player(self): def has_player(self):
@ -372,7 +397,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return results return results
return _AT_SEARCH_RESULT(results, self, query=searchdata) return _AT_SEARCH_RESULT(results, self, query=searchdata)
def execute_cmd(self, raw_string, sessid=None, **kwargs): def execute_cmd(self, raw_string, session=None, **kwargs):
""" """
Do something as this object. This method is a copy of the Do something as this object. This method is a copy of the
`execute_cmd` method on the session. This is never called `execute_cmd` method on the session. This is never called
@ -382,7 +407,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args: Args:
raw_string (string): Raw command input raw_string (string): Raw command input
sessid (int, optional): Session id to return results to session (Session, optional): Session to
return results to
Kwargs: Kwargs:
Other keyword arguments will be added to the found command Other keyword arguments will be added to the found command
@ -407,10 +433,10 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
raw_string = to_unicode(raw_string) raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string, raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=True) categories=("inputline", "channel"), include_player=True)
return cmdhandler.cmdhandler(self, raw_string, callertype="object", sessid=sessid, **kwargs) return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs)
def msg(self, text=None, from_obj=None, sessid=0, **kwargs): def msg(self, text=None, from_obj=None, session=None, **kwargs):
""" """
Emits something to a session attached to the object. Emits something to a session attached to the object.
@ -418,17 +444,19 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
text (str, optional): The message to send text (str, optional): The message to send
from_obj (obj, optional): object that is sending. If from_obj (obj, optional): object that is sending. If
given, at_msg_send will be called given, at_msg_send will be called
sessid (int or list, optional): sessid or list of session (Session or list, optional): Session or list of
sessids to relay to, if any. If set, will Sessions to relay data to, if any. If set, will
force send regardless of MULTISESSION_MODE. force send to these sessions. If unset, who receives the
message depends on the MULTISESSION_MODE.
Notes: Notes:
`at_msg_receive` will be called on this Object. `at_msg_receive` will be called on this Object.
All extra kwargs will be passed on to the protocol. All extra kwargs will be passed on to the protocol.
""" """
text = to_str(text, force_string=True) if text != None else "" text = to_str(text, force_string=True) if text != None else ""
# try send hooks
if from_obj: if from_obj:
# call hook
try: try:
from_obj.at_msg_send(text=text, to_obj=self, **kwargs) from_obj.at_msg_send(text=text, to_obj=self, **kwargs)
except Exception: except Exception:
@ -440,18 +468,10 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
except Exception: except Exception:
logger.log_trace() logger.log_trace()
# session relay # relay to session(s)
kwargs['_nomulti'] = kwargs.get('_nomulti', True) sessions = make_iter(session) if session else self.sessions.all()
for session in sessions:
if self.player: session.msg(text=text, **kwargs)
# for there to be a session there must be a Player.
if sessid:
sessions = make_iter(self.player.get_session(sessid))
else:
# Send to all sessions connected to this object
sessions = [self.player.get_session(sessid) for sessid in self.sessid.get()]
if sessions:
sessions[0].msg(text=text, session=sessions, **kwargs)
def for_contents(self, func, exclude=None, **kwargs): def for_contents(self, func, exclude=None, **kwargs):
""" """
@ -756,8 +776,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# no need to disconnect, Player just jumps to OOC mode. # no need to disconnect, Player just jumps to OOC mode.
# sever the connection (important!) # sever the connection (important!)
if self.player: if self.player:
for sessid in self.sessid.all(): for session in self.sessions.all():
self.player.unpuppet_object(sessid) self.player.unpuppet_object(session)
self.player = None self.player = None
for script in _ScriptDB.objects.get_all_scripts_on_obj(self): for script in _ScriptDB.objects.get_all_scripts_on_obj(self):
@ -964,14 +984,14 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
""" """
pass pass
def at_pre_puppet(self, player, sessid=None): def at_pre_puppet(self, player, session=None):
""" """
Called just before a Player connects to this object to puppet Called just before a Player connects to this object to puppet
it. it.
Args: Args:
player (Player): This is the connecting player. player (Player): This is the connecting player.
sessid (int): Session id controlling the connection. session (Session): Session controlling the connection.
""" """
pass pass
@ -991,7 +1011,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
""" """
pass pass
def at_post_unpuppet(self, player, sessid=None): def at_post_unpuppet(self, player, session=None):
""" """
Called just after the Player successfully disconnected from Called just after the Player successfully disconnected from
this object, severing all connections. this object, severing all connections.
@ -999,7 +1019,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args: Args:
player (Player): The player object that just disconnected player (Player): The player object that just disconnected
from this object. from this object.
sessid (int): Session id controlling the connection that session (Session): Session id controlling the connection that
just disconnected. just disconnected.
""" """
@ -1404,14 +1424,14 @@ class DefaultCharacter(DefaultObject):
""" """
self.msg(self.at_look(self.location)) self.msg(self.at_look(self.location))
def at_pre_puppet(self, player, sessid=None): def at_pre_puppet(self, player, session=None):
""" """
This implementation recovers the character again after having been "stoved This implementation recovers the character again after having been "stoved
away" to the `None` location in `at_post_unpuppet`. away" to the `None` location in `at_post_unpuppet`.
Args: Args:
player (Player): This is the connecting player. player (Player): This is the connecting player.
sessid (int): Session id controlling the connection. session (Session): Session controlling the connection.
""" """
if self.db.prelogout_location: if self.db.prelogout_location:
@ -1425,7 +1445,7 @@ class DefaultCharacter(DefaultObject):
self.db.prelogout_location = self.location self.db.prelogout_location = self.location
self.location.at_object_receive(self, self.location) self.location.at_object_receive(self, self.location)
else: else:
player.msg("{r%s has no location and no home is set.{n" % self, sessid=sessid) player.msg("{r%s has no location and no home is set.{n" % self, session=session)
def at_post_puppet(self): def at_post_puppet(self):
""" """
@ -1440,7 +1460,7 @@ class DefaultCharacter(DefaultObject):
obj.msg("%s has entered the game." % self.get_display_name(obj), from_obj=from_obj) obj.msg("%s has entered the game." % self.get_display_name(obj), from_obj=from_obj)
self.location.for_contents(message, exclude=[self], from_obj=self) self.location.for_contents(message, exclude=[self], from_obj=self)
def at_post_unpuppet(self, player, sessid=None): def at_post_unpuppet(self, player, session=None):
""" """
We stove away the character when the player goes ooc/logs off, We stove away the character when the player goes ooc/logs off,
otherwise the character object will remain in the room also otherwise the character object will remain in the room also
@ -1449,7 +1469,7 @@ class DefaultCharacter(DefaultObject):
Args: Args:
player (Player): The player object that just disconnected player (Player): The player object that just disconnected
from this object. from this object.
sessid (int): Session id controlling the connection that session (Session): Session controlling the connection that
just disconnected. just disconnected.
""" """
if self.location: # have to check, in case of multiple connections closing if self.location: # have to check, in case of multiple connections closing

View file

@ -40,6 +40,55 @@ _MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
_CMDSET_PLAYER = settings.CMDSET_PLAYER _CMDSET_PLAYER = settings.CMDSET_PLAYER
_CONNECT_CHANNEL = None _CONNECT_CHANNEL = None
class PlayerSessionHandler(object):
"""
Manages the session(s) attached to a player.
"""
def __init__(self, player):
"""
Initializes the handler.
Args:
player (Player): The Player on which this handler is defined.
"""
self.player = player
def get(self, sessid=None):
"""
Get the sessions linked to this object.
Args:
sessid (int, optional): Specify a given session by
session id.
Returns:
sessions (list): A list of Session objects. If `sessid`
is given, this is a list with one (or zero) elements.
"""
global _SESSIONS
if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
if sessid:
return make_iter(_SESSIONS.session_from_player(self, sessid))
else:
return _SESSIONS.sessions_from_player(self)
def all(self):
"""
Alias to get(), returning all sessions.
Returns:
sessions (list): All sessions.
"""
return self.get()
class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
""" """
This is the base Typeclass for all Players. Players represent This is the base Typeclass for all Players. Players represent
@ -100,7 +149,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
- at_access() - at_access()
- at_cmdset_get(**kwargs) - at_cmdset_get(**kwargs)
- at_first_login() - at_first_login()
- at_post_login(sessid=None) - at_post_login(session=None)
- at_disconnect() - at_disconnect()
- at_message_receive() - at_message_receive()
- at_message_send() - at_message_send()
@ -124,69 +173,37 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
def nicks(self): def nicks(self):
return NickHandler(self) return NickHandler(self)
@lazy_property
def sessions(self):
return PlayerSessionHandler(self)
# session-related methods # session-related methods
def get_session(self, sessid): def disconnect_session_from_player(self, session):
"""
Retrieve given session.
Args:
sessid (int or list): A session id or list of such to retrieve.
Returns:
session (Session or list): One or more Sessions matching
the given `sessid`s while also being connected to this
Player.
"""
global _SESSIONS
if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.session_from_player(self, sessid)
def get_all_sessions(self):
"""
Get all sessions connected to this player.
Returns:
sessions (list): All Sessions currently connected to this
player.
"""
global _SESSIONS
if not _SESSIONS:
from evennia.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 Access method for disconnecting a given session from the
player (connection happens automatically in the player (connection happens automatically in the
sessionhandler) sessionhandler)
Args: Args:
sessid (int): Session id to disconnect. session (Session): Session to disconnect.
""" """
# this should only be one value, loop just to make sure to global _SESSIONS
# clean everything if not _SESSIONS:
sessions = (session for session in self.get_all_sessions() from evennia.server.sessionhandler import SESSIONS as _SESSIONS
if session.sessid == sessid) _SESSIONS.disconnect(session)
for session in sessions:
# this will also trigger unpuppeting
session.sessionhandler.disconnect(session)
# puppeting operations # puppeting operations
def puppet_object(self, sessid, obj): def puppet_object(self, session, obj):
""" """
Use the given session to control (puppet) the given object (usually Use the given session to control (puppet) the given object (usually
a Character type). a Character type).
Args: Args:
sessid (int): session id of session to connect session (Session): session to use for puppeting
obj (Object): the object to start puppeting obj (Object): the object to start puppeting
Raises: Raises:
@ -198,46 +215,50 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# safety checks # safety checks
if not obj: if not obj:
raise RuntimeError("Object not found") raise RuntimeError("Object not found")
session = self.get_session(sessid)
if not session: if not session:
raise RuntimeError("Session not found") raise RuntimeError("Session not found")
if self.get_puppet(sessid) == obj: if self.get_puppet(session) == obj:
# already puppeting this object # already puppeting this object
raise RuntimeError("You are already puppeting this object.") self.msg("You are already puppeting this object.")
return
if not obj.access(self, 'puppet'): if not obj.access(self, 'puppet'):
# no access # no access
raise RuntimeError("You don't have permission to puppet '%s'." % obj.key) self.msg("You don't have permission to puppet '%s'." % obj.key)
return
if obj.player: if obj.player:
# object already puppeted # object already puppeted
if obj.player == self: if obj.player == self:
if obj.sessid.count(): if obj.sessions.count():
# we may take over another of our sessions # we may take over another of our sessions
# output messages to the affected sessions # output messages to the affected sessions
if _MULTISESSION_MODE in (1, 3): if _MULTISESSION_MODE in (1, 3):
txt1 = "{c%s{n{G is now shared from another of your sessions.{n" txt1 = "Sharing {c%s{n with another of your sessions."
txt2 = "Sharing {c%s{n with another of your sessions." txt2 = "{c%s{n{G is now shared from another of your sessions.{n"
self.msg(txt1 % obj.name, session=session)
self.msg(txt2 % obj.name, session=obj.sessions.all())
else: else:
txt1 = "{c%s{n{R is now acted from another of your sessions.{n" txt1 = "Taking over {c%s{n from another of your sessions."
txt2 = "Taking over {c%s{n from another of your sessions." txt2 = "{c%s{n{R is now acted from another of your sessions.{n"
self.unpuppet_object(obj.sessid.get()) self.msg(txt1 % obj.name, session=session)
self.msg(txt1 % obj.name, sessid=obj.sessid.get(), _forced_nomulti=True) self.msg(txt2 % obj.name, session=obj.sessions.all())
self.msg(txt2 % obj.name, sessid=sessid, _forced_nomulti=True) self.unpuppet_object(obj.sessions.get())
elif obj.player.is_connected: elif obj.player.is_connected:
# controlled by another player # controlled by another player
raise RuntimeError("{R{c%s{R is already puppeted by another Player.") self.msg("{R{c%s{R is already puppeted by another Player.")
return
# do the puppeting # do the puppeting
if session.puppet: if session.puppet:
# cleanly unpuppet eventual previous object puppeted by this session # cleanly unpuppet eventual previous object puppeted by this session
self.unpuppet_object(sessid) self.unpuppet_object(session)
# if we get to this point the character is ready to puppet or it # 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 # was left with a lingering player/session reference from an unclean
# server kill or similar # server kill or similar
obj.at_pre_puppet(self, sessid=sessid) obj.at_pre_puppet(self, session=session)
# do the connection # do the connection
obj.sessid.add(sessid) obj.session.add(session)
obj.player = self obj.player = self
session.puid = obj.id session.puid = obj.id
session.puppet = obj session.puppet = obj
@ -246,37 +267,30 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# re-cache locks to make sure superuser bypass is updated # re-cache locks to make sure superuser bypass is updated
obj.locks.cache_lock_bypass(obj) obj.locks.cache_lock_bypass(obj)
# final hook
obj.at_post_puppet() obj.at_post_puppet()
def unpuppet_object(self, sessid): def unpuppet_object(self, session):
""" """
Disengage control over an object. Disengage control over an object.
Args: Args:
sessid(int): The session id to disengage. session (Session or list): The session or a list of
sessions to disengage from their puppets.
Raises: Raises:
RuntimeError With message about error. RuntimeError With message about error.
""" """
if _MULTISESSION_MODE == 1: for session in make_iter(session):
sessions = self.get_all_sessions() obj = session.puppet
else: if obj:
sessions = self.get_session(sessid)
if not sessions:
raise RuntimeError("No session was found.")
for session in make_iter(sessions):
obj = session.puppet or None
if not obj:
raise RuntimeError("No puppet was found to disconnect from.")
elif obj:
# do the disconnect, but only if we are the last session to puppet # do the disconnect, but only if we are the last session to puppet
obj.at_pre_unpuppet() obj.at_pre_unpuppet()
obj.sessid.remove(session.sessid) obj.sessions.remove(session)
if not obj.sessid.count(): if not obj.sessions.count():
del obj.player del obj.player
obj.at_post_unpuppet(self, sessid=sessid) obj.at_post_unpuppet(self, session=session)
# Just to be sure we're always clear. # Just to be sure we're always clear.
session.puppet = None session.puppet = None
session.puid = None session.puid = None
@ -286,24 +300,22 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
Disconnect all puppets. This is called by server before a Disconnect all puppets. This is called by server before a
reset/shutdown. reset/shutdown.
""" """
for session in (sess for sess in self.get_all_sessions() if sess.puppet): self.unpuppet_object(self.sessions.all())
self.unpuppet_object(session.sessid)
def get_puppet(self, sessid): def get_puppet(self, session):
""" """
Get an object puppeted by this session through this player. This is Get an object puppeted by this session through this player. This is
the main method for retrieving the puppeted object from the the main method for retrieving the puppeted object from the
player's end. player's end.
Args: Args:
sessid (int): Find puppeted object based on this sessid. session (Session): Find puppeted object based on this session
Returns: Returns:
puppet (Object): The matching puppeted object, if any. puppet (Object): The matching puppeted object, if any.
""" """
session = self.get_session(sessid) return session.puppet
return session.puppet if session and session.puppet else None
def get_all_puppets(self): def get_all_puppets(self):
""" """
@ -314,7 +326,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
by this Player. by this Player.
""" """
return list(set(session.puppet for session in self.get_all_sessions() return list(set(session.puppet for session in self.sessions.all()
if session.puppet)) if session.puppet))
def __get_single_puppet(self): def __get_single_puppet(self):
@ -350,7 +362,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# sessions remain (should usually be handled from the # sessions remain (should usually be handled from the
# deleting command) # deleting command)
try: try:
self.unpuppet_object(session.sessid) self.unpuppet_object(session)
except RuntimeError: except RuntimeError:
# no puppet to disconnect from # no puppet to disconnect from
pass pass
@ -362,7 +374,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
super(PlayerDB, self).delete(*args, **kwargs) super(PlayerDB, self).delete(*args, **kwargs)
## methods inherited from database model ## methods inherited from database model
def msg(self, text=None, from_obj=None, sessid=None, **kwargs): def msg(self, text=None, from_obj=None, session=None, **kwargs):
""" """
Evennia -> User Evennia -> User
This is the main route for sending data back to the user from the This is the main route for sending data back to the user from the
@ -370,15 +382,18 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
Args: Args:
text (str, optional): text data to send text (str, optional): text data to send
from_obj (Object or Player, optional): object sending. If given, from_obj (Object or Player, optional): Object sending. If given,
its at_msg_send() hook will be called. its at_msg_send() hook will be called.
sessid (int or list, optional): session id or ids to receive this session (Session or list, optional): Session object or a list of
send. If given, overrules MULTISESSION_MODE. Sessions to receive this send. If given, overrules the
default send behavior for the current
MULTISESSION_MODE.
Notes: Notes:
All other keywords are passed on to the protocol. All other keywords are passed on to the protocol.
""" """
text = to_str(text, force_string=True) if text else "" text = to_str(text, force_string=True) if text else ""
if from_obj: if from_obj:
# call hook # call hook
try: try:
@ -387,23 +402,11 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
pass pass
# session relay # session relay
if sessid: sessions = make_iter(session) if session else self.sessions.all()
# this could still be an iterable if sessid is an iterable for session in sessions:
sessions = self.get_session(sessid) session.msg(text=text, **kwargs)
if sessions:
# this is a special instruction to ignore MULTISESSION_MODE
# and only relay to this given session.
kwargs["_nomulti"] = True
for session in make_iter(sessions):
session.msg(text=text, **kwargs)
return
# we only send to the first of any connected sessions - the sessionhandler
# will disperse this to the other sessions based on MULTISESSION_MODE.
sessions = self.get_all_sessions()
if sessions:
sessions[0].msg(text=text, **kwargs)
def execute_cmd(self, raw_string, sessid=None, **kwargs): def execute_cmd(self, raw_string, session=None, **kwargs):
""" """
Do something as this player. This method is never called normally, Do something as this player. This method is never called normally,
but only when the player object itself is supposed to execute the but only when the player object itself is supposed to execute the
@ -412,8 +415,8 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
Args: Args:
raw_string (str): Raw command input coming from the command line. raw_string (str): Raw command input coming from the command line.
sessid (int, optional): The optional session id to be session (Session, optional): The session to be responsible
responsible for the command-send for the command-send
Kwargs: Kwargs:
kwargs (any): Other keyword arguments will be added to the kwargs (any): Other keyword arguments will be added to the
@ -426,17 +429,12 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
raw_string = to_unicode(raw_string) raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string, raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=False) categories=("inputline", "channel"), include_player=False)
if not sessid and _MULTISESSION_MODE in (0, 1): if not session and _MULTISESSION_MODE in (0, 1):
# in this case, we should either have only one sessid, or the sessid # for these modes we use the
# should not matter (since the return goes to all of them we can session
# 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, return cmdhandler.cmdhandler(self, raw_string,
callertype="player", sessid=sessid, **kwargs) callertype="player", session=session, **kwargs)
def search(self, searchdata, return_puppet=False, def search(self, searchdata, return_puppet=False,
nofound_string=None, multimatch_string=None, **kwargs): nofound_string=None, multimatch_string=None, **kwargs):
@ -675,7 +673,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
else: else:
logger.log_info("[%s]: %s" % (now, message)) logger.log_info("[%s]: %s" % (now, message))
def at_post_login(self, sessid=None): def at_post_login(self, session=None):
""" """
Called at the end of the login process, just before letting Called at the end of the login process, just before letting
the player loose. the player loose.
@ -693,14 +691,14 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
if _MULTISESSION_MODE == 0: if _MULTISESSION_MODE == 0:
# in this mode we should have only one character available. We # in this mode we should have only one character available. We
# try to auto-connect to our last conneted object, if any # try to auto-connect to our last conneted object, if any
self.puppet_object(sessid, self.db._last_puppet) self.puppet_object(session, self.db._last_puppet)
elif _MULTISESSION_MODE == 1: elif _MULTISESSION_MODE == 1:
# in this mode all sessions connect to the same puppet. # in this mode all sessions connect to the same puppet.
self.puppet_object(sessid, self.db._last_puppet) self.puppet_object(session, self.db._last_puppet)
elif _MULTISESSION_MODE in (2, 3): elif _MULTISESSION_MODE in (2, 3):
# In this mode we by default end up at a character selection # In this mode we by default end up at a character selection
# screen. We execute look on the player. # screen. We execute look on the player.
self.msg(self.at_look(sessid=sessid)) self.msg(self.at_look(session=session))
def at_failed_login(self, session): def at_failed_login(self, session):
""" """
@ -765,7 +763,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
""" """
pass pass
def at_look(self, target=None, sessid=None): def at_look(self, target=None, session=None):
""" """
Called when this object executes a look. It allows to customize Called when this object executes a look. It allows to customize
just what this means. just what this means.
@ -773,7 +771,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
Args: Args:
target (Object or list, optional): An object or a list target (Object or list, optional): An object or a list
objects to inspect. objects to inspect.
sessid (int, optional): Id of the session doing this look. session (Session, optional): The session doing this look.
Returns: Returns:
look_string (str): A prepared look string, ready to send look_string (str): A prepared look string, ready to send
@ -799,7 +797,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
for isess, sess in enumerate(sessions): for isess, sess in enumerate(sessions):
csessid = sess.sessid csessid = sess.sessid
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address)) addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address))
string += "\n %s %s" % (sessid == csessid and "{w%s{n" % (isess + 1) or (isess + 1), addr) string += "\n %s %s" % (session.sessid == csessid and "{w%s{n" % (isess + 1) or (isess + 1), addr)
string += "\n\n {whelp{n - more commands" string += "\n\n {whelp{n - more commands"
string += "\n {wooc <Text>{n - talk on public channel" string += "\n {wooc <Text>{n - talk on public channel"
@ -821,16 +819,14 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "") charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "")
for char in characters: for char in characters:
csessid = char.sessid.get() csessions = char.sessions.all()
if csessid: for sess in csessions:
# character is already puppeted # character is already puppeted
sessi = self.get_session(csessid) sid = sess in sessions and sessions.index(sess) + 1
for sess in make_iter(sessi): if sess and sid:
sid = sess in sessions and sessions.index(sess) + 1 string += "\n - {G%s{n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid)
if sess and sid: else:
string += "\n - {G%s{n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid) string += "\n - {R%s{n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all()))
else:
string += "\n - {R%s{n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all()))
else: else:
# character is "free to puppet" # character is "free to puppet"
string += "\n - %s [%s]" % (char.key, ", ".join(char.permissions.all())) string += "\n - %s [%s]" % (char.key, ", ".join(char.permissions.all()))
@ -843,7 +839,7 @@ class DefaultGuest(DefaultPlayer):
This class is used for guest logins. Unlike Players, Guests and This class is used for guest logins. Unlike Players, Guests and
their characters are deleted after disconnection. their characters are deleted after disconnection.
""" """
def at_post_login(self, sessid=None): def at_post_login(self, session=None):
""" """
In theory, guests only have one character regardless of which In theory, guests only have one character regardless of which
MULTISESSION_MODE we're in. They don't get a choice. MULTISESSION_MODE we're in. They don't get a choice.
@ -853,7 +849,7 @@ class DefaultGuest(DefaultPlayer):
""" """
self._send_to_connect_channel("{G%s connected{n" % self.key) self._send_to_connect_channel("{G%s connected{n" % self.key)
self.puppet_object(sessid, self.db._last_puppet) self.puppet_object(session, self.db._last_puppet)
def at_disconnect(self): def at_disconnect(self):
""" """

View file

@ -396,9 +396,7 @@ class ServerSession(Session):
from evennia.utils import ansi as _ANSI from evennia.utils import ansi as _ANSI
text = _ANSI.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False) text = _ANSI.parse_ansi(text, strip_ansi=True, xterm256=False, mxp=False)
text = _RE_SCREENREADER_REGEX.sub("", text) text = _RE_SCREENREADER_REGEX.sub("", text)
session = kwargs.pop('session', None) self.sessionhandler.data_out(self, text=text, **kwargs)
session = session or self
self.sessionhandler.data_out(session, text=text, **kwargs)
# alias # alias
msg = data_out msg = data_out

View file

@ -517,53 +517,19 @@ class ServerSessionHandler(SessionHandler):
Sending data Server -> Portal Sending data Server -> Portal
Args: Args:
session (Session): Session object session (Session): Session to relay to.
text (str, optional): text data to return text (str, optional): text data to return
_nomulti (bool, optional): if given, only this
session will receive the rest of the data,
regardless of MULTISESSION_MODE. This is an
internal variable that will not be passed on.
This is ignored for MULTISESSION_MODE = 1,
since all messages are mirrored everywhere for
that.
_forced_nomulti (bool, optional): Like _nomulti,
but works even when MULTISESSION_MODE = 1.
Useful for connection handling messages.
""" """
#from evennia.server.profiling.timetrace import timetrace #from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "ServerSessionHandler.data_out") #text = timetrace(text, "ServerSessionHandler.data_out")
sessions = make_iter(session)
session = sessions[0]
text = text and to_str(to_unicode(text), encoding=session.encoding) text = text and to_str(to_unicode(text), encoding=session.encoding)
multi = not kwargs.pop("_nomulti", None)
forced_nomulti = kwargs.pop("_forced_nomulti", None)
# Mode 1 mirrors to all.
if _MULTISESSION_MODE == 1:
multi = True
# ...Unless we're absolutely sure.
if forced_nomulti:
multi = False
if multi: # send across AMP
if _MULTISESSION_MODE == 1: self.server.amp_protocol.send_MsgServer2Portal(sessid=session.sessid,
if session.player: text=text,
sessions = self.sessions_from_player(session.player) **kwargs)
if _MULTISESSION_MODE == 2:
if session.player:
sessions = self.sessions_from_player(session.player)
elif _MULTISESSION_MODE == 3:
if session.puppet:
sessions = self.sessions_from_puppet(session.puppet)
elif session.player:
sessions = self.sessions_from_player(session.player)
# send to all found sessions
for session in sessions:
self.server.amp_protocol.send_MsgServer2Portal(sessid=session.sessid,
text=text,
**kwargs)
def data_in(self, sessid, text="", **kwargs): def data_in(self, sessid, text="", **kwargs):
""" """