Heavily reworked the many-char system, cleaner and more consistent by not having any persistent links on the Object side once a player has unconnected.

This commit is contained in:
Griatch 2013-04-09 15:59:21 +02:00
parent 5100a0561f
commit 26ced2cb90
8 changed files with 321 additions and 251 deletions

View file

@ -403,6 +403,7 @@ class CmdQuit(MuxCommand):
def func(self): def func(self):
"hook function" "hook function"
# always operate on the player
if hasattr(self.caller, "player"): if hasattr(self.caller, "player"):
player = self.caller.player player = self.caller.player
else: else:
@ -772,7 +773,7 @@ class CmdColorTest(MuxCommand):
#------------------------------------------------------------ #------------------------------------------------------------
# OOC commands # Player commands
# #
# Note that in commands inheriting from MuxCommandOOC, # Note that in commands inheriting from MuxCommandOOC,
# self.caller is always the Player object, not the Character. # self.caller is always the Player object, not the Character.
@ -803,15 +804,14 @@ class CmdOOCLook(MuxCommandOOC, CmdLook):
def look_target(self): def look_target(self):
"Hook method for when an argument is given." "Hook method for when an argument is given."
# caller is assumed to be a player object here. player = self.caller
caller = self.caller
key = self.args.lower() key = self.args.lower()
chars = dict((utils.to_str(char.key.lower()), char) for char in caller.db._playable_characters) chars = dict((utils.to_str(char.key.lower()), char) for char in player.db._playable_characters)
looktarget = chars.get(key) looktarget = chars.get(key)
if looktarget: if looktarget:
caller.msg(looktarget.return_appearance(caller)) self.msg(looktarget.return_appearance(caller))
else: else:
caller.msg("No such character.") self.msg("No such character.")
return return
def no_look_target(self): def no_look_target(self):
@ -887,7 +887,7 @@ class CmdCharCreate(MuxCommandOOC):
"create the new character" "create the new character"
player = self.caller player = self.caller
if not self.args: if not self.args:
player.msg("Usage: @charcreate <charname> [= description]") self.msg("Usage: @charcreate <charname> [= description]")
return return
key = self.lhs key = self.lhs
desc = self.rhs desc = self.rhs
@ -941,44 +941,43 @@ class CmdIC(MuxCommandOOC):
""" """
Simple puppet method Simple puppet method
""" """
caller = self.caller player = self.caller
sessid = self.sessid sessid = self.sessid
old_character = self.character old_character = self.character
new_character = None new_character = None
if not self.args: if not self.args:
new_character = caller.db._last_puppet new_character = player.db._last_puppet
if not new_character: if not new_character:
self.msg("Usage: @ic <character>") self.msg("Usage: @ic <character>")
return return
if not new_character: if not new_character:
# search for a matching character # search for a matching character
new_character = search.objects(self.args, caller) new_character = search.objects(self.args, player)
if new_character: if new_character:
new_character = new_character[0] new_character = new_character[0]
else: else:
self.msg("That is not a valid character choice.") self.msg("That is not a valid character choice.")
return return
# permission checks # permission checks
if caller.get_character(sessid=sessid, character=new_character): if player.get_puppet(sessid) == new_character:
self.msg("{RYou already act as {c%s{n." % new_character.name) self.msg("{RYou already act as {c%s{n." % new_character.name)
return return
if new_character.player: if new_character.player:
if new_character.sessid == sessid: # may not puppet an already puppeted character
self.msg("{RYou already act as {c%s{n." % new_character.name) if new_character.sessid and new_character.player == player:
return
elif new_character.sessid and new_character.player == caller:
self.msg("{RYou already act as {c%s{n in another session." % new_character.name) self.msg("{RYou already act as {c%s{n in another session." % new_character.name)
return return
elif not caller.get_character(character=new_character): elif new_character.player != player and new_character.player.is_connected:
self.msg("{c%s{r is already acted by another player.{n" % new_character.name) self.msg("{c%s{r is already acted by another player.{n" % new_character.name)
return return
if not new_character.access(caller, "puppet"): if not new_character.access(player, "puppet"):
# main acccess check
self.msg("{rYou may not become %s.{n" % new_character.name) self.msg("{rYou may not become %s.{n" % new_character.name)
return return
if caller.connect_character(new_character, sessid=sessid): if player.puppet_object(sessid, new_character):
self.msg("\n{gYou become {c%s{n.\n" % new_character.name) self.msg("\n{gYou become {c%s{n.\n" % new_character.name)
caller.db._last_puppet = old_character player.db._last_puppet = old_character
if not new_character.location: if not new_character.location:
# this might be due to being hidden away at logout; check # this might be due to being hidden away at logout; check
loc = new_character.db.prelogout_location loc = new_character.db.prelogout_location
@ -1012,15 +1011,16 @@ class CmdOOC(MuxCommandOOC):
def func(self): def func(self):
"Implement function" "Implement function"
caller = self.caller player = self.caller
sessid = self.sessid
old_char = caller.get_character(sessid=self.sessid) old_char = player.get_puppet(sessid)
if not old_char: if not old_char:
string = "You are already OOC." string = "You are already OOC."
self.msg(string) self.msg(string)
return return
caller.db._last_puppet = old_char player.db._last_puppet = old_char
# save location as if we were disconnecting from the game entirely. # save location as if we were disconnecting from the game entirely.
if old_char.location: if old_char.location:
old_char.location.msg_contents("%s has left the game." % old_char.key, exclude=[old_char]) old_char.location.msg_contents("%s has left the game." % old_char.key, exclude=[old_char])
@ -1028,7 +1028,9 @@ class CmdOOC(MuxCommandOOC):
old_char.location = None old_char.location = None
# disconnect # disconnect
err = caller.disconnect_character(self.character) if player.unpuppet_object(sessid):
self.msg("\n{GYou go OOC.{n\n") self.msg("\n{GYou go OOC.{n\n")
caller.execute_cmd("look") player.execute_cmd("look")
else:
raise RuntimeError("Could not unpuppet!")

View file

@ -147,7 +147,8 @@ class ObjectDB(TypedObject):
ndb - non-persistent attribute storage ndb - non-persistent attribute storage
The ObjectDB adds the following properties: The ObjectDB adds the following properties:
player - optional connected player player - optional connected player (always together with sessid)
sessid - optional connection session id (always together with player)
location - in-game location of object location - in-game location of object
home - safety location for object (handler) home - safety location for object (handler)

View file

@ -288,24 +288,6 @@ class PlayerDB(TypedObject):
raise Exception("User id cannot be deleted!") raise Exception("User id cannot be deleted!")
uid = property(uid_get, uid_set, uid_del) uid = property(uid_get, uid_set, uid_del)
# sessions property
#@property
def sessions_get(self):
"Getter. Retrieve sessions related to this player/user"
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.sessions_from_player(self)
#@sessions.setter
def sessions_set(self, value):
"Setter. Protects the sessions property from adding things"
raise Exception("Cannot set sessions manually!")
#@sessions.deleter
def sessions_del(self):
"Deleter. Protects the sessions property from deletion"
raise Exception("Cannot delete sessions manually!")
sessions = property(sessions_get, sessions_set, sessions_del)
#@property #@property
def is_superuser_get(self): def is_superuser_get(self):
"Superusers have all permissions." "Superusers have all permissions."
@ -344,7 +326,7 @@ class PlayerDB(TypedObject):
session = _MULTISESSION_MODE == 2 and sessid and _GA(self, "get_session")(sessid) or None session = _MULTISESSION_MODE == 2 and sessid and _GA(self, "get_session")(sessid) or None
if session: if session:
char = _GA(self, "get_character")(sessid=sessid) obj = session.puppet
if char and not char.at_msg_receive(outgoing_string, from_obj=from_obj, data=data): if char and not char.at_msg_receive(outgoing_string, from_obj=from_obj, data=data):
# if hook returns false, cancel send # if hook returns false, cancel send
return return
@ -354,24 +336,29 @@ class PlayerDB(TypedObject):
for sess in _GA(self, 'get_all_sessions')(): for sess in _GA(self, 'get_all_sessions')():
sess.msg(outgoing_string, data) sess.msg(outgoing_string, data)
def inmsg(self, ingoing_string, sessid): def inmsg(self, ingoing_string, session):
""" """
User -> Evennia User -> Evennia
This is the reverse of msg - used by sessions to relay This is the reverse of msg - used by sessions to relay
messages/data back into the game. It is normally not called messages/data back into the game. It is not called
from inside game code but only by the serversessions directly. from inside game code but only by the serversessions directly.
ingoing_string - text string (i.e. command string) ingoing_string - text string (i.e. command string)
data - dictionary of optional data data - dictionary of optional data
sessid - session sending this data session - session sending this data (no need to look it up again)
""" """
character = _GA(self, "get_character")(sessid=sessid) puppet = session.puppet
if character: if puppet:
# execute command on character # execute command on the puppeted object (this will include
_GA(character, "execute_cmd")(ingoing_string, sessid=sessid) # cmdsets both on object and on player)
_GA(puppet, "execute_cmd")(ingoing_string, sessid=session.sessid)
else: else:
# a non-character session; this goes to player directly # a non-character session; this executes on player directly
_GA(self, "execute_cmd")(ingoing_string, sessid=sessid) # (this will only include cmdsets on player)
_GA(self, "execute_cmd")(ingoing_string, sessid=session.sessid)
# session-related methods
def get_session(self, sessid): def get_session(self, sessid):
""" """
@ -380,7 +367,7 @@ class PlayerDB(TypedObject):
global _SESSIONS global _SESSIONS
if not _SESSIONS: if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.sessions_from_player(self, sessid=sessid) return _SESSIONS.session_from_player(self, sessid)
def get_all_sessions(self): def get_all_sessions(self):
"Return all sessions connected to this player" "Return all sessions connected to this player"
@ -388,208 +375,272 @@ class PlayerDB(TypedObject):
if not _SESSIONS: if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.sessions_from_player(self) return _SESSIONS.sessions_from_player(self)
sessions = property(get_all_sessions) # alias shortcut
def get_session_from_sessid(self, sessid):
"""
Get the session object from sessid. If session with sessid is not
connected to this player, return None.
"""
global _SESSIONS
if not _SESSIONS:
from src.server.sessionhandler import SESSIONS as _SESSIONS
return _SESSIONS.sessions_from_player(self, sessid=sessid)
def disconnect_session_from_player(self, sessid): def disconnect_session_from_player(self, sessid):
""" """
Access method for disconnecting a given session from the player. Access method for disconnecting a given session from the player
(connection happens automatically in the sessionhandler)
""" """
sessions = self.get_session_from_sessid(sessid) # this should only be one value, loop just to make sure to clean everything
for session in make_iter(sessions): sessions = (session for session in self.get_all_sessions(sessid) if session.sessid == sessid)
# this will also trigger disconnection of character(s) for session in sessions:
# this will also trigger unpuppeting
session.sessionhandler.disconnect(session) session.sessionhandler.disconnect(session)
def connect_session_to_character(self, sessid, character, force=False, call_hooks=True): # puppeting operations
"""
Connect the given session to a character through this player.
Note that this assumes the character has previously been
linked to the player using self.connect_character().
force - drop existing connection to other character def puppet_object(self, sessid, obj, normal_mode=True):
call_hooks - call puppet/unpuppet hooks. This is not wanted e.g. if
server is reloading
Returns True if connection was successful, False otherwise
""" """
# first check if we already have a character tied to this session Use the given session to control (puppet) the given object (usually
char = _GA(self, "get_character")(sessid=sessid, return_dbobj=True) a Character type). Note that we make no puppet checks here, that must
if char: have been done before calling this method.
if force and char != character:
_GA(self, "disconnect_session_from_character")(sessid) sessid - session id of session to connect
else: obj - the object to connect to
return normal_mode - trigger hooks and extra checks - this is turned off when
# pre-puppet hook the server reloads, to quickly re-connect puppets.
if call_hooks:
# if e.g. server reloads we don't want to call any hooks anew returns True if successful, False otherwise
_GA(character.typeclass, "at_pre_puppet")(self.typeclass) """
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(self, 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:
_GA(obj.typeclass, "at_pre_puppet")(self.typeclass)
# do the connection # do the connection
character.sessid = sessid obj.sessid = sessid
# update cache obj.player = self
cache = get_prop_cache(self, "_characters") or {} session.puid = obj.id
cache[sessid] = character session.puppet = obj
set_prop_cache(self, "_characters", cache) # validate/start persistent scripts on object
# start/validate (persistent) scripts on this object ScriptDB.objects.validate(obj=obj)
ScriptDB.objects.validate(obj=character) if normal_mode:
# post-puppet hook _GA(obj.typeclass, "at_post_puppet")()
if call_hooks:
_GA(character.typeclass, "at_post_puppet")()
return True return True
def disconnect_session_from_character(self, sessid): def unpuppet_object(self, sessid):
""" """
Disconnect a session from the characterm (still keeping the Disengage control over an object
connection to the Player)
returns the newly disconnected character, if it existed
"""
print "player disconnect_session_from_character", sessid
if not sessid:
return
char = _GA(self, "get_character")(sessid=sessid, return_dbobj=True)
print char
if char:
# call hook before disconnecting
_GA(char.typeclass, "at_pre_unpuppet")()
del char.sessid
# update cache
cache = get_prop_cache(self, "_characters") or {}
if sessid in cache:
del cache[sessid]
set_prop_cache(self, "_characters", cache)
# call post-unpuppet hook
_GA(char.typeclass, "at_post_unpuppet")(self.typeclass)
print "... leaving player disconnect_session_from_character", sessid
return char
def server_reconnect_session_to_character(self, sessid): sessid - the session id to disengage
"""
Auto-re-connect a session to a character. This is called by the sessionhandler
during a server reload. It goes through the characters stored in this player's
db_objs many2many fields and checks if any of those has the given sessid
stored on themselves - if so they connect them. This should ONLY be called
automatically by sessionhandler after a reload - after a portal shutdown
the portal sessids will be out of sync with whatever is stored on character
objects which could lead to a session being linked to the wrong character.
"""
char = _GA(self, "get_character")(sessid=sessid, return_dbobj=True)
if not char:
return
_GA(self, "connect_session_to_character")(sessid, char, force=True, call_hooks=False)
returns True if successful
def get_character(self, sessid=None, character=None, return_dbobj=False):
""" """
Get the character connected to this player and sessid. This is the main session = self.get_session(sessid)
method for retrieving the character from the player's end. if not session:
return False
obj = hasattr(session, "puppet") and session.puppet or None
if not obj:
return False
# do the disconnect
_GA(obj.typeclass, "at_pre_unpuppet")()
del obj.dbobj.sessid
del obj.dbobj.player
session.puppet = None
session.puid = None
_GA(obj.typeclass, "at_post_unpuppet")(self)
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, sessid - return character connected to this sessid,
character - return character if connected to this player, else None. character - return character if connected to this player, else None.
Combining both keywords will check the entire connection - if the
given session is currently connected to the given char. If no
keywords are given, returns all connected characters as a list.
""" """
cache = get_prop_cache(self, "_characters") or {} session = self.get_session(sessid)
if sessid: if not session:
# try to return a character with a given sessid return None
char = cache.get(sessid) if return_dbobj:
if not char: return session.puppet
char = _GA(self, "db_objs").filter(db_player=self, db_sessid=sessid) or None return session.puppet and session.puppet.typeclass or None
if char:
char = char[0]
cache[sessid] = char
set_prop_cache(self, "_characters", cache)
if character:
return char and (char == character.dbobj and (return_dbobj and char or char.typeclass)) or None
return char and (return_dbobj and char or char.typeclass) or None
elif character:
char = _GA(self, "db_objs").filter(id=_GA(character.dbobj, "id"))
return char and (return_dbobj and char[0] or char[0].typeclass) or None
else:
# no sessid given - return all available characters
chars = list(return_dbobj and o or o.typeclass for o in self.db_objs.all())
return len(chars) == 1 and chars[0] or chars
def get_all_characters(self): def get_all_puppets(self, return_dbobj=False):
""" """
Readability-wrapper for getting all characters Get all currently puppeted objects as a list
""" """
return _GA(self, "get_character")(sessid=None, character=None) puppets = [session.puppet for session in self.get_all_sessions() if session.puppet]
if return_dbobj:
return puppets
return [puppet.typeclass for puppet in puppets]
def get_all_connected_characters(self): def has_puppet(self, obj):
""" """
Return all characters with an active session connected Checks of this player currently puppets this object or not
to them through this player
""" """
chars = make_iter(_GA(self, "get_character")(sessid=None, character=None)) return obj in self.get_all_puppets()
sessids = [sess.sessid for sess in _GA(self, "get_all_sessions")()]
return [char for char in chars if char.sessid in sessids]
def connect_character(self, character, sessid=None): # def connect_session_to_character(self, sessid, character, force=False, call_hooks=True):
""" # """
Use the Player to connect a Character to the Player. Note that # Connect the given session to a character through this player.
we don't do any access checks at this point. If the # Note that this assumes the character has previously been
game was fully restarted (including the Portal), this must be # linked to the player using self.connect_character().
used, since sessids will have changed as players reconnect. #
# force - drop existing connection to other character
# call_hooks - call puppet/unpuppet hooks. This is not wanted e.g. if
# server is reloading
#
# Returns True if connection was successful, False otherwise
# """
# # first check if we already have a character tied to this session
# char = _GA(self, "get_character")(sessid=sessid, return_dbobj=True)
# if char:
# if force and char != character:
# _GA(self, "disconnect_session_from_character")(sessid)
# else:
# return
# # pre-puppet hook
# if call_hooks:
# # if e.g. server reloads we don't want to call any hooks anew
# _GA(character.typeclass, "at_pre_puppet")(self.typeclass)
# # do the connection
# character.sessid = sessid
# # update cache
# cache = get_prop_cache(self, "_characters") or {}
# cache[sessid] = character
# set_prop_cache(self, "_characters", cache)
# # start/validate (persistent) scripts on this object
# ScriptDB.objects.validate(obj=character)
# # post-puppet hook
# if call_hooks:
# _GA(character.typeclass, "at_post_puppet")()
# return True
#
# def disconnect_session_from_character(self, sessid):
# """
# Disconnect a session from the characterm (still keeping the
# connection to the Player)
# returns the newly disconnected character, if it existed
# """
# print "player disconnect_session_from_character", sessid
# if not sessid:
# return
# char = _GA(self, "get_character")(sessid=sessid, return_dbobj=True)
# print char
# if char:
# # call hook before disconnecting
# _GA(char.typeclass, "at_pre_unpuppet")()
# del char.sessid
# # update cache
# cache = get_prop_cache(self, "_characters") or {}
# if sessid in cache:
# del cache[sessid]
# set_prop_cache(self, "_characters", cache)
# # call post-unpuppet hook
# _GA(char.typeclass, "at_post_unpuppet")(self.typeclass)
# print "... leaving player disconnect_session_from_character", sessid
# return char
#
# def server_reconnect_session_to_character(self, sessid):
# """
# Auto-re-connect a session to a character. This is called by the sessionhandler
# during a server reload. It goes through the characters stored in this player's
# db_objs many2many fields and checks if any of those has the given sessid
# stored on themselves - if so they connect them. This should ONLY be called
# automatically by sessionhandler after a reload - after a portal shutdown
# the portal sessids will be out of sync with whatever is stored on character
# objects which could lead to a session being linked to the wrong character.
# """
# char = _GA(self, "get_character")(sessid=sessid, return_dbobj=True)
# if not char:
# return
# _GA(self, "connect_session_to_character")(sessid, char, force=True, call_hooks=False)
if sessid is given, also connect the sessid to the character directly.
"""
# first disconnect any other character from this session
char = character.dbobj
_GA(self, "disconnect_character")(char)
char.player = self
_GA(self, "db_objs").add(char)
_GA(self, "save")()
if sessid:
return _GA(self, "connect_session_to_character")(sessid=sessid, character=char)
return True
def disconnect_character(self, character):
"""
Disconnect a character from this player, either based
on sessid or by giving the character object directly
Returns newly disconnected character. # def get_all_characters(self):
""" # """
if not character: # Readability-wrapper for getting all characters
return # """
char = _GA(self, "get_character")(character=character, return_dbobj=True) # return _GA(self, "get_character")(sessid=None, character=None)
if char: #
err = _GA(self, "disconnect_session_from_character")(char.sessid) # def get_all_connected_characters(self):
_GA(self, "db_objs").remove(char) # """
del char.player # Return all characters with an active session connected
self.save() # to them through this player
# clear cache # """
cache = get_prop_cache(self, "_characters") or {} # chars = make_iter(_GA(self, "get_character")(sessid=None, character=None))
[cache.pop(sessid) for sessid,stored_char in cache.items() if stored_char==char] # sessids = [sess.sessid for sess in _GA(self, "get_all_sessions")()]
set_prop_cache(self, "_characters", cache) # return [char for char in chars if char.sessid in sessids]
return char
def disconnect_all_characters(self): # def connect_character(self, character, sessid=None):
for char in self.db_objs.all(): # """
_GA(self, "disconnect_character")(char) # Use the Player to connect a Character to the Player. Note that
# we don't do any access checks at this point. If the
# game was fully restarted (including the Portal), this must be
# used, since sessids will have changed as players reconnect.
#
# if sessid is given, also connect the sessid to the character directly.
# """
# # first disconnect any other character from this session
# char = character.dbobj
# _GA(self, "disconnect_character")(char)
# char.player = self
# _GA(self, "db_objs").add(char)
# _GA(self, "save")()
# if sessid:
# return _GA(self, "connect_session_to_character")(sessid=sessid, character=char)
# return True
#
# def disconnect_character(self, character):
# """
# Disconnect a character from this player, either based
# on sessid or by giving the character object directly
#
# Returns newly disconnected character.
# """
# if not character:
# return
# char = _GA(self, "get_character")(character=character, return_dbobj=True)
# if char:
# err = _GA(self, "disconnect_session_from_character")(char.sessid)
# _GA(self, "db_objs").remove(char)
# del char.player
# self.save()
# # clear cache
# cache = get_prop_cache(self, "_characters") or {}
# [cache.pop(sessid) for sessid,stored_char in cache.items() if stored_char==char]
# set_prop_cache(self, "_characters", cache)
# return char
#
# def disconnect_all_characters(self):
# for char in self.db_objs.all():
# _GA(self, "disconnect_character")(char)
def swap_character(self, old_character, new_character):
"""
Swaps character between sessions, if possible
"""
this_sessid = old_character.sessid
other_sessd = new_character.sessid
this_char = _GA(self, "disconnect_session_from_character")(this_sessid)
other_char = _GA(self, "disconnect_session_from_character")(other_sessid)
_GA(self, "connect_session_to_character")(this_sessid, other_char)
_GA(self, "connect_session_to_character")(other_sessid, this_char)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
"Make sure to delete user also when deleting player - the two may never exist separately." """
Deletes the player permanently.
Makes sure to delete user also when deleting player - the two may never exist separately.
"""
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."))
try: try:
if _GA(self, "user"): if _GA(self, "user"):
_GA(_GA(self, "user"), "delete")() _GA(_GA(self, "user"), "delete")()

View file

@ -327,7 +327,7 @@ class Player(TypeClass):
print "player at_post_login", self print "player at_post_login", self
self._send_to_connect_channel("{G%s connected{n" % self.key) self._send_to_connect_channel("{G%s connected{n" % self.key)
if _MULTISESSION_MODE == 2 or not self.get_all_characters(): if _MULTISESSION_MODE == 2:
# Character.at_post_login also looks around. Only use # Character.at_post_login also looks around. Only use
# this as a backup when logging in without a character # this as a backup when logging in without a character
self.execute_cmd("look") self.execute_cmd("look")

View file

@ -268,13 +268,13 @@ class Evennia(object):
else: else:
if mode == 'reset': if mode == 'reset':
# don't call disconnect hooks on reset # don't unset the is_connected flag on reset, otherwise same as shutdown
yield [(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()] yield [(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
else: # shutdown else: # shutdown
yield [_SA(p, "is_connected", False) for p in PlayerDB.get_all_cached_instances()] yield [_SA(p, "is_connected", False) for p in PlayerDB.get_all_cached_instances()]
yield [(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()] yield [(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
yield [(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()] yield [(p.typeclass, p.unpuppet_all(), p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
yield [(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()] yield [(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
yield ObjectDB.objects.clear_all_sessids() yield ObjectDB.objects.clear_all_sessids()
ServerConfig.objects.conf("server_restart_mode", "reset") ServerConfig.objects.conf("server_restart_mode", "reset")

View file

@ -18,6 +18,7 @@ from src.server.session import Session
IDLE_COMMAND = settings.IDLE_COMMAND IDLE_COMMAND = settings.IDLE_COMMAND
_GA = object.__getattribute__ _GA = object.__getattribute__
_ObjectDB = None
# load optional out-of-band function module # load optional out-of-band function module
OOB_FUNC_MODULE = settings.OOB_FUNC_MODULE OOB_FUNC_MODULE = settings.OOB_FUNC_MODULE
@ -51,14 +52,19 @@ class ServerSession(Session):
Since this is often called after a server restart we need to set up Since this is often called after a server restart we need to set up
the session as it was. the session as it was.
""" """
global _ObjectDB
if not _ObjectDB:
from src.objects.models import ObjectDB as _ObjectDB
if not self.logged_in: if not self.logged_in:
# assign the unloggedin-command set. # assign the unloggedin-command set.
self.cmdset = cmdsethandler.CmdSetHandler(self) self.cmdset = cmdsethandler.CmdSetHandler(self)
self.cmdset_storage = [settings.CMDSET_UNLOGGEDIN] self.cmdset_storage = [settings.CMDSET_UNLOGGEDIN]
self.cmdset.update(init_mode=True) self.cmdset.update(init_mode=True)
return elif self.puid:
else: self.puppet = None
self.player.server_reconnect_session_to_character(self.sessid) obj = _ObjectDB.objects.get(id=self.puid)
self.player.puppet_object(self.sessid, obj, normal_mode=False)
def at_login(self, player): def at_login(self, player):
""" """
@ -72,6 +78,8 @@ class ServerSession(Session):
self.uname = self.user.username self.uname = self.user.username
self.logged_in = True self.logged_in = True
self.conn_time = time.time() self.conn_time = time.time()
self.puid = None
self.puppet = None
# Update account's last login time. # Update account's last login time.
self.user.last_login = datetime.now() self.user.last_login = datetime.now()
@ -85,7 +93,7 @@ class ServerSession(Session):
sessid = self.sessid sessid = self.sessid
player = self.player player = self.player
print "session at_disconnect", self print "session at_disconnect", self
_GA(player.dbobj, "disconnect_session_from_character")(sessid) _GA(player.dbobj, "unpuppet_object")(sessid)
uaccount = _GA(player.dbobj, "user") uaccount = _GA(player.dbobj, "user")
uaccount.last_login = datetime.now() uaccount.last_login = datetime.now()
uaccount.save() uaccount.save()
@ -102,12 +110,13 @@ class ServerSession(Session):
""" """
return self.logged_in and self.player return self.logged_in and self.player
def get_character(self): def get_puppet(self):
""" """
Returns the in-game character associated with this session. Returns the in-game character associated with this session.
This returns the typeclass of the object. This returns the typeclass of the object.
""" """
return self.logged_in and self.player.get_character(self.sessid) or None return self.logged_in and self.puppet
get_character = get_puppet
def log(self, message, channel=True): def log(self, message, channel=True):
""" """
@ -135,9 +144,11 @@ class ServerSession(Session):
# Player-visible idle time, not used in idle timeout calcs. # Player-visible idle time, not used in idle timeout calcs.
self.cmd_last_visible = time.time() self.cmd_last_visible = time.time()
def execute_cmd(self, command_string): def data_in(self, command_string):
""" """
Execute a command string on the server. Send Player->Evennia. This will in effect
execute a command string on the server.
Eventual extra data moves through oob_data_in
""" """
# handle the 'idle' command # handle the 'idle' command
if str(command_string).strip() == IDLE_COMMAND: if str(command_string).strip() == IDLE_COMMAND:
@ -145,11 +156,12 @@ class ServerSession(Session):
return return
if self.logged_in: if self.logged_in:
# the inmsg handler will relay to the right place # the inmsg handler will relay to the right place
self.player.inmsg(command_string, sessid=self.sessid) self.player.inmsg(command_string, self)
else: else:
# we are not logged in. Use the session directly # we are not logged in. Execute cmd with the the session directly
# (it uses the settings.UNLOGGEDIN cmdset) # (it uses the settings.UNLOGGEDIN cmdset)
cmdhandler.cmdhandler(self, command_string, sessid=self.sessid) cmdhandler.cmdhandler(self, command_string, sessid=self.sessid)
execute_cmd = data_in # alias
def data_out(self, msg, data=None): def data_out(self, msg, data=None):
""" """
@ -157,6 +169,7 @@ class ServerSession(Session):
""" """
self.sessionhandler.data_out(self, msg, data) self.sessionhandler.data_out(self, msg, data)
def oob_data_in(self, data): def oob_data_in(self, data):
""" """
This receives out-of-band data from the Portal. This receives out-of-band data from the Portal.
@ -249,7 +262,7 @@ class ServerSession(Session):
self.data_out(string, data=data) self.data_out(string, data=data)
# Dummy API hooks for use a non-loggedin operation # Dummy API hooks for use during non-loggedin operation
def at_cmdset_get(self): def at_cmdset_get(self):
"dummy hook all objects with cmdsets need to have" "dummy hook all objects with cmdsets need to have"

View file

@ -34,9 +34,9 @@ class Session(object):
# names of attributes that should be affected by syncing. # names of attributes that should be affected by syncing.
_attrs_to_sync = ('protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname', _attrs_to_sync = ('protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding', 'logged_in', 'puid', 'encoding',
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total', 'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
'server_data') 'protocol_flags', 'server_data')
def init_session(self, protocol_key, address, sessionhandler): def init_session(self, protocol_key, address, sessionhandler):
""" """
@ -63,15 +63,15 @@ class Session(object):
# if user has authenticated already or not # if user has authenticated already or not
self.logged_in = False self.logged_in = False
# database id of character/object connected to this player session (if any) # database id of puppeted object (if any)
self.cid = None self.puid = None
self.encoding = "utf-8"
# session time statistics # session time statistics
self.conn_time = time.time() self.conn_time = time.time()
self.cmd_last_visible = self.conn_time self.cmd_last_visible = self.conn_time
self.cmd_last = self.conn_time self.cmd_last = self.conn_time
self.cmd_total = 0 self.cmd_total = 0
self.encoding = "utf-8"
self.protocol_flags = {} self.protocol_flags = {}
self.server_data = {} self.server_data = {}

View file

@ -138,7 +138,7 @@ class ServerSessionHandler(SessionHandler):
# validate all script # validate all script
_ScriptDB.objects.validate() _ScriptDB.objects.validate()
self.sessions[sess.sessid] = sess self.sessions[sess.sessid] = sess
sess.execute_cmd(CMD_LOGINSTART) sess.data_in(CMD_LOGINSTART)
def portal_disconnect(self, sessid): def portal_disconnect(self, sessid):
""" """
@ -196,7 +196,7 @@ class ServerSessionHandler(SessionHandler):
Called from server side to remove session and inform portal Called from server side to remove session and inform portal
of this fact. of this fact.
""" """
session = self.sessions.get(session.sessid, None) session = self.sessions.get(session.sessid)
if session: if session:
session.at_disconnect() session.at_disconnect()
sessid = session.sessid sessid = session.sessid
@ -305,16 +305,19 @@ class ServerSessionHandler(SessionHandler):
""" """
return len(set(session.uid for session in self.sessions.values() if session.logged_in)) return len(set(session.uid for session in self.sessions.values() if session.logged_in))
def sessions_from_player(self, player, sessid=None): def session_from_player(self, player, sessid):
""" """
Given a player, return any matching sessions. Given a player and a session id, return the actual session object
"""
session = self.sessions.get(sessid)
return session and session.logged_in and player.uid == session.uid and session or None
def sessions_from_player(self, player):
"""
Given a player, return all matching sessions.
""" """
uid = player.uid uid = player.uid
if sessid: return [session for session in self.sessions.values() if session.logged_in and session.uid == uid]
session = self.sessions.get(sessid)
return session and session.logged_in and session.uid == uid and session or None
else:
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_character(self, character):
""" """
@ -345,7 +348,7 @@ class ServerSessionHandler(SessionHandler):
""" """
session = self.sessions.get(sessid, None) session = self.sessions.get(sessid, None)
if session: if session:
session.execute_cmd(string) session.data_in(string)
# ignore 'data' argument for now; this is otherwise the place # ignore 'data' argument for now; this is otherwise the place
# to put custom effects on the server due to data input, e.g. # to put custom effects on the server due to data input, e.g.