diff --git a/src/objects/models.py b/src/objects/models.py index 21bcdda2d0..f0dd76a16c 100644 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -176,6 +176,9 @@ class ObjectDB(TypedObject): # If this is a character object, the player is connected here. db_player = models.ForeignKey("players.PlayerDB", blank=True, null=True, verbose_name='player', help_text='a Player connected to this object, if any.') + # the session id associated with this player, if any + db_sessid = models.IntegerField(null=True, verbose_name="session id", + help_text="unique session id of connected Player, if any." # The location in the game world. Since this one is likely # to change often, we set this with the 'location' property # to transparently handle Typeclassing. @@ -191,7 +194,6 @@ class ObjectDB(TypedObject): # database storage of persistant cmdsets. db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True, blank=True, help_text="optional python path to a cmdset class.") - # Database manager objects = ObjectManager() @@ -260,6 +262,30 @@ class ObjectDB(TypedObject): del_field_cache(self, "player") player = property(__player_get, __player_set, __player_del) + # sessid property (wraps db_sessid) + #@property + def __sessid_get(self): + """ + Getter. Allows for value = self.sessid. Since sessid + is directly related to self.player, we cannot have + a sessid without a player being connected (but the + opposite could be true). + """ + if not get_field_cache(self, "player"): + del_field_cache(self, "sessid") + return get_field_cache(self, "sessid") + + #@player.setter + def __sessid_set(self, player): + "Setter. Allows for self.player = value" + if inherits_from(player, TypeClass): + player = player.dbobj + set_field_cache(self, "player", player) + #@player.deleter + def __player_del(self): + "Deleter. Allows for del self.player" + del_field_cache(self, "player") + player = property(__player_get, __player_set, __player_del) # location property (wraps db_location) #@property def __location_get(self): diff --git a/src/players/models.py b/src/players/models.py index 4306a8476e..59ac2c4aaf 100644 --- a/src/players/models.py +++ b/src/players/models.py @@ -163,6 +163,7 @@ class PlayerDB(TypedObject): # Use the property 'obj' to access. db_obj = models.ForeignKey("objects.ObjectDB", null=True, blank=True, verbose_name="character", help_text='In-game object.') + db_objs = models.ManyToManyField("objects.ObjectDB", null=True, verbose_name="characters", help_text="In-game objects.") # store a connected flag here too, not just in sessionhandler. # This makes it easier to track from various out-of-process locations db_is_connected = models.BooleanField(default=False, verbose_name="is_connected", help_text="If player is connected to game or not") @@ -224,7 +225,7 @@ class PlayerDB(TypedObject): #@property def character_get(self): "Getter. Allows for value = self.character" - return get_field_cache(self, "obj") + return get_field_cache(self, "obj) #@character.setter def character_set(self, character): "Setter. Allows for self.character = value" @@ -236,6 +237,7 @@ class PlayerDB(TypedObject): "Deleter. Allows for del self.character" del_field_cache(self, "obj") character = property(character_get, character_set, character_del) + # cmdset_storage property # This seems very sensitive to caching, so leaving it be for now /Griatch #@property @@ -358,26 +360,111 @@ class PlayerDB(TypedObject): # PlayerDB class access methods # - def msg(self, outgoing_string, from_obj=None, data=None): + def msg(self, outgoing_string, from_obj=None, data=None, sessid=None): """ Evennia -> User This is the main route for sending data back to the user from the server. """ if from_obj: + # call hook try: _GA(from_obj, "at_msg_send")(outgoing_string, to_obj=self, data=data) except Exception: pass - if (_GA(self, "character") and not - _GA(self, "character").at_msg_receive(outgoing_string, from_obj=from_obj, data=data)): - # the at_msg_receive() hook may block receiving of certain messages - return - outgoing_string = utils.to_str(outgoing_string, force_string=True) - for session in _GA(self, 'sessions'): + session = None + if sessid: + session = _GA(self, "get_session")(sessid) + if session: + char = _GA(self, "get_character")(sessid) + if char and not char.at_msg_receive(outgoing_string, from_obj=from_obj, data=data)): + # if hook returns false, cancel send + return session.msg(outgoing_string, data) + else: + # if no session was specified, send to them all + for sess in _GA(self, 'get_sessions'): + sess.msg(outgoing_string, data) + def inmsg(self, ingoing_string, data, sessid): + """ + This is the reverse of msg - used by sessions to relay + messages/data back into the game. It is normally 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 + session - session sending this data + """ + character = _GA(self, "get_character")(sessid) + if character: + # execute command on character + _GA(character, "execute_cmd")(ingoing_string) + else: + # a non-character session; this goes to player directly + _GA(self, "execute_cmd")(ingoing_string) + + def connect_session(self, sessid): + """ + Connect session to this player to a session through + its session id. + """ + + def get_sessions(self, sessid=None): + """ + Return session with given sessid connected to this player. If sessid is + not given, return all connected sessions. + Note that this method will always return a list, even if it only has one + (or zero) element(s). + """ + return SESSIONS.get_session_from_player(self, sessid=sessid) + + def get_character(self, sessid): + """ + Get the character connected through this sessid, if any + """ + if not sessid: # sessid is always > 0 + return None + try: + char = get_prop_cache(self, "_characters").get(sessid) + except AttributeError: + set_prop_cache(self, "_characters", {}) + char = None + if not char: + char = self.db_objs.filter(player=self, sessid=sessid) + if char.count(): + chars_cache = get_prop_cache(self, "_characters") + chars_cache[sessid] = char[0] + set_prop_cache(self, "_characters") + return char + + def connect_character(self, character, sessid): + """ + Use the Player to connect a Character to a session. Note that + we don't do any access checks at this point. Note that if the + game was fully restarted (including the Portal), this must be + used, since sessids will have changed as players reconnect. + """ + character = character.dbobj + character.player = self + character.sessid = sessid + self.db_objs.add(character) + self.save() + + def disconnect_character(self, character): + """ + Disconnect a character from this player. + """ + character = character.dbobj + if self.db_objs.filter(id=_GA(character,"id")): + self.db_objs.remove(character) + character.player = None + character.sessid = None + self.save() + + def disconnect_all_characters(self): + for char in self.db_objs.all(): def swap_character(self, new_character, delete_old_character=False): """ diff --git a/src/server/sessionhandler.py b/src/server/sessionhandler.py index 46a5e58ee9..e171343095 100644 --- a/src/server/sessionhandler.py +++ b/src/server/sessionhandler.py @@ -228,12 +228,15 @@ class ServerSessionHandler(SessionHandler): """ return len(set(session.uid for session in self.sessions.values() if session.logged_in)) - def sessions_from_player(self, player): + def sessions_from_player(self, player, sessid=None): """ Given a player, return any matching sessions. """ uid = player.uid - return [session for session in self.sessions.values() if session.logged_in and session.uid == uid] + if sessid: + return [session for session in self.sessions.values() if session.logged_in and session.sessid == sessid and session.uid == uid] + else + return [session for session in self.sessions.values() if session.logged_in and session.uid == uid] def sessions_from_character(self, character): """