From b9661c5c963e14869e90b43806b3bfff69ecf2ad Mon Sep 17 00:00:00 2001 From: n0q Date: Sun, 6 Jul 2014 16:46:02 -0400 Subject: [PATCH 01/10] Guest Functionality Allows for guest logins by entering 'connect guest' at the login screen. Cleans up after itself but does not yet clean up stale guests from a system crash. --- src/commands/default/comms.py | 4 +- src/commands/default/player.py | 26 ++--- src/commands/default/unloggedin.py | 173 +++++++++++++++++++---------- src/players/player.py | 41 +++++++ src/settings_default.py | 13 +++ 5 files changed, 182 insertions(+), 75 deletions(-) diff --git a/src/commands/default/comms.py b/src/commands/default/comms.py index f77b7ad272..b69d898f6e 100644 --- a/src/commands/default/comms.py +++ b/src/commands/default/comms.py @@ -422,7 +422,7 @@ class CmdCemit(MuxPlayerCommand): key = "@cemit" aliases = ["@cmsg"] - locks = "cmd: not pperm(channel_banned)" + locks = "cmd: not pperm(channel_banned) and pperm(Players)" help_category = "Comms" def func(self): @@ -498,7 +498,7 @@ class CmdChannelCreate(MuxPlayerCommand): key = "@ccreate" aliases = "channelcreate" - locks = "cmd:not pperm(channel_banned)" + locks = "cmd:not pperm(channel_banned) and pperm(Players)" help_category = "Comms" def func(self): diff --git a/src/commands/default/player.py b/src/commands/default/player.py index c0c0980384..c49e691e9f 100644 --- a/src/commands/default/player.py +++ b/src/commands/default/player.py @@ -162,7 +162,7 @@ class CmdCharCreate(MuxPlayerCommand): if you want. """ key = "@charcreate" - locks = "cmd:all()" + locks = "cmd:pperm(Players)" help_category = "General" def func(self): @@ -285,7 +285,7 @@ class CmdOOC(MuxPlayerCommand): key = "@ooc" # lock must be all(), for different puppeted objects to access it. - locks = "cmd:all()" + locks = "cmd:pperm(Players)" aliases = "@unpuppet" help_category = "General" @@ -378,11 +378,9 @@ class CmdWho(MuxPlayerCommand): nplayers = (SESSIONS.player_count()) if show_session_data: - # privileged info table = prettytable.PrettyTable(["{wPlayer Name", "{wOn for", "{wIdle", - "{wPuppeting", "{wRoom", "{wCmds", "{wProtocol", @@ -391,27 +389,25 @@ class CmdWho(MuxPlayerCommand): if not session.logged_in: continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time - player = session.get_player() - puppet = session.get_puppet() - location = puppet.location.key if puppet else "None" - table.add_row([utils.crop(player.name, width=25), + plr_pobject = session.get_puppet() + plr_pobject = plr_pobject or session.get_player() + table.add_row([utils.crop(plr_pobject.name, width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1), - utils.crop(puppet.key if puppet else "None", width=25), - utils.crop(location, width=25), + hasattr(plr_pobject, "location") and plr_pobject.location and plr_pobject.location.key or "None", session.cmd_total, session.protocol_key, isinstance(session.address, tuple) and session.address[0] or session.address]) else: - # unprivileged table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"]) for session in session_list: if not session.logged_in: continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time - player = session.get_player() - table.add_row([utils.crop(player.key, width=25), + plr_pobject = session.get_puppet() + plr_pobject = plr_pobject or session.get_player() + table.add_row([utils.crop(plr_pobject.name, width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1)]) @@ -491,7 +487,7 @@ class CmdPassword(MuxPlayerCommand): Changes your password. Make sure to pick a safe one. """ key = "@password" - locks = "cmd:all()" + locks = "cmd:pperm(Players)" def func(self): "hook function." @@ -650,7 +646,7 @@ class CmdQuell(MuxPlayerCommand): key = "@quell" aliases = ["@unquell"] - locks = "cmd:all()" + locks = "cmd:pperm(Players)" help_category = "General" def _recache_locks(self, player): diff --git a/src/commands/default/unloggedin.py b/src/commands/default/unloggedin.py index a6cb7299ac..8bf60a5887 100644 --- a/src/commands/default/unloggedin.py +++ b/src/commands/default/unloggedin.py @@ -2,6 +2,7 @@ Commands that are available from the connect screen. """ import re +from random import getrandbits import traceback from django.conf import settings from src.players.models import PlayerDB @@ -53,14 +54,49 @@ class CmdUnconnectedConnect(MuxCommand): other types of logged-in commands (this is because there is no object yet before the player has logged in) """ - session = self.caller args = self.args # extract quoted parts parts = [part.strip() for part in re.split(r"\"|\'", args) if part.strip()] if len(parts) == 1: - # this was (hopefully) due to no quotes being found + # this was (hopefully) due to no quotes being found, or a guest login parts = parts[0].split(None, 1) + # Guest login + if len(parts) == 1 and parts[0].lower() == "guest" and settings.GUEST_LIST: + try: + # Find an available guest name. + for playername in settings.GUEST_LIST: + if not PlayerDB.objects.filter(username__iexact=playername): + break + playername = None + if playername == None: + session.msg("All guest accounts are in use. Please try again later.") + return + + password = "%016x" % getrandbits(64) + home = ObjectDB.objects.get_id(settings.GUEST_HOME) + permissions = settings.PERMISSION_GUEST_DEFAULT + typeclass = settings.BASE_CHARACTER_TYPECLASS + ptypeclass = settings.BASE_GUEST_TYPECLASS + start_location = ObjectDB.objects.get_id(settings.GUEST_START_LOCATION) + + new_player = CreatePlayer(session, playername, password, + home, permissions, ptypeclass) + if new_player: + CreateCharacter(session, new_player, typeclass, start_location, + home, permissions) + session.sessionhandler.login(session, new_player) + + except Exception: + # We are in the middle between logged in and -not, so we have + # to handle tracebacks ourselves at this point. If we don't, + # we won't see any errors at all. + string = "%s\nThis is a bug. Please e-mail an admin if the problem persists." + session.msg(string % (traceback.format_exc())) + logger.log_errmsg(traceback.format_exc()) + finally: + return + if len(parts) != 2: session.msg("\n\r Usage (without <>): connect ") return @@ -151,6 +187,11 @@ class CmdUnconnectedCreate(MuxCommand): # player already exists (we also ignore capitalization here) session.msg("Sorry, there is already a player with the name '%s'." % playername) return + # Reserve playernames found in GUEST_LIST + if settings.GUEST_LIST and playername.lower() in map(str.lower, settings.GUEST_LIST): + string = "\n\r That name is reserved. Please choose another Playername." + session.msg(string) + return if not re.findall('^[\w. @+-]+$', password) or not (3 < len(password)): string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @\.\+\-\_ only." string += "\nFor best security, make it longer than 8 characters. You can also use a phrase of" @@ -161,63 +202,22 @@ class CmdUnconnectedCreate(MuxCommand): # everything's ok. Create the new player account. try: default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) - - typeclass = settings.BASE_CHARACTER_TYPECLASS permissions = settings.PERMISSION_PLAYER_DEFAULT - - try: - new_player = create.create_player(playername, None, password, - permissions=permissions) - - except Exception, e: - session.msg("There was an error creating the default Player/Character:\n%s\n If this problem persists, contact an admin." % e) - logger.log_trace() - return - - # This needs to be called so the engine knows this player is - # logging in for the first time. (so it knows to call the right - # hooks during login later) - utils.init_new_player(new_player) - - # join the new player to the public channel - pchanneldef = settings.CHANNEL_PUBLIC - if pchanneldef: - pchannel = ChannelDB.objects.get_channel(pchanneldef[0]) - if not pchannel.connect(new_player): - string = "New player '%s' could not connect to public channel!" % new_player.key - logger.log_errmsg(string) - - if MULTISESSION_MODE < 2: - # if we only allow one character, create one with the same name as Player - # (in mode 2, the character must be created manually once logging in) - start_location = ObjectDB.objects.get_id(settings.START_LOCATION) - if not start_location: - start_location = default_home # fallback - - new_character = create.create_object(typeclass, key=playername, - location=start_location, home=default_home, - permissions=permissions) - # set playable character list - new_player.db._playable_characters.append(new_character) - - # allow only the character itself and the player to puppet this character (and Immortals). - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % - (new_character.id, new_player.id)) - - # If no description is set, set a default description - if not new_character.db.desc: - new_character.db.desc = "This is a Player." - # We need to set this to have @ic auto-connect to this character - new_player.db._last_puppet = new_character - - # tell the caller everything went well. - string = "A new account '%s' was created. Welcome!" - if " " in playername: - string += "\n\nYou can now log in with the command 'connect \"%s\" '." - else: - string += "\n\nYou can now log with the command 'connect %s '." - session.msg(string % (playername, playername)) - + typeclass = settings.BASE_CHARACTER_TYPECLASS + new_player = CreatePlayer(session, playername, password, default_home, permissions) + start_location = ObjectDB.objects.get_id(settings.START_LOCATION) + if new_player: + if MULTISESSION_MODE < 2: + CreateCharacter(session, new_player, typeclass, start_location, + default_home, permissions) + # tell the caller everything went well. + string = "A new account '%s' was created. Welcome!" + if " " in playername: + string += "\n\nYou can now log in with the command 'connect \"%s\" '." + else: + string += "\n\nYou can now log with the command 'connect %s '." + session.msg(string % (playername, playername)) + except Exception: # We are in the middle between logged in and -not, so we have # to handle tracebacks ourselves at this point. If we don't, @@ -225,6 +225,63 @@ class CmdUnconnectedCreate(MuxCommand): string = "%s\nThis is a bug. Please e-mail an admin if the problem persists." session.msg(string % (traceback.format_exc())) logger.log_errmsg(traceback.format_exc()) + + +def CreatePlayer(session, playername, password, + default_home, permissions, typeclass=None): + """ + Creates a player of the specified typeclass. + """ + try: + new_player = create.create_player(playername, None, password, + permissions=permissions, typeclass=typeclass) + + except Exception, e: + session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e) + logger.log_trace() + return False + + # This needs to be called so the engine knows this player is + # logging in for the first time. (so it knows to call the right + # hooks during login later) + utils.init_new_player(new_player) + + # join the new player to the public channel + pchanneldef = settings.CHANNEL_PUBLIC + if pchanneldef: + pchannel = ChannelDB.objects.get_channel(pchanneldef[0]) + if not pchannel.connect(new_player): + string = "New player '%s' could not connect to public channel!" % new_player.key + logger.log_errmsg(string) + return new_player + +def CreateCharacter(session, new_player, typeclass, start_location, home, permissions): + """ + Creates a character based on a player's name. This is meant for Guest and + MULTISESSION_MODE <2 situations. + """ + try: + if not start_location: + start_location = home # fallback + new_character = create.create_object(typeclass, key=new_player.key, + location=start_location, home=home, + permissions=permissions) + # set playable character list + new_player.db._playable_characters.append(new_character) + + # allow only the character itself and the player to puppet this character (and Immortals). + new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % + (new_character.id, new_player.id)) + + # If no description is set, set a default description + if not new_character.db.desc: + new_character.db.desc = "This is a Player." + # We need to set this to have @ic auto-connect to this character + new_player.db._last_puppet = new_character + except Exception, e: + session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) + logger.log_trace() + return False class CmdUnconnectedQuit(MuxCommand): diff --git a/src/players/player.py b/src/players/player.py index 9935a6c6fe..bfc618dc5d 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -423,3 +423,44 @@ class Player(TypeClass): (i.e. not for a restart). """ pass + +class Guest(Player): + """ + This class is used for guest logins. Unlike Players, Guests and their + characters are deleted after disconnection. + """ + print __doc__ + def at_post_login(self, sessid=None): + """ + In theory, guests only have one character regardless of which + MULTISESSION_MODE we're in. They don't get a choice. + """ + self._send_to_connect_channel("{G%s connected{n" % self.key) + self.execute_cmd("@ic", sessid=sessid) + + def at_disconnect(self): + """ + A Guest's characters aren't meant to linger on the server. When a + Guest disconnects, we remove its character. + """ + super(Guest, self).at_disconnect() + characters = self.db._playable_characters + for character in characters: + character.delete() + + def at_server_shutdown(self): + """ + We repeat at_disconnect() here just to be on the safe side. + """ + super(Guest, self).at_server_shutdown() + characters = self.db._playable_characters + for character in characters: + character.delete() + + def at_post_disconnect(self): + """ + Guests aren't meant to linger on the server, either. We need to wait + until after the Guest disconnects to delete it, though. + """ + super(Guest, self).at_post_disconnect() + self.delete() diff --git a/src/settings_default.py b/src/settings_default.py index 10dc7cde28..6bdb3a4374 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -287,6 +287,8 @@ CHANNEL_TYPECLASS_PATHS = ["game.gamesrc.conf", "contrib"] # Typeclass for player objects (linked to a character) (fallback) BASE_PLAYER_TYPECLASS = "src.players.player.Player" +# Typeclass for guest player objects (linked to a character) +BASE_GUEST_TYPECLASS = "src.players.player.Guest" # Typeclass and base for all objects (fallback) BASE_OBJECT_TYPECLASS = "src.objects.objects.Object" # Typeclass for character objects linked to a player (fallback) @@ -304,10 +306,14 @@ BASE_SCRIPT_TYPECLASS = "src.scripts.scripts.DoNothing" # fallback if an object's normal home location is deleted. Default # is Limbo (#2). DEFAULT_HOME = "#2" +# The default home location used for guests. +GUEST_HOME = "#2" # The start position for new characters. Default is Limbo (#2). # MULTISESSION_MODE = 0, 1 - used by default unloggedin create command # MULTISESSION_MODE = 2 - used by default character_create command START_LOCATION = "#2" +# The start position used for guest characters. +GUEST_START_LOCATION = "#2" # Lookups of Attributes, Tags, Nicks, Aliases can be aggressively # cached to avoid repeated database hits. This often gives noticeable # performance gains since they are called so often. Drawback is that @@ -376,6 +382,13 @@ PERMISSION_HIERARCHY = ("Players", "Immortals") # The default permission given to all new players PERMISSION_PLAYER_DEFAULT = "Players" +# The permission given to guests +PERMISSION_GUEST_DEFAULT = "Guests" +# The naming convention for guest players/characters. The size of this list +# also detemines how many guests may be on the game at once. The default is +# a maximum of five guests, named Guest1 through Guest5. +# Set to None to disable guest logins entirely. +GUEST_LIST = ["Guest" + str(s+1) for s in range(5)] ###################################################################### # In-game Channels created from server start From 757cfded99816f6ff1fa8a85fc3573c145876a56 Mon Sep 17 00:00:00 2001 From: n0q Date: Sun, 6 Jul 2014 16:53:42 -0400 Subject: [PATCH 02/10] Stray bit of code --- src/players/player.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/players/player.py b/src/players/player.py index bfc618dc5d..d408a32dc1 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -429,7 +429,6 @@ class Guest(Player): This class is used for guest logins. Unlike Players, Guests and their characters are deleted after disconnection. """ - print __doc__ def at_post_login(self, sessid=None): """ In theory, guests only have one character regardless of which From a8f9c4034e500c27a8c5406e3621475316662154 Mon Sep 17 00:00:00 2001 From: n0q Date: Sun, 6 Jul 2014 17:11:26 -0400 Subject: [PATCH 03/10] Porting changes to latest master Ported my changes to commands/default/player.py to latest evennia/evenna --- src/commands/default/player.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/commands/default/player.py b/src/commands/default/player.py index c49e691e9f..b50c3639e7 100644 --- a/src/commands/default/player.py +++ b/src/commands/default/player.py @@ -378,9 +378,11 @@ class CmdWho(MuxPlayerCommand): nplayers = (SESSIONS.player_count()) if show_session_data: + # privileged info table = prettytable.PrettyTable(["{wPlayer Name", "{wOn for", "{wIdle", + "{wPuppeting", "{wRoom", "{wCmds", "{wProtocol", @@ -389,25 +391,27 @@ class CmdWho(MuxPlayerCommand): if not session.logged_in: continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time - plr_pobject = session.get_puppet() - plr_pobject = plr_pobject or session.get_player() - table.add_row([utils.crop(plr_pobject.name, width=25), + player = session.get_player() + puppet = session.get_puppet() + location = puppet.location.key if puppet else "None" + table.add_row([utils.crop(player.name, width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1), - hasattr(plr_pobject, "location") and plr_pobject.location and plr_pobject.location.key or "None", + utils.crop(puppet.key if puppet else "None", width=25), + utils.crop(location, width=25), session.cmd_total, session.protocol_key, isinstance(session.address, tuple) and session.address[0] or session.address]) else: + # unprivileged table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"]) for session in session_list: if not session.logged_in: continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time - plr_pobject = session.get_puppet() - plr_pobject = plr_pobject or session.get_player() - table.add_row([utils.crop(plr_pobject.name, width=25), + player = session.get_player() + table.add_row([utils.crop(player.key, width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1)]) From de6badd70974aefd3d6896eccdf19f5636b86343 Mon Sep 17 00:00:00 2001 From: n0q Date: Mon, 7 Jul 2014 22:42:24 -0400 Subject: [PATCH 04/10] Fix for empty character sets Filtering away characters who's type is None, to avoid tracebacks in the event of a characterless guest. --- src/players/player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/players/player.py b/src/players/player.py index d408a32dc1..f303804c18 100644 --- a/src/players/player.py +++ b/src/players/player.py @@ -444,7 +444,7 @@ class Guest(Player): """ super(Guest, self).at_disconnect() characters = self.db._playable_characters - for character in characters: + for character in filter(None, characters): character.delete() def at_server_shutdown(self): @@ -453,7 +453,7 @@ class Guest(Player): """ super(Guest, self).at_server_shutdown() characters = self.db._playable_characters - for character in characters: + for character in filter(None, characters): character.delete() def at_post_disconnect(self): From f6b3535021a9d4426b60e715e2a6de423e630f93 Mon Sep 17 00:00:00 2001 From: n0q Date: Mon, 7 Jul 2014 23:34:07 -0400 Subject: [PATCH 05/10] Fixed settings_default.py compliance Guests are enabled and disabled with GUEST_ENABLED. --- src/commands/default/unloggedin.py | 118 +++++++++++++++-------------- src/settings_default.py | 11 +-- 2 files changed, 66 insertions(+), 63 deletions(-) diff --git a/src/commands/default/unloggedin.py b/src/commands/default/unloggedin.py index 8bf60a5887..e2486c6f98 100644 --- a/src/commands/default/unloggedin.py +++ b/src/commands/default/unloggedin.py @@ -54,6 +54,7 @@ class CmdUnconnectedConnect(MuxCommand): other types of logged-in commands (this is because there is no object yet before the player has logged in) """ + session = self.caller args = self.args # extract quoted parts @@ -62,7 +63,7 @@ class CmdUnconnectedConnect(MuxCommand): # this was (hopefully) due to no quotes being found, or a guest login parts = parts[0].split(None, 1) # Guest login - if len(parts) == 1 and parts[0].lower() == "guest" and settings.GUEST_LIST: + if len(parts) == 1 and parts[0].lower() == "guest" and settings.GUEST_ENABLED: try: # Find an available guest name. for playername in settings.GUEST_LIST: @@ -225,63 +226,6 @@ class CmdUnconnectedCreate(MuxCommand): string = "%s\nThis is a bug. Please e-mail an admin if the problem persists." session.msg(string % (traceback.format_exc())) logger.log_errmsg(traceback.format_exc()) - - -def CreatePlayer(session, playername, password, - default_home, permissions, typeclass=None): - """ - Creates a player of the specified typeclass. - """ - try: - new_player = create.create_player(playername, None, password, - permissions=permissions, typeclass=typeclass) - - except Exception, e: - session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e) - logger.log_trace() - return False - - # This needs to be called so the engine knows this player is - # logging in for the first time. (so it knows to call the right - # hooks during login later) - utils.init_new_player(new_player) - - # join the new player to the public channel - pchanneldef = settings.CHANNEL_PUBLIC - if pchanneldef: - pchannel = ChannelDB.objects.get_channel(pchanneldef[0]) - if not pchannel.connect(new_player): - string = "New player '%s' could not connect to public channel!" % new_player.key - logger.log_errmsg(string) - return new_player - -def CreateCharacter(session, new_player, typeclass, start_location, home, permissions): - """ - Creates a character based on a player's name. This is meant for Guest and - MULTISESSION_MODE <2 situations. - """ - try: - if not start_location: - start_location = home # fallback - new_character = create.create_object(typeclass, key=new_player.key, - location=start_location, home=home, - permissions=permissions) - # set playable character list - new_player.db._playable_characters.append(new_character) - - # allow only the character itself and the player to puppet this character (and Immortals). - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % - (new_character.id, new_player.id)) - - # If no description is set, set a default description - if not new_character.db.desc: - new_character.db.desc = "This is a Player." - # We need to set this to have @ic auto-connect to this character - new_player.db._last_puppet = new_character - except Exception, e: - session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) - logger.log_trace() - return False class CmdUnconnectedQuit(MuxCommand): @@ -376,3 +320,61 @@ To login to the system, you need to do one of the following: You can use the {wlook{n command if you want to see the connect screen again. """ self.caller.msg(string) + + +def CreatePlayer(session, playername, password, + default_home, permissions, typeclass=None): + """ + Creates a player of the specified typeclass. + """ + try: + new_player = create.create_player(playername, None, password, + permissions=permissions, typeclass=typeclass) + + except Exception, e: + session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e) + logger.log_trace() + return False + + # This needs to be called so the engine knows this player is + # logging in for the first time. (so it knows to call the right + # hooks during login later) + utils.init_new_player(new_player) + + # join the new player to the public channel + pchanneldef = settings.CHANNEL_PUBLIC + if pchanneldef: + pchannel = ChannelDB.objects.get_channel(pchanneldef[0]) + if not pchannel.connect(new_player): + string = "New player '%s' could not connect to public channel!" % new_player.key + logger.log_errmsg(string) + return new_player + + +def CreateCharacter(session, new_player, typeclass, start_location, home, permissions): + """ + Creates a character based on a player's name. This is meant for Guest and + MULTISESSION_MODE <2 situations. + """ + try: + if not start_location: + start_location = home # fallback + new_character = create.create_object(typeclass, key=new_player.key, + location=start_location, home=home, + permissions=permissions) + # set playable character list + new_player.db._playable_characters.append(new_character) + + # allow only the character itself and the player to puppet this character (and Immortals). + new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % + (new_character.id, new_player.id)) + + # If no description is set, set a default description + if not new_character.db.desc: + new_character.db.desc = "This is a Player." + # We need to set this to have @ic auto-connect to this character + new_player.db._last_puppet = new_character + except Exception, e: + session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) + logger.log_trace() + return False \ No newline at end of file diff --git a/src/settings_default.py b/src/settings_default.py index 6bdb3a4374..1913c789e1 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -306,6 +306,8 @@ BASE_SCRIPT_TYPECLASS = "src.scripts.scripts.DoNothing" # fallback if an object's normal home location is deleted. Default # is Limbo (#2). DEFAULT_HOME = "#2" +# This enables guest logins. +GUEST_ENABLED = True # The default home location used for guests. GUEST_HOME = "#2" # The start position for new characters. Default is Limbo (#2). @@ -314,6 +316,10 @@ GUEST_HOME = "#2" START_LOCATION = "#2" # The start position used for guest characters. GUEST_START_LOCATION = "#2" +# The naming convention for guest players/characters. The size of this list +# also detemines how many guests may be on the game at once. The default is +# a maximum of five guests, named Guest1 through Guest5. +GUEST_LIST = ["Guest" + str(s+1) for s in range(9)] # Lookups of Attributes, Tags, Nicks, Aliases can be aggressively # cached to avoid repeated database hits. This often gives noticeable # performance gains since they are called so often. Drawback is that @@ -384,11 +390,6 @@ PERMISSION_HIERARCHY = ("Players", PERMISSION_PLAYER_DEFAULT = "Players" # The permission given to guests PERMISSION_GUEST_DEFAULT = "Guests" -# The naming convention for guest players/characters. The size of this list -# also detemines how many guests may be on the game at once. The default is -# a maximum of five guests, named Guest1 through Guest5. -# Set to None to disable guest logins entirely. -GUEST_LIST = ["Guest" + str(s+1) for s in range(5)] ###################################################################### # In-game Channels created from server start From d43c003cc8e5a595eefa89a0b1aaac82e97e83c2 Mon Sep 17 00:00:00 2001 From: n0q Date: Mon, 7 Jul 2014 23:37:11 -0400 Subject: [PATCH 06/10] Guest cleanup and server hooks Outstanding guest objects (say, from crashes) are now cleaned up at startup. Copied the at_server_startstop.py hooks directly into Server object, and set them to reference SERVER_STARTSTOP_MODULE. This seemed to be the cleanest way to go about things. --- src/server/server.py | 88 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/src/server/server.py b/src/server/server.py index 5e7166ce1e..42c2bafa0b 100644 --- a/src/server/server.py +++ b/src/server/server.py @@ -73,6 +73,8 @@ AMP_INTERFACE = settings.AMP_INTERFACE WEBSERVER_PORTS = settings.WEBSERVER_PORTS WEBSERVER_INTERFACES = settings.WEBSERVER_INTERFACES +GUEST_ENABLED = settings.GUEST_ENABLED + # server-channel mappings WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES IMC2_ENABLED = settings.IMC2_ENABLED @@ -240,17 +242,16 @@ class Evennia(object): from src.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.restore() - if SERVER_STARTSTOP_MODULE: - # call correct server hook based on start file value - if mode in ('True', 'reload'): - # True was the old reload flag, kept for compatibilty - SERVER_STARTSTOP_MODULE.at_server_reload_start() - elif mode in ('reset', 'shutdown'): - SERVER_STARTSTOP_MODULE.at_server_cold_start() - # clear eventual lingering session storages - ObjectDB.objects.clear_all_sessids() - # always call this regardless of start type - SERVER_STARTSTOP_MODULE.at_server_start() + # call correct server hook based on start file value + if mode in ('True', 'reload'): + # True was the old reload flag, kept for compatibilty + self.at_server_reload_start() + elif mode in ('reset', 'shutdown'): + self.at_server_cold_start() + # clear eventual lingering session storages + ObjectDB.objects.clear_all_sessids() + # always call this regardless of start type + self.at_server_start() def set_restart_mode(self, mode=None): """ @@ -316,8 +317,7 @@ class Evennia(object): from src.scripts.tickerhandler import TICKER_HANDLER TICKER_HANDLER.save() - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_reload_stop() + self.at_server_reload_stop() else: if mode == 'reset': @@ -339,15 +339,13 @@ class Evennia(object): yield ObjectDB.objects.clear_all_sessids() ServerConfig.objects.conf("server_restart_mode", "reset") - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_cold_stop() + self.at_server_cold_stop() # stopping time from src.utils import gametime gametime.save() - if SERVER_STARTSTOP_MODULE: - SERVER_STARTSTOP_MODULE.at_server_stop() + self.at_server_stop() # if _reactor_stopping is true, reactor does not need to # be stopped again. if os.name == 'nt' and os.path.exists(SERVER_PIDFILE): @@ -358,6 +356,62 @@ class Evennia(object): # flag to avoid loops. self.shutdown_complete = True reactor.callLater(0, reactor.stop) + + # server start/stop hooks + + def at_server_start(self): + """ + This is called every time the server starts up, regardless of + how it was shut down. + """ + if SERVER_STARTSTOP_MODULE: + SERVER_STARTSTOP_MODULE.at_server_start() + + + def at_server_stop(self): + """ + This is called just before a server is shut down, regardless + of it is fore a reload, reset or shutdown. + """ + if SERVER_STARTSTOP_MODULE: + SERVER_STARTSTOP_MODULE.at_server_stop() + + + def at_server_reload_start(self): + """ + This is called only when server starts back up after a reload. + """ + if SERVER_STARTSTOP_MODULE: + SERVER_STARTSTOP_MODULE.at_server_reload_start() + + + def at_server_reload_stop(self): + """ + This is called only time the server stops before a reload. + """ + if SERVER_STARTSTOP_MODULE: + SERVER_STARTSTOP_MODULE.at_server_reload_stop() + + + def at_server_cold_start(self): + """ + This is called only when the server starts "cold", i.e. after a + shutdown or a reset. + """ + if GUEST_ENABLED: + for guest in PlayerDB.objects.all().filter(db_typeclass_path=settings.BASE_GUEST_TYPECLASS): + for character in filter(None, guest.db._playable_characters): + character.delete() + guest.delete() + if SERVER_STARTSTOP_MODULE: + SERVER_STARTSTOP_MODULE.at_server_cold_start() + + def at_server_cold_stop(self): + """ + This is called only when the server goes down due to a shutdown or reset. + """ + if SERVER_STARTSTOP_MODULE: + SERVER_STARTSTOP_MODULE.at_server_cold_stop() #------------------------------------------------------------ # From 485844a15086144d0bedce4a869c2fb286f4c9cc Mon Sep 17 00:00:00 2001 From: n0q Date: Mon, 7 Jul 2014 23:41:20 -0400 Subject: [PATCH 07/10] Added newline to end of file --- src/commands/default/unloggedin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/default/unloggedin.py b/src/commands/default/unloggedin.py index e2486c6f98..ecb4ff246d 100644 --- a/src/commands/default/unloggedin.py +++ b/src/commands/default/unloggedin.py @@ -377,4 +377,5 @@ def CreateCharacter(session, new_player, typeclass, start_location, home, permis except Exception, e: session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace() - return False \ No newline at end of file + return False + \ No newline at end of file From 5b6ab697b0247a5fbfb497ac06515505ce15b244 Mon Sep 17 00:00:00 2001 From: n0q Date: Tue, 8 Jul 2014 00:03:07 -0400 Subject: [PATCH 08/10] Update unloggedin.py --- src/commands/default/unloggedin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/default/unloggedin.py b/src/commands/default/unloggedin.py index ecb4ff246d..1a7d299770 100644 --- a/src/commands/default/unloggedin.py +++ b/src/commands/default/unloggedin.py @@ -54,7 +54,7 @@ class CmdUnconnectedConnect(MuxCommand): other types of logged-in commands (this is because there is no object yet before the player has logged in) """ - + session = self.caller args = self.args # extract quoted parts @@ -378,4 +378,4 @@ def CreateCharacter(session, new_player, typeclass, start_location, home, permis session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace() return False - \ No newline at end of file + From e47b2b83b14c496599f4ef43e34572f528a63b09 Mon Sep 17 00:00:00 2001 From: n0q Date: Tue, 8 Jul 2014 01:33:06 -0400 Subject: [PATCH 09/10] Added Guest permission level --- src/settings_default.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/settings_default.py b/src/settings_default.py index 1913c789e1..18a66916df 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -381,7 +381,8 @@ MAX_NR_CHARACTERS = 1 # The access hiearchy, in climbing order. A higher permission in the # hierarchy includes access of all levels below it. Used by the perm()/pperm() # lock functions. -PERMISSION_HIERARCHY = ("Players", +PERMISSION_HIERARCHY = ("Guests", + "Players", "PlayerHelpers", "Builders", "Wizards", From ddf8c603416a91bf448e0ec8860887f87453dfc4 Mon Sep 17 00:00:00 2001 From: n0q Date: Tue, 8 Jul 2014 02:21:59 -0400 Subject: [PATCH 10/10] Corrected guest_list comment Comment on GUEST_LIST specified 5 guests when the actual list was configured for nine. --- src/settings_default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings_default.py b/src/settings_default.py index 18a66916df..7379a34ac8 100644 --- a/src/settings_default.py +++ b/src/settings_default.py @@ -318,7 +318,7 @@ START_LOCATION = "#2" GUEST_START_LOCATION = "#2" # The naming convention for guest players/characters. The size of this list # also detemines how many guests may be on the game at once. The default is -# a maximum of five guests, named Guest1 through Guest5. +# a maximum of nine guests, named Guest1 through Guest9. GUEST_LIST = ["Guest" + str(s+1) for s in range(9)] # Lookups of Attributes, Tags, Nicks, Aliases can be aggressively # cached to avoid repeated database hits. This often gives noticeable