diff --git a/contrib/menusystem.py b/contrib/menusystem.py index 2d53fbbc06..8a32db155c 100644 --- a/contrib/menusystem.py +++ b/contrib/menusystem.py @@ -48,7 +48,7 @@ class CmdMenuNode(Command): locks = "cmd:all()" help_category = "Menu" - menutree = None + menutree = None code = None def func(self): @@ -127,8 +127,7 @@ class MenuCmdSet(CmdSet): mergetype = "Replace" def at_cmdset_creation(self): "populate cmdset" - self.add(CmdMenuLook()) - self.add(CmdMenuHelp()) + pass # # Menu Node system @@ -218,18 +217,27 @@ class MenuNode(object): """ def __init__(self, key, text="", links=None, linktexts=None, - keywords=None, cols=1, helptext=None, code=""): + keywords=None, cols=1, helptext=None, selectcmds=None, code="", nodefaultcmds=False): """ key - the unique identifier of this node. text - is the text that will be displayed at top when viewing this node. - links - a list of keys for unique menunodes this is connected to. - linktexts - a list of texts to describe the links. If defined, need to match links list - keywords - a list of unique keys for choosing links. Must match links list. If not given, index numbers will be used. + links - a list of keys for unique menunodes this is connected to. The actual keys will not be + printed - keywords will be used (or a number) + linktexts - an optional list of texts to describe the links. Must match link list if defined. Entries can be None + to not generate any extra text for a particular link. + keywords - an optional list of unique keys for choosing links. Must match links list. If not given, index numbers + will be used. Also individual list entries can be None and will be replaed by indices. + If CMD_NOMATCH or CMD_NOENTRY, no text will be generated to indicate the option exists. cols - how many columns to use for displaying options. helptext - if defined, this is shown when using the help command instead of the normal help index. + selectcmds- a list of custom cmdclasses for handling each option. Must match links list, but some entries + may be set to None to use default menu cmds. The given command's key will be used for the menu + list entry unless it's CMD_NOMATCH or CMD_NOENTRY, in which case no text will be generated. These + commands have access to self.menutree and so can be used to select nodes. code - functional code. This will be executed just before this node is loaded (i.e. as soon after it's been selected from another node). self.caller is available to call from this code block, as well as ObjectDB and PlayerDB. + nodefaultcmds - if true, don't offer the default help and look commands in the node """ self.key = key self.cmdset = None @@ -237,23 +245,31 @@ class MenuNode(object): self.linktexts = linktexts self.keywords = keywords self.cols = cols + self.selectcmds = selectcmds self.code = code - + self.nodefaultcmds = nodefaultcmds + Nlinks = len(self.links) + # validate the input if not self.links: self.links = [] - if not self.linktexts or (self.linktexts and len(self.linktexts) != len(self.links)): - self.linktexts = [] - if not self.keywords or (self.keywords and len(self.keywords) != len(self.links)): - self.keywords = [] + if not self.linktexts or (len(self.linktexts) != Nlinks): + self.linktexts = [None for i in range(Nlinks)] + if not self.keywords or (len(self.keywords) != Nlinks): + self.keywords = [None for i in range(Nlinks)] + if not selectcmds or (len(self.selectcmds) != Nlinks): + self.selectcmds = [None for i in range(Nlinks)] # Format default text for the menu-help command if not helptext: - helptext = "Select one of the valid options" - if self.keywords: - helptext += " (" + ", ".join(self.keywords) + ")" - elif self.links: - helptext += " (" + ", ".join([str(i + 1) for i in range(len(self.links))]) + ")" + helptext = "Select one of the valid options (" + for i in range(Nlinks): + if self.keywords[i]: + if self.keywords[i] not in (CMD_NOMATCH, CMD_NOINPUT): + helptext += "%s, " % self.keywords[i] + else: + helptext += "%s, " % (i + 1) + helptext = helptext.rstrip(", ") + ")" self.helptext = helptext # Format text display @@ -264,12 +280,14 @@ class MenuNode(object): # format the choices into as many collumns as specified choices = [] for ilink, link in enumerate(self.links): - if self.keywords: - choice = "{g%s{n" % self.keywords[ilink] + choice = "" + if self.keywords[ilink]: + if self.keywords[ilink] not in (CMD_NOMATCH, CMD_NOINPUT): + choice += "{g%s{n" % self.keywords[ilink] else: - choice = "{g%i{n" % (ilink + 1) - if self.linktexts: - choice += "-%s" % self.linktexts[ilink] + choice += "{g %i{n" % (ilink + 1) + if self.linktexts[ilink]: + choice += " - %s" % self.linktexts[ilink] choices.append(choice) cols = [[] for i in range(min(len(choices), cols))] while True: @@ -282,9 +300,9 @@ class MenuNode(object): break ftable = utils.format_table(cols) for row in ftable: - string += "\n" + "".join(row) + string +="\n" + "".join(row) # store text - self.text = 78*"-" + "\n" + string.strip() + self.text = 78*"-" + "\n" + string.rstrip() def init(self, menutree): """ @@ -292,15 +310,24 @@ class MenuNode(object): """ # Create the relevant cmdset self.cmdset = MenuCmdSet() - for i, link in enumerate(self.links): - cmd = CmdMenuNode() - cmd.key = str(i + 1) + if not self.nodefaultcmds: + # add default menu commands + self.cmdset.add(CmdMenuLook()) + self.cmdset.add(CmdMenuHelp()) + + for i, link in enumerate(self.links): + if self.selectcmds[i]: + cmd = self.selectcmds[i]() + else: + cmd = CmdMenuNode() + cmd.key = str(i + 1) + # this is the operable command, it moves us to the next node. + cmd.code = "self.menutree.goto('%s')" % link + # also custom commands get access to the menutree. cmd.menutree = menutree - # this is the operable command, it moves us to the next node. - cmd.code = "self.menutree.goto('%s')" % link - if self.keywords: + if self.keywords[i] and cmd.key not in (CMD_NOMATCH, CMD_NOINPUT): cmd.aliases = [self.keywords[i]] - self.cmdset.add(cmd) + self.cmdset.add(cmd) def __str__(self): "Returns the string representation." diff --git a/game/gamesrc/commands/basecmdset.py b/game/gamesrc/commands/basecmdset.py index 921d857e3d..5870a6fa9b 100644 --- a/game/gamesrc/commands/basecmdset.py +++ b/game/gamesrc/commands/basecmdset.py @@ -22,7 +22,7 @@ from src.commands.cmdset import CmdSet from src.commands.default import cmdset_default, cmdset_unloggedin, cmdset_ooc from game.gamesrc.commands.basecommand import Command -#from contrib import menusystem, lineeditor +from contrib import menusystem, lineeditor #from contrib import misc_commands class DefaultCmdSet(cmdset_default.DefaultCmdSet): @@ -47,9 +47,9 @@ class DefaultCmdSet(cmdset_default.DefaultCmdSet): # # any commands you add below will overload the default ones. # - #self.add(menusystem.CmdMenuTest()) + self.add(menusystem.CmdMenuTest()) #self.add(lineeditor.CmdEditor()) - #self.add(misc_commands.CmdQuell()) + #self.add(misc_commands.CmdQuell()) class UnloggedinCmdSet(cmdset_unloggedin.UnloggedinCmdSet): """ @@ -75,7 +75,6 @@ class UnloggedinCmdSet(cmdset_unloggedin.UnloggedinCmdSet): # any commands you add below will overload the default ones. # - class OOCCmdSet(cmdset_ooc.OOCCmdSet): """ This is set is available to the player when they have no diff --git a/src/commands/cmdhandler.py b/src/commands/cmdhandler.py index 2186ef2d69..8ae98f9560 100644 --- a/src/commands/cmdhandler.py +++ b/src/commands/cmdhandler.py @@ -8,9 +8,7 @@ command line. The process is as follows: 2) The system checks the state of the caller - loggedin or not 3) If no command string was supplied, we search the merged cmdset for system command CMD_NOINPUT and branches to execute that. --> Finished -4) Depending on the login/not state, it collects cmdsets from different sources: - not logged in - uses the single cmdset defined as settings.CMDSET_UNLOGGEDIN - normal - gathers command sets from many different sources (shown in dropping priority): +4) Cmdsets are gathered from different sources (in order of dropping priority): channels - all available channel names are auto-created into a cmdset, to allow for giving the channel name and have the following immediately sent to the channel. The sending is performed by the CMD_CHANNEL @@ -140,26 +138,20 @@ def get_and_merge_cmdsets(caller): # Main command-handler function -def cmdhandler(caller, raw_string, unloggedin=False, testing=False): +def cmdhandler(caller, raw_string, testing=False): """ This is the main function to handle any string sent to the engine. caller - calling object raw_string - the command string given on the command line - unloggedin - if caller is an authenticated user or not testing - if we should actually execute the command or not. if True, the command instance will be returned instead. """ try: # catch bugs in cmdhandler itself try: # catch special-type commands - if unloggedin: - # not logged in, so it's just one cmdset we are interested in - cmdset = import_cmdset(settings.CMDSET_UNLOGGEDIN, caller) - else: - # We are logged in, collect all relevant cmdsets and merge - cmdset = get_and_merge_cmdsets(caller) - + cmdset = get_and_merge_cmdsets(caller) + #print cmdset if not cmdset: # this is bad and shouldn't happen. diff --git a/src/commands/cmdset.py b/src/commands/cmdset.py index fe61c08d48..8bdaed3db1 100644 --- a/src/commands/cmdset.py +++ b/src/commands/cmdset.py @@ -221,7 +221,7 @@ class CmdSet(object): are made, rather later added commands will simply replace existing ones to make a unique set. """ - + if inherits_from(cmd, "src.commands.cmdset.CmdSet"): # this is a command set so merge all commands in that set # to this one. We are not protecting against recursive @@ -235,19 +235,19 @@ class CmdSet(object): string += "make sure they are not themself cyclically added to the new cmdset somewhere in the chain." raise RuntimeError(string % (cmd, self.__class__)) cmds = cmd.commands - elif not is_iter(cmd): - cmds = [instantiate(cmd)] + elif is_iter(cmd): + cmds = [instantiate(c) for c in cmd] else: - cmds = instantiate(cmd) + cmds = [instantiate(cmd)] for cmd in cmds: # add all commands if not hasattr(cmd, 'obj'): - cmd.obj = self.cmdsetobj - try: - ic = self.commands.index(cmd) - self.commands[ic] = cmd # replace - except ValueError: - self.commands.append(cmd) + cmd.obj = self.cmdsetobj + ic = [i for i, oldcmd in enumerate(self.commands) if oldcmd.match(cmd)] + if ic: + self.commands[ic[0]] = cmd # replace + else: + self.commands.append(cmd) # extra run to make sure to avoid doublets self.commands = list(set(self.commands)) #print "In cmdset.add(cmd):", self.key, cmd diff --git a/src/commands/command.py b/src/commands/command.py index 2605586310..20eba4f9b9 100644 --- a/src/commands/command.py +++ b/src/commands/command.py @@ -129,7 +129,7 @@ class Command(object): previously extracted from the raw string by the system. cmdname is always lowercase when reaching this point. """ - return (cmdname == self.key) or (cmdname in self.aliases) + return cmdname and ((cmdname == self.key) or (cmdname in self.aliases)) def access(self, srcobj, access_type="cmd", default=False): """ diff --git a/src/commands/default/unloggedin.py b/src/commands/default/unloggedin.py index 3da12c66c4..57f999972b 100644 --- a/src/commands/default/unloggedin.py +++ b/src/commands/default/unloggedin.py @@ -169,20 +169,10 @@ its and @/./+/-/_ only.") # this echoes the restrictions made by django's auth m session.msg("There was an error creating the default Character/Player. This error was logged. Contact an admin.") return new_player = new_character.player - - # character safety features - new_character.locks.delete("get") - new_character.locks.add("get:perm(Wizards)") - # allow the character itself and the player to puppet this character. - new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % - (new_character.id, new_player.id)) - # set a default description - new_character.db.desc = "This is a Player." - - new_character.db.FIRST_LOGIN = True - new_player = new_character.player - new_player.db.FIRST_LOGIN = True + # 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(player) # join the new player to the public channel pchanneldef = settings.CHANNEL_PUBLIC @@ -191,10 +181,20 @@ its and @/./+/-/_ only.") # this echoes the restrictions made by django's auth m if not pchannel.connect_to(new_player): string = "New player '%s' could not connect to public channel!" % new_player.key logger.log_errmsg(string) + + # 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)) + + + # set a default description + new_character.db.desc = "This is a Player." + # tell the caller everything went well. string = "A new account '%s' was created with the email address %s. Welcome!" string += "\n\nYou can now log with the command 'connect %s '." session.msg(string % (playername, email, email)) + 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. diff --git a/src/server/amp.py b/src/server/amp.py index 7a740dd29e..97adaf375b 100644 --- a/src/server/amp.py +++ b/src/server/amp.py @@ -276,7 +276,7 @@ class AMPProtocol(amp.AMP): if sess.logged_in and sess.uid: # this can happen in the case of auto-authenticating protocols like SSH sess.player = PlayerDB.objects.get_player_from_uid(sess.uid) - sess.at_sync() # this runs initialization without acr + sess.at_sync() # this runs initialization without acr self.factory.server.sessions.portal_connect(sessid, sess) diff --git a/src/server/serversession.py b/src/server/serversession.py index e6eae3ac2f..4bcba0201f 100644 --- a/src/server/serversession.py +++ b/src/server/serversession.py @@ -13,7 +13,7 @@ from django.conf import settings from src.scripts.models import ScriptDB from src.comms.models import Channel from src.utils import logger -from src.commands import cmdhandler +from src.commands import cmdhandler, cmdsethandler IDLE_COMMAND = settings.IDLE_COMMAND @@ -37,7 +37,6 @@ class ServerSession(Session): through their session. """ - def at_sync(self): """ This is called whenever a session has been resynced with the portal. @@ -48,12 +47,22 @@ class ServerSession(Session): the session as it was. """ 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) + self.cmdset.update(init_mode=True) return + character = self.get_character() if character: # start (persistent) scripts on this object ScriptDB.objects.validate(obj=character) - + + def at_cmdset_get(self): + "dummy hook all objects with cmdsets need to have" + pass + def session_login(self, player): """ Startup mechanisms that need to run at login. This is called @@ -193,8 +202,9 @@ class ServerSession(Session): # there is no character, but we are logged in. Use player instead. self.get_player().execute_cmd(command_string) else: - # we are not logged in. Use special unlogged-in call. - cmdhandler.cmdhandler(self, command_string, unloggedin=True) + # we are not logged in. Use the session directly + # (it uses the settings.UNLOGGEDIN cmdset) + cmdhandler.cmdhandler(self, command_string) self.update_session_counters() def data_out(self, msg, data=None): diff --git a/src/utils/create.py b/src/utils/create.py index 05e90e8bba..d75808a766 100644 --- a/src/utils/create.py +++ b/src/utils/create.py @@ -389,6 +389,8 @@ def create_player(name, email, password, from src.players.models import PlayerDB from src.players.player import Player + if not email: + email = "dummy@dummy.com" if user: new_user = user else: diff --git a/src/utils/utils.py b/src/utils/utils.py index 3ec23a1f83..d294bf9ca2 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -624,3 +624,15 @@ def string_from_module(modpath, variable=None): if not mvars: return None return mvars[random.randint(0, len(mvars)-1)] + +def init_new_player(player): + """ + Helper method to call all hooks, set flags etc on a newly created + player (and potentially their character, if it exists already) + """ + # the FIRST_LOGIN flags are necessary for the system to call + # the relevant first-login hooks. + if player.character: + player.character.db.FIRST_LOGIN = True + player.db.FIRST_LOGIN = True +