Changed the way unloggedin commands work. Rather than the cmdhander having a special state for unlogged-in commands, the session itself simply stores the cmdset from settings.CMDSET_UNLOGGEDIN. Clean and efficient and also gives a lot more freedom for creating custom login mechanisms (notably it opens the door to using menu systems).

This commit is contained in:
Griatch 2011-11-06 17:38:29 +01:00
parent 0ed692c19c
commit 2b2d27ed39
10 changed files with 119 additions and 77 deletions

View file

@ -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."

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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):
"""

View file

@ -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 <your password>'."
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.

View file

@ -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)

View file

@ -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):

View file

@ -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:

View file

@ -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