Started work on #673; refactored the data flow. Still issues with correctly puppeting/unpuppeting in modes > 0.

This commit is contained in:
Griatch 2015-02-26 21:53:19 +01:00
parent c0aafe74ab
commit a87fbff366
5 changed files with 141 additions and 102 deletions

View file

@ -14,7 +14,7 @@ access the character when these commands are triggered with
a connected character (such as the case of the @ooc command), it
is None if we are OOC.
Note that under MULTISESSION_MODE=2, Player- commands should use
Note that under MULTISESSION_MODE > 2, Player- commands should use
self.msg() and similar methods to reroute returns to the correct
method. Otherwise all text will be returned to all connected sessions.
@ -249,10 +249,11 @@ class CmdIC(MuxPlayerCommand):
else:
self.msg("That is not a valid character choice.")
return
if player.puppet_object(sessid, new_character):
try:
player.puppet_object(sessid, new_character)
player.db._last_puppet = new_character
else:
self.msg("{rYou cannot become {C%s{n." % new_character.name)
except RuntimeError, exc:
self.msg("{rYou cannot become {C%s{n: %s" % (new_character.name, exc))
class CmdOOC(MuxPlayerCommand):
@ -287,11 +288,12 @@ class CmdOOC(MuxPlayerCommand):
player.db._last_puppet = old_char
# disconnect
if player.unpuppet_object(sessid):
try:
player.unpuppet_object(sessid)
self.msg("\n{GYou go OOC.{n\n")
player.execute_cmd("look", sessid=sessid)
else:
raise RuntimeError("Could not unpuppet!")
except RuntimeError, exc:
self.msg("{rCould not unpuppet from {c%s{n: %s" % (old_char, exc))
class CmdSessions(MuxPlayerCommand):
"""

View file

@ -501,23 +501,19 @@ class DefaultObject(ObjectDB):
"""
Emits something to a session attached to the object.
message (str): The message to send
from_obj (obj): object that is sending.
data (object): an optional data object that may or may not
be used by the protocol.
sessid (int): sessid to relay to, if any.
If set to 0 (default), use either from_obj.sessid (if set) or self.sessid automatically
If None, echo to all connected sessions
When this message is called, from_obj.at_msg_send and self.at_msg_receive are called.
Args:
text (str, optional): The message to send
from_obj (obj, optional): object that is sending. If
given, at_msg_send will be called
sessid (int or list, optional): sessid or list of
sessids to relay to, if any. If set, will
force send regardless of MULTISESSION_MODE.
Notes:
`at_msg_receive` will be called on this Object.
All extra kwargs will be passed on to the protocol.
"""
global _SESSIONS
if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
text = to_str(text, force_string=True) if text else ""
if from_obj:
# call hook
try:
@ -531,9 +527,25 @@ class DefaultObject(ObjectDB):
except Exception:
log_trace()
sessions = _SESSIONS.session_from_sessid([sessid] if sessid else make_iter(self.sessid.get()))
for session in sessions:
session.msg(text=text, **kwargs)
# session relay
if self.player:
# for there to be a session there must be a Player.
if sessid:
# this could still be an iterable if sessid is.
sessions = self.player.get_session(sessid)
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.player.get_all_sessions()
if sessions:
sessions[0].msg(text=text, **kwargs)
def msg_contents(self, message, exclude=None, from_obj=None, **kwargs):
"""

View file

@ -166,26 +166,30 @@ class DefaultPlayer(PlayerDB):
Use the given session to control (puppet) the given object (usually
a Character type).
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.
Args:
sessid (int): session id of session to connect
obj (Object): the object to start puppeting
normal_mode (bool, optional): trigger hooks and extra
checks - this is turned off when the server reloads, to
quickly re-connect puppets.
Raises:
RuntimeError with message if puppeting is not possible
returns True if successful, False otherwise
"""
# safety checks
if not obj:
return
raise RuntimeError("Object not found")
session = self.get_session(sessid)
if not session:
return False
raise RuntimeError("Session not found")
if self.get_puppet(sessid) == obj:
# already puppeting this object
return
raise RuntimeError("You are already puppeting this object.")
if not obj.access(self, 'puppet'):
# no access
self.msg("You don't have permission to puppet '%s'." % obj.key)
return
raise RuntimeError("You don't have permission to puppet '%s'." % obj.key)
if normal_mode and obj.player:
# object already puppeted
if obj.player == self:
@ -198,13 +202,12 @@ class DefaultPlayer(PlayerDB):
else:
txt1 = "{c%s{n{R is now acted from another of your sessions.{n"
txt2 = "Taking over {c%s{n from another of your sessions."
self.unpuppet_object(obj.sessid.get())
self.msg(txt1 % obj.name, sessid=obj.sessid.get())
self.msg(txt2 % obj.name, sessid=sessid)
self.unpuppet_object(obj.sessid.get())
elif obj.player.is_connected:
# controlled by another player
self.msg("{R{c%s{R is already puppeted by another Player.")
return
raise RuntimeError("{R{c%s{R is already puppeted by another Player.")
# do the puppeting
if normal_mode and session.puppet:
@ -227,34 +230,38 @@ class DefaultPlayer(PlayerDB):
obj.at_post_puppet()
# re-cache locks to make sure superuser bypass is updated
obj.locks.cache_lock_bypass(obj)
return True
def unpuppet_object(self, sessid):
"""
Disengage control over an object
sessid - the session id to disengage
Args:
sessid(int): the session id to disengage
returns True if successful
Raises:
RuntimeError with message about error.
"""
session = self.get_session(sessid)
if not session:
return False
session = make_iter(session)[0]
#print "unpuppet, session:", session, session.puppet
obj = hasattr(session, "puppet") and session.puppet or None
#print "unpuppet, obj:", obj
if not obj:
return False
# do the disconnect, but only if we are the last session to puppet
obj.at_pre_unpuppet()
obj.sessid.remove(sessid)
if not obj.sessid.count():
del obj.player
obj.at_post_unpuppet(self, sessid=sessid)
session.puppet = None
session.puid = None
return True
if _MULTISESSION_MODE == 1:
sessions = self.get_all_sessions()
else:
sessions = self.get_session(sessid)
if not sessions:
raise RuntimeError("No session was found.")
for session in make_iter(sessions):
#print "unpuppet, session:", session, session.puppet
obj = hasattr(session, "puppet") and session.puppet or None
#print "unpuppet, obj:", obj
if not obj:
raise RuntimeError("No puppet was found to disconnect from.")
# do the disconnect, but only if we are the last session to puppet
obj.at_pre_unpuppet()
obj.sessid.remove(sessid)
if not obj.sessid.count():
del obj.player
obj.at_post_unpuppet(self, sessid=sessid)
session.puppet = None
session.puid = None
def unpuppet_all(self):
"""
@ -329,15 +336,14 @@ class DefaultPlayer(PlayerDB):
This is the main route for sending data back to the user from the
server.
outgoing_string (string) - text data to send
from_obj (Object/Player) - source object of message to send. Its
at_msg_send() hook will be called.
sessid - the session id of the session to send to. If not given, return
to all sessions connected to this player. This is usually only
relevant when using msg() directly from a player-command (from
a command on a Character, the character automatically stores
and handles the sessid). Can also be a list of sessids.
kwargs (dict) - All other keywords are parsed as extra data.
Args:
text (str, optional): text data to send
from_obj (Object or Player, optional): object sending. If given,
its at_msg_send() hook will be called.
sessid (int or list, optional): session id or ids to receive this
send. If given, overrules MULTISESSION_MODE.
Notes:
All other keywords are passed on to the protocol.
"""
text = to_str(text, force_string=True) if text else ""
if from_obj:
@ -346,18 +352,24 @@ class DefaultPlayer(PlayerDB):
from_obj.at_msg_send(text=text, to_obj=self, **kwargs)
except Exception:
pass
sessions = _MULTISESSION_MODE > 1 and sessid and self.get_session(sessid) or None
# session relay
if sessid:
# this could still be an iterable if sessid is an iterable
sessions = self.get_session(sessid)
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:
for session in make_iter(sessions):
obj = session.puppet
if obj and not obj.at_msg_receive(text=text, **kwargs):
# if hook returns false, cancel send
continue
session.msg(text=text, **kwargs)
else:
# if no session was specified, send to them all
for sess in self.get_all_sessions():
sess.msg(text=text, **kwargs)
sessions[0].msg(text=text, **kwargs)
def execute_cmd(self, raw_string, sessid=None, **kwargs):
"""
@ -629,12 +641,8 @@ class DefaultPlayer(PlayerDB):
# try to auto-connect to our last conneted object, if any
self.puppet_object(sessid, self.db._last_puppet)
elif _MULTISESSION_MODE == 1:
# in this mode the first session to connect acts like mode 0,
# the following sessions "share" the same view and should
# not perform any actions
if not self.get_all_puppets():
# we are first. Connect.
self.puppet_object(sessid, self.db._last_puppet)
# in this mode all sessions connect to the same puppet.
self.puppet_object(sessid, self.db._last_puppet)
elif _MULTISESSION_MODE in (2, 3):
# In this mode we by default end up at a character selection
# screen. We execute look on the player.

View file

@ -224,6 +224,8 @@ class ServerSession(Session):
if INLINEFUNC_ENABLED and not "raw" in kwargs:
text = parse_inlinefunc(text, strip="strip_inlinefunc" in kwargs, session=self)
self.sessionhandler.data_out(self, text=text, **kwargs)
# alias
msg = data_out
def __eq__(self, other):
return self.address == other.address
@ -251,18 +253,6 @@ class ServerSession(Session):
"""
return u"%s" % str(self)
# easy-access functions
#def login(self, player):
# "alias for at_login"
# self.session_login(player)
#def disconnect(self):
# "alias for session_disconnect"
# self.session_disconnect()
def msg(self, text='', **kwargs):
"alias for at_data_out"
self.data_out(text=text, **kwargs)
# Dummy API hooks for use during non-loggedin operation
def at_cmdset_get(self, **kwargs):

View file

@ -46,7 +46,7 @@ PCONNSYNC = chr(10) # portal post-syncing session
from django.utils.translation import ugettext as _
SERVERNAME = settings.SERVERNAME
MULTISESSION_MODE = settings.MULTISESSION_MODE
_MULTISESSION_MODE = settings.MULTISESSION_MODE
IDLE_TIMEOUT = settings.IDLE_TIMEOUT
@ -278,7 +278,7 @@ class ServerSessionHandler(SessionHandler):
player.at_pre_login()
if MULTISESSION_MODE == 0:
if _MULTISESSION_MODE == 0:
# disconnect all previous sessions.
self.disconnect_duplicate_sessions(session)
@ -418,14 +418,15 @@ class ServerSessionHandler(SessionHandler):
uid = player.uid
return [session for session in self.sessions.values() if session.logged_in and session.uid == uid]
def sessions_from_character(self, character):
def sessions_from_puppet(self, puppet):
"""
Given a game character, return any matching sessions.
Given a puppeted object, return all controlling sessions.
"""
sessid = character.sessid.get()
sessid = puppet.sessid.get()
if is_iter(sessid):
return [self.sessions.get(sess) for sess in sessid if sessid in self.sessions]
return [self.sessions.get(sid) for sid in sessid if sid in self.sessions]
return self.sessions.get(sessid)
sessions_from_character = sessions_from_puppet
def announce_all(self, message):
"""
@ -437,9 +438,35 @@ class ServerSessionHandler(SessionHandler):
def data_out(self, session, text="", **kwargs):
"""
Sending data Server -> Portal
Args:
session (Session): Session object
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.
"""
text = text and to_str(to_unicode(text), encoding=session.encoding)
self.server.amp_protocol.call_remote_MsgServer2Portal(sessid=session.sessid,
multi = not kwargs.pop("_nomulti", None)
sessions = [session]
if _MULTISESSION_MODE == 1:
if session.player:
sessions = self.sessions_from_player(session.player)
elif multi:
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.call_remote_MsgServer2Portal(sessid=session.sessid,
msg=text,
data=kwargs)