From 26ced2cb90f3d73fd30406f724f914a92f199ab3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 9 Apr 2013 15:59:21 +0200 Subject: [PATCH] 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. --- src/commands/default/general.py | 52 ++-- src/objects/models.py | 3 +- src/players/models.py | 443 ++++++++++++++++++-------------- src/players/player.py | 2 +- src/server/server.py | 4 +- src/server/serversession.py | 35 ++- src/server/session.py | 10 +- src/server/sessionhandler.py | 23 +- 8 files changed, 321 insertions(+), 251 deletions(-) diff --git a/src/commands/default/general.py b/src/commands/default/general.py index defe862841..1127e781f7 100644 --- a/src/commands/default/general.py +++ b/src/commands/default/general.py @@ -403,6 +403,7 @@ class CmdQuit(MuxCommand): def func(self): "hook function" + # always operate on the player if hasattr(self.caller, "player"): player = self.caller.player else: @@ -772,7 +773,7 @@ class CmdColorTest(MuxCommand): #------------------------------------------------------------ -# OOC commands +# Player commands # # Note that in commands inheriting from MuxCommandOOC, # self.caller is always the Player object, not the Character. @@ -803,15 +804,14 @@ class CmdOOCLook(MuxCommandOOC, CmdLook): def look_target(self): "Hook method for when an argument is given." - # caller is assumed to be a player object here. - caller = self.caller + player = self.caller 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) if looktarget: - caller.msg(looktarget.return_appearance(caller)) + self.msg(looktarget.return_appearance(caller)) else: - caller.msg("No such character.") + self.msg("No such character.") return def no_look_target(self): @@ -887,7 +887,7 @@ class CmdCharCreate(MuxCommandOOC): "create the new character" player = self.caller if not self.args: - player.msg("Usage: @charcreate [= description]") + self.msg("Usage: @charcreate [= description]") return key = self.lhs desc = self.rhs @@ -941,44 +941,43 @@ class CmdIC(MuxCommandOOC): """ Simple puppet method """ - caller = self.caller + player = self.caller sessid = self.sessid old_character = self.character new_character = None if not self.args: - new_character = caller.db._last_puppet + new_character = player.db._last_puppet if not new_character: self.msg("Usage: @ic ") return if not new_character: # search for a matching character - new_character = search.objects(self.args, caller) + new_character = search.objects(self.args, player) if new_character: new_character = new_character[0] else: self.msg("That is not a valid character choice.") return # 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) return if new_character.player: - if new_character.sessid == sessid: - self.msg("{RYou already act as {c%s{n." % new_character.name) - return - elif new_character.sessid and new_character.player == caller: + # may not puppet an already puppeted character + if new_character.sessid and new_character.player == player: self.msg("{RYou already act as {c%s{n in another session." % new_character.name) 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) 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) 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) - caller.db._last_puppet = old_character + player.db._last_puppet = old_character if not new_character.location: # this might be due to being hidden away at logout; check loc = new_character.db.prelogout_location @@ -1012,15 +1011,16 @@ class CmdOOC(MuxCommandOOC): def func(self): "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: string = "You are already OOC." self.msg(string) return - caller.db._last_puppet = old_char + player.db._last_puppet = old_char # save location as if we were disconnecting from the game entirely. if old_char.location: 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 # disconnect - err = caller.disconnect_character(self.character) - self.msg("\n{GYou go OOC.{n\n") - caller.execute_cmd("look") + if player.unpuppet_object(sessid): + self.msg("\n{GYou go OOC.{n\n") + player.execute_cmd("look") + else: + raise RuntimeError("Could not unpuppet!") diff --git a/src/objects/models.py b/src/objects/models.py index 2229203751..6502f39b52 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -147,7 +147,8 @@ class ObjectDB(TypedObject): ndb - non-persistent attribute storage 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 home - safety location for object (handler) diff --git a/src/players/models.py b/src/players/models.py index bf7a512399..b7ccf9ae1c 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -288,24 +288,6 @@ class PlayerDB(TypedObject): raise Exception("User id cannot be deleted!") 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 def is_superuser_get(self): "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 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 hook returns false, cancel send return @@ -354,24 +336,29 @@ class PlayerDB(TypedObject): for sess in _GA(self, 'get_all_sessions')(): sess.msg(outgoing_string, data) - def inmsg(self, ingoing_string, sessid): + def inmsg(self, ingoing_string, session): """ User -> Evennia 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. ingoing_string - text string (i.e. command string) 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) - if character: - # execute command on character - _GA(character, "execute_cmd")(ingoing_string, sessid=sessid) + puppet = session.puppet + if puppet: + # execute command on the puppeted object (this will include + # cmdsets both on object and on player) + _GA(puppet, "execute_cmd")(ingoing_string, sessid=session.sessid) else: - # a non-character session; this goes to player directly - _GA(self, "execute_cmd")(ingoing_string, sessid=sessid) + # a non-character session; this executes on player directly + # (this will only include cmdsets on player) + _GA(self, "execute_cmd")(ingoing_string, sessid=session.sessid) + + + # session-related methods def get_session(self, sessid): """ @@ -380,7 +367,7 @@ class PlayerDB(TypedObject): global _SESSIONS if not _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): "Return all sessions connected to this player" @@ -388,208 +375,272 @@ class PlayerDB(TypedObject): 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 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): """ - 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) - for session in make_iter(sessions): - # this will also trigger disconnection of character(s) + # this should only be one value, loop just to make sure to clean everything + sessions = (session for session in self.get_all_sessions(sessid) if session.sessid == sessid) + for session in sessions: + # this will also trigger unpuppeting session.sessionhandler.disconnect(session) - def connect_session_to_character(self, sessid, character, force=False, call_hooks=True): - """ - 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(). + # puppeting operations - 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 + def puppet_object(self, sessid, obj, normal_mode=True): """ - # 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) + 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(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 - 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")() + obj.sessid = 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: + _GA(obj.typeclass, "at_post_puppet")() return True - def disconnect_session_from_character(self, sessid): + def unpuppet_object(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 + Disengage control over an object - 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) + sessid - the session id to disengage - - def get_character(self, sessid=None, character=None, return_dbobj=False): + returns True if successful """ - Get the character connected to this player and sessid. This is the main - method for retrieving the character from the player's end. + 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 + _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, 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 {} - if sessid: - # try to return a character with a given sessid - char = cache.get(sessid) - if not char: - char = _GA(self, "db_objs").filter(db_player=self, db_sessid=sessid) 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 + session = self.get_session(sessid) + if not session: + return None + if return_dbobj: + return session.puppet + return session.puppet and session.puppet.typeclass or None - 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 - to them through this player + Checks of this player currently puppets this object or not """ - chars = make_iter(_GA(self, "get_character")(sessid=None, character=None)) - sessids = [sess.sessid for sess in _GA(self, "get_all_sessions")()] - return [char for char in chars if char.sessid in sessids] + return obj in self.get_all_puppets() - def connect_character(self, character, sessid=None): - """ - 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. +# def connect_session_to_character(self, sessid, character, force=False, call_hooks=True): +# """ +# 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 +# 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. - """ - 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 get_all_characters(self): +# """ +# Readability-wrapper for getting all characters +# """ +# return _GA(self, "get_character")(sessid=None, character=None) +# +# def get_all_connected_characters(self): +# """ +# Return all characters with an active session connected +# to them through this player +# """ +# chars = make_iter(_GA(self, "get_character")(sessid=None, character=None)) +# sessids = [sess.sessid for sess in _GA(self, "get_all_sessions")()] +# return [char for char in chars if char.sessid in sessids] - def disconnect_all_characters(self): - for char in self.db_objs.all(): - _GA(self, "disconnect_character")(char) +# def connect_character(self, character, sessid=None): +# """ +# 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): - "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: if _GA(self, "user"): _GA(_GA(self, "user"), "delete")() diff --git a/src/players/player.py b/src/players/player.py index bb96345ba2..25edda446b 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -327,7 +327,7 @@ class Player(TypeClass): print "player at_post_login", self 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 # this as a backup when logging in without a character self.execute_cmd("look") diff --git a/src/server/server.py b/src/server/server.py index 23c24bad7e..738900e908 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -268,13 +268,13 @@ class Evennia(object): else: 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()] else: # shutdown 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 [(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 ObjectDB.objects.clear_all_sessids() ServerConfig.objects.conf("server_restart_mode", "reset") diff --git a/src/server/serversession.py b/src/server/serversession.py index 41e47ab0b3..29faa7fc14 100644 --- a/src/server/serversession.py +++ b/src/server/serversession.py @@ -18,6 +18,7 @@ from src.server.session import Session IDLE_COMMAND = settings.IDLE_COMMAND _GA = object.__getattribute__ +_ObjectDB = None # load optional out-of-band function 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 the session as it was. """ + global _ObjectDB + if not _ObjectDB: + from src.objects.models import ObjectDB as _ObjectDB + if not self.logged_in: # assign the unloggedin-command set. self.cmdset = cmdsethandler.CmdSetHandler(self) self.cmdset_storage = [settings.CMDSET_UNLOGGEDIN] self.cmdset.update(init_mode=True) - return - else: - self.player.server_reconnect_session_to_character(self.sessid) + elif self.puid: + self.puppet = None + obj = _ObjectDB.objects.get(id=self.puid) + self.player.puppet_object(self.sessid, obj, normal_mode=False) def at_login(self, player): """ @@ -72,6 +78,8 @@ class ServerSession(Session): self.uname = self.user.username self.logged_in = True self.conn_time = time.time() + self.puid = None + self.puppet = None # Update account's last login time. self.user.last_login = datetime.now() @@ -85,7 +93,7 @@ class ServerSession(Session): sessid = self.sessid player = self.player 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.last_login = datetime.now() uaccount.save() @@ -102,12 +110,13 @@ class ServerSession(Session): """ return self.logged_in and self.player - def get_character(self): + def get_puppet(self): """ Returns the in-game character associated with this session. 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): """ @@ -135,9 +144,11 @@ class ServerSession(Session): # Player-visible idle time, not used in idle timeout calcs. 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 if str(command_string).strip() == IDLE_COMMAND: @@ -145,11 +156,12 @@ class ServerSession(Session): return if self.logged_in: # the inmsg handler will relay to the right place - self.player.inmsg(command_string, sessid=self.sessid) + self.player.inmsg(command_string, self) 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) cmdhandler.cmdhandler(self, command_string, sessid=self.sessid) + execute_cmd = data_in # alias def data_out(self, msg, data=None): """ @@ -157,6 +169,7 @@ class ServerSession(Session): """ self.sessionhandler.data_out(self, msg, data) + def oob_data_in(self, data): """ This receives out-of-band data from the Portal. @@ -249,7 +262,7 @@ class ServerSession(Session): 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): "dummy hook all objects with cmdsets need to have" diff --git a/src/server/session.py b/src/server/session.py index d76cc0c821..d2866a2abb 100644 --- a/src/server/session.py +++ b/src/server/session.py @@ -34,9 +34,9 @@ class Session(object): # names of attributes that should be affected by syncing. _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', - 'server_data') + 'protocol_flags', 'server_data') def init_session(self, protocol_key, address, sessionhandler): """ @@ -63,15 +63,15 @@ class Session(object): # if user has authenticated already or not self.logged_in = False - # database id of character/object connected to this player session (if any) - self.cid = None - self.encoding = "utf-8" + # database id of puppeted object (if any) + self.puid = None # session time statistics self.conn_time = time.time() self.cmd_last_visible = self.conn_time self.cmd_last = self.conn_time self.cmd_total = 0 + self.encoding = "utf-8" self.protocol_flags = {} self.server_data = {} diff --git a/src/server/sessionhandler.py b/src/server/sessionhandler.py index 26c017d0a1..ef1631a544 100644 --- a/src/server/sessionhandler.py +++ b/src/server/sessionhandler.py @@ -138,7 +138,7 @@ class ServerSessionHandler(SessionHandler): # validate all script _ScriptDB.objects.validate() self.sessions[sess.sessid] = sess - sess.execute_cmd(CMD_LOGINSTART) + sess.data_in(CMD_LOGINSTART) def portal_disconnect(self, sessid): """ @@ -196,7 +196,7 @@ class ServerSessionHandler(SessionHandler): Called from server side to remove session and inform portal of this fact. """ - session = self.sessions.get(session.sessid, None) + session = self.sessions.get(session.sessid) if session: session.at_disconnect() 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)) - 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 - if sessid: - 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] + return [session for session in self.sessions.values() if session.logged_in and session.uid == uid] def sessions_from_character(self, character): """ @@ -345,7 +348,7 @@ class ServerSessionHandler(SessionHandler): """ session = self.sessions.get(sessid, None) if session: - session.execute_cmd(string) + session.data_in(string) # ignore 'data' argument for now; this is otherwise the place # to put custom effects on the server due to data input, e.g.