mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
merge.
This commit is contained in:
commit
ce0e3c4857
32 changed files with 1208 additions and 166 deletions
199
contrib/chargen.py
Normal file
199
contrib/chargen.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
"""
|
||||
|
||||
Contribution - Griatch 2011
|
||||
|
||||
This is a simple character creation commandset. A suggestion is to
|
||||
test this together with menu_login, which doesn't create a Character
|
||||
on its own. This shows some more info and gives the Player the option
|
||||
to create a character without any more customizations than their name
|
||||
(further options are unique for each game anyway).
|
||||
|
||||
Since this extends the OOC cmdset, logging in from the menu will
|
||||
automatically drop the Player into this cmdset unless they logged off
|
||||
while puppeting a Character already before.
|
||||
|
||||
Installation:
|
||||
|
||||
Import this module in game.gamesrc.basecmdset and
|
||||
add the following line to the end of OOCCmdSet's at_cmdset_creation():
|
||||
|
||||
self.add(character_creation.OOCCmdSetCharGen)
|
||||
|
||||
|
||||
If you have a freshly installed database you could also instead add/edit
|
||||
this line to your game/settings.py file:
|
||||
|
||||
CMDSET_OOC = "contrib.character_creation.OOCCmdSetCharGen"
|
||||
|
||||
This will replace the default OOCCmdset to look to this module
|
||||
instead of the one in game.gamesrc.basecmdset. If you do this, uncomment
|
||||
the super() statement in OOCCmdSetCharGen (end of this file) too. This will
|
||||
however only affect NEWLY created players, not those already in the game, which i
|
||||
s why you'd usually only do this if you are starting from scratch.
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from src.commands.command import Command
|
||||
from src.commands.default.general import CmdLook
|
||||
from src.commands.default.cmdset_ooc import OOCCmdSet
|
||||
from src.objects.models import ObjectDB
|
||||
from src.utils import utils, create
|
||||
|
||||
CHARACTER_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
|
||||
|
||||
class CmdOOCLook(CmdLook):
|
||||
"""
|
||||
ooc look
|
||||
|
||||
Usage:
|
||||
look
|
||||
look <character>
|
||||
|
||||
This is an OOC version of the look command. Since a Player doesn't
|
||||
have an in-game existence, there is no concept of location or
|
||||
"self".
|
||||
|
||||
If any characters are available for you to control, you may look
|
||||
at them with this command.
|
||||
"""
|
||||
|
||||
key = "look"
|
||||
aliases = ["l", "ls"]
|
||||
locks = "cmd:all()"
|
||||
help_cateogory = "General"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Implements the ooc look command
|
||||
|
||||
We use an attribute _character_dbrefs on the player in order
|
||||
to figure out which characters are "theirs". A drawback of this
|
||||
is that only the CmdCharacterCreate command adds this attribute,
|
||||
and thus e.g. player #1 will not be listed (although it will work).
|
||||
Existence in this list does not depend on puppeting rights though,
|
||||
that is checked by the @ic command directly.
|
||||
"""
|
||||
|
||||
# making sure caller is really a player
|
||||
self.character = None
|
||||
if utils.inherits_from(self.caller, "src.objects.objects.Object"):
|
||||
# An object of some type is calling. Convert to player.
|
||||
#print self.caller, self.caller.__class__
|
||||
self.character = self.caller
|
||||
if hasattr(self.caller, "player"):
|
||||
self.caller = self.caller.player
|
||||
|
||||
if not self.character:
|
||||
# ooc mode, we are players
|
||||
|
||||
avail_chars = self.caller.db._character_dbrefs
|
||||
if self.args:
|
||||
# Maybe the caller wants to look at a character
|
||||
if not avail_chars:
|
||||
self.caller.msg("You have no characters to look at. Why not create one?")
|
||||
return
|
||||
objs = ObjectDB.objects.get_objs_with_key_and_typeclass(self.args.strip(), CHARACTER_TYPECLASS)
|
||||
objs = [obj for obj in objs if obj.id in avail_chars]
|
||||
if not objs:
|
||||
self.caller.msg("You cannot see this Character.")
|
||||
return
|
||||
self.caller.msg(objs[0].return_appearance(self.caller))
|
||||
return
|
||||
|
||||
# not inspecting a character. Show the OOC info.
|
||||
charobjs = []
|
||||
charnames = []
|
||||
if self.caller.db._character_dbrefs:
|
||||
dbrefs = self.caller.db._character_dbrefs
|
||||
charobjs = [ObjectDB.objects.get_id(dbref) for dbref in dbrefs]
|
||||
charnames = [charobj.key for charobj in charobjs if charobj]
|
||||
if charnames:
|
||||
charlist = "The following Character(s) are available:\n\n"
|
||||
charlist += "\n\r".join(["{w %s{n" % charname for charname in charnames])
|
||||
charlist += "\n\n Use {w@ic <character name>{n to switch to that Character."
|
||||
else:
|
||||
charlist = "You have no Characters."
|
||||
string = \
|
||||
""" You, %s, are an {wOOC ghost{n without form. The world is hidden
|
||||
from you and besides chatting on channels your options are limited.
|
||||
You need to have a Character in order to interact with the world.
|
||||
|
||||
%s
|
||||
|
||||
Use {wcreate <name>{n to create a new character and {whelp{n for a
|
||||
list of available commands.""" % (self.caller.key, charlist)
|
||||
self.caller.msg(string)
|
||||
|
||||
else:
|
||||
# not ooc mode - leave back to normal look
|
||||
self.caller = self.character # we have to put this back for normal look to work.
|
||||
super(CmdOOCLook, self).func()
|
||||
|
||||
class CmdOOCCharacterCreate(Command):
|
||||
"""
|
||||
creates a character
|
||||
|
||||
Usage:
|
||||
create <character name>
|
||||
|
||||
This will create a new character, assuming
|
||||
the given character name does not already exist.
|
||||
"""
|
||||
|
||||
key = "create"
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Tries to create the Character object. We also put an
|
||||
attribute on ourselves to remember it.
|
||||
"""
|
||||
|
||||
# making sure caller is really a player
|
||||
self.character = None
|
||||
if utils.inherits_from(self.caller, "src.objects.objects.Object"):
|
||||
# An object of some type is calling. Convert to player.
|
||||
#print self.caller, self.caller.__class__
|
||||
self.character = self.caller
|
||||
if hasattr(self.caller, "player"):
|
||||
self.caller = self.caller.player
|
||||
|
||||
if not self.args:
|
||||
self.caller.msg("Usage: create <character name>")
|
||||
return
|
||||
charname = self.args.strip()
|
||||
old_char = ObjectDB.objects.get_objs_with_key_and_typeclass(charname, CHARACTER_TYPECLASS)
|
||||
if old_char:
|
||||
self.caller.msg("Character {c%s{n already exists." % charname)
|
||||
return
|
||||
# create the character
|
||||
|
||||
new_character = create.create_object(CHARACTER_TYPECLASS, key=charname)
|
||||
if not new_character:
|
||||
self.caller.msg("{rThe Character couldn't be created. This is a bug. Please contact an admin.")
|
||||
return
|
||||
# make sure to lock the character to only be puppeted by this player
|
||||
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" %
|
||||
(new_character.id, self.caller.id))
|
||||
|
||||
# save dbref
|
||||
avail_chars = self.caller.db._character_dbrefs
|
||||
if avail_chars:
|
||||
avail_chars.append(new_character.id)
|
||||
else:
|
||||
avail_chars = [new_character.id]
|
||||
self.caller.db._character_dbrefs = avail_chars
|
||||
|
||||
self.caller.msg("{gThe Character {c%s{g was successfully created!" % charname)
|
||||
|
||||
class OOCCmdSetCharGen(OOCCmdSet):
|
||||
"""
|
||||
Extends the default OOC cmdset.
|
||||
"""
|
||||
def at_cmdset_creation(self):
|
||||
"Install everything from the default set, then overload"
|
||||
#super(OOCCmdSetCharGen, self).at_cmdset_creation()
|
||||
self.add(CmdOOCLook())
|
||||
self.add(CmdOOCCharacterCreate())
|
||||
|
||||
332
contrib/menu_login.py
Normal file
332
contrib/menu_login.py
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
"""
|
||||
Menu-driven login system
|
||||
|
||||
Contribution - Griatch 2011
|
||||
|
||||
|
||||
This is an alternative login system for Evennia, using the
|
||||
contrib.menusystem module. As opposed to the default system it doesn't
|
||||
use emails for authentication and also don't auto-creates a Character
|
||||
with the same name as the Player (instead assuming some sort of
|
||||
character-creation to come next).
|
||||
|
||||
|
||||
Install is simple:
|
||||
|
||||
To your settings file, add/edit the line:
|
||||
|
||||
CMDSET_UNLOGGEDIN = "contrib.menu_login.UnloggedInCmdSet"
|
||||
|
||||
That's it. The cmdset in this module will now be used instead of the
|
||||
default one.
|
||||
|
||||
The initial login "graphic" is taken from strings in the module given
|
||||
by settings.CONNECTION_SCREEN_MODULE. You will want to edit the string
|
||||
in that module (at least comment out the default string that mentions
|
||||
commands that are not available) and add something more suitable for
|
||||
the initial splash screen.
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
import traceback
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from src.server import sessionhandler
|
||||
from src.players.models import PlayerDB
|
||||
from src.objects.models import ObjectDB
|
||||
from src.server.models import ServerConfig
|
||||
from src.comms.models import Channel
|
||||
|
||||
from src.utils import create, logger, utils, ansi
|
||||
from src.commands.command import Command
|
||||
from src.commands.cmdset import CmdSet
|
||||
from src.commands.cmdhandler import CMD_LOGINSTART
|
||||
from contrib.menusystem import MenuNode, MenuTree, CMD_NOINPUT, CMD_NOMATCH
|
||||
|
||||
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
||||
|
||||
|
||||
# Commands run on the unloggedin screen. Note that this is not using settings.UNLOGGEDIN_CMDSET but
|
||||
# the menu system, which is why some are named for the numbers in the menu.
|
||||
#
|
||||
# Also note that the menu system will automatically assign all
|
||||
# commands used in its structure a property "menutree" holding a reference
|
||||
# back to the menutree. This allows the commands to do direct manipulation
|
||||
# for example by triggering a conditional jump to another node.
|
||||
#
|
||||
|
||||
# Menu entry 1a - Entering a Username
|
||||
|
||||
class CmdBackToStart(Command):
|
||||
"""
|
||||
Step back to node0
|
||||
"""
|
||||
key = CMD_NOINPUT
|
||||
locks = "cmd:all()"
|
||||
def func(self):
|
||||
"Execute the command"
|
||||
self.menutree.goto("START")
|
||||
|
||||
class CmdUsernameSelect(Command):
|
||||
"""
|
||||
Handles the entering of a username and
|
||||
checks if it exists.
|
||||
"""
|
||||
key = CMD_NOMATCH
|
||||
locks = "cmd:all()"
|
||||
def func(self):
|
||||
"Execute the command"
|
||||
player = PlayerDB.objects.get_player_from_name(self.args)
|
||||
if not player:
|
||||
self.caller.msg("{rThis account name couldn't be found. Did you create it? If you did, make sure you spelled it right (case doesn't matter).{n")
|
||||
self.menutree.goto("node1a")
|
||||
else:
|
||||
self.menutree.player = player # store the player so next step can find it
|
||||
self.menutree.goto("node1b")
|
||||
|
||||
# Menu entry 1b - Entering a Password
|
||||
|
||||
class CmdPasswordSelectBack(Command):
|
||||
"""
|
||||
Steps back from the Password selection
|
||||
"""
|
||||
key = CMD_NOINPUT
|
||||
locks = "cmd:all()"
|
||||
def func(self):
|
||||
"Execute the command"
|
||||
self.menutree.goto("node1a")
|
||||
|
||||
class CmdPasswordSelect(Command):
|
||||
"""
|
||||
Handles the entering of a password and logs into the game.
|
||||
"""
|
||||
key = CMD_NOMATCH
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Execute the command"
|
||||
if not hasattr(self.menutree, "player"):
|
||||
self.caller.msg("{rSomething went wrong! The player was not remembered from last step!{n")
|
||||
self.menutree.goto("node1a")
|
||||
return
|
||||
player = self.menutree.player
|
||||
if not player.user.check_password(self.args):
|
||||
self.caller.msg("{rIncorrect password.{n")
|
||||
self.menutree.goto("node1b")
|
||||
return
|
||||
|
||||
# we are ok, log us in.
|
||||
self.caller.msg("{gWelcome %s! Logging in ...{n" % player.key)
|
||||
self.caller.session_login(player)
|
||||
|
||||
# abort menu, do cleanup.
|
||||
self.menutree.goto("END")
|
||||
|
||||
# we are logged in. Look around.
|
||||
character = player.character
|
||||
if character:
|
||||
character.execute_cmd("look")
|
||||
else:
|
||||
# we have no character yet; use player's look, if it exists
|
||||
player.execute_cmd("look")
|
||||
|
||||
# Menu entry 2a - Creating a Username
|
||||
|
||||
class CmdUsernameCreate(Command):
|
||||
"""
|
||||
Handle the creation of a valid username
|
||||
"""
|
||||
key = CMD_NOMATCH
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Execute the command"
|
||||
playername = self.args
|
||||
|
||||
# sanity check on the name
|
||||
if not re.findall('^[\w. @+-]+$', playername) or not (3 <= len(playername) <= 30):
|
||||
self.caller.msg("\n\r {rAccount name should be between 3 and 30 characters. Letters, spaces, dig\
|
||||
its and @/./+/-/_ only.{n") # this echoes the restrictions made by django's auth module.
|
||||
self.menutree.goto("node2a")
|
||||
return
|
||||
if PlayerDB.objects.get_player_from_name(playername):
|
||||
self.caller.msg("\n\r {rAccount name %s already exists.{n" % playername)
|
||||
self.menutree.goto("node2a")
|
||||
return
|
||||
# store the name for the next step
|
||||
self.menutree.playername = playername
|
||||
self.menutree.goto("node2b")
|
||||
|
||||
# Menu entry 2b - Creating a Password
|
||||
|
||||
class CmdPasswordCreateBack(Command):
|
||||
"Step back from the password creation"
|
||||
key = CMD_NOINPUT
|
||||
locks = "cmd:all()"
|
||||
def func(self):
|
||||
"Execute the command"
|
||||
self.menutree.goto("node2a")
|
||||
|
||||
class CmdPasswordCreate(Command):
|
||||
"Handle the creation of a password. This also creates the actual Player/User object."
|
||||
key = CMD_NOMATCH
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Execute the command"
|
||||
password = self.args
|
||||
if not hasattr(self.menutree, 'playername'):
|
||||
self.caller.msg("{rSomething went wrong! Playername not remembered from previous step!{n")
|
||||
self.menutree.goto("node2a")
|
||||
return
|
||||
playername = self.menutree.playername
|
||||
if len(password) < 3:
|
||||
# too short password
|
||||
string = "{rYour password must be at least 3 characters or longer."
|
||||
string += "\n\rFor best security, make it at least 8 characters long, "
|
||||
string += "avoid making it a real word and mix numbers into it.{n"
|
||||
self.caller.msg(string)
|
||||
self.menutree.goto("node2b")
|
||||
return
|
||||
# everything's ok. Create the new player account. Don't create a Character here.
|
||||
try:
|
||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
||||
typeclass = settings.BASE_PLAYER_TYPECLASS
|
||||
new_player = create.create_player(playername, None, password,
|
||||
typeclass=typeclass,
|
||||
permissions=permissions,
|
||||
create_character=False)
|
||||
if not new_player:
|
||||
self.msg("There was an error creating the Player. This error was logged. Contact an admin.")
|
||||
self.menutree.goto("START")
|
||||
return
|
||||
utils.init_new_player(new_player)
|
||||
|
||||
# join the new player to the public channel
|
||||
pchanneldef = settings.CHANNEL_PUBLIC
|
||||
if pchanneldef:
|
||||
pchannel = Channel.objects.get_channel(pchanneldef[0])
|
||||
if not pchannel.connect_to(new_player):
|
||||
string = "New player '%s' could not connect to public channel!" % new_player.key
|
||||
logger.log_errmsg(string)
|
||||
|
||||
# tell the caller everything went well.
|
||||
string = "{gA new account '%s' was created. Now go log in from the menu!{n"
|
||||
self.caller.msg(string % (playername))
|
||||
self.menutree.goto("START")
|
||||
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."
|
||||
self.caller.msg(string % (traceback.format_exc()))
|
||||
logger.log_errmsg(traceback.format_exc())
|
||||
|
||||
|
||||
# Menu entry 3 - help screen
|
||||
|
||||
LOGIN_SCREEN_HELP = \
|
||||
"""
|
||||
Welcome to %s!
|
||||
|
||||
To login you need to first create an account. This is easy and
|
||||
free to do: Choose option {w(1){n in the menu and enter an account
|
||||
name and password when prompted. Obs- the account name is {wnot{n
|
||||
the name of the Character you will play in the game!
|
||||
|
||||
It's always a good idea (not only here, but everywhere on the net)
|
||||
to not use a regular word for your password. Make it longer than 3
|
||||
characters (ideally 6 or more) and mix numbers and capitalization
|
||||
into it. The password also handles whitespace, so why not make it
|
||||
a small sentence - easy to remember, hard for a computer to crack.
|
||||
|
||||
Once you have an account, use option {w(2){n to log in using the
|
||||
account name and password you specified.
|
||||
|
||||
Use the {whelp{n command once you're logged in to get more
|
||||
aid. Hope you enjoy your stay!
|
||||
|
||||
|
||||
(return to go back)""" % settings.SERVERNAME
|
||||
# Menu entry 4
|
||||
|
||||
class CmdUnloggedinQuit(Command):
|
||||
"""
|
||||
We maintain a different version of the quit command
|
||||
here for unconnected players for the sake of simplicity. The logged in
|
||||
version is a bit more complicated.
|
||||
"""
|
||||
key = "4"
|
||||
aliases = ["quit", "qu", "q"]
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Simply close the connection."
|
||||
self.menutree.goto("END")
|
||||
self.caller.msg("Good bye! Disconnecting ...")
|
||||
self.caller.session_disconnect()
|
||||
|
||||
|
||||
# The login menu tree, using the commands above
|
||||
|
||||
START = MenuNode("START", text=utils.string_from_module(CONNECTION_SCREEN_MODULE),
|
||||
links=["node1a", "node2a", "node3", "END"],
|
||||
linktexts=["Log in with an existing account",
|
||||
"Create a new account",
|
||||
"Help",
|
||||
"Quit",],
|
||||
selectcmds=[None, None, None, CmdUnloggedinQuit])
|
||||
|
||||
node1a = MenuNode("node1a", text="Please enter your account name (empty to abort).",
|
||||
links=["START", "node1b"],
|
||||
helptext=["Enter the account name you previously registered with."],
|
||||
keywords=[CMD_NOINPUT, CMD_NOMATCH],
|
||||
selectcmds=[CmdBackToStart, CmdUsernameSelect],
|
||||
nodefaultcmds=True) # if we don't, default help/look will be triggered by names starting with l/h ...
|
||||
node1b = MenuNode("node1b", text="Please enter your password (empty to go back).",
|
||||
links=["node1a", "END"],
|
||||
keywords=[CMD_NOINPUT, CMD_NOMATCH],
|
||||
selectcmds=[CmdPasswordSelectBack, CmdPasswordSelect],
|
||||
nodefaultcmds=True)
|
||||
|
||||
node2a = MenuNode("node2a", text="Please enter your desired account name (empty to abort).",
|
||||
links=["START", "node2b"],
|
||||
helptext="Account name can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.",
|
||||
keywords=[CMD_NOINPUT, CMD_NOMATCH],
|
||||
selectcmds=[CmdBackToStart, CmdUsernameCreate],
|
||||
nodefaultcmds=True)
|
||||
node2b = MenuNode("node2b", text="Please enter your password (empty to go back).",
|
||||
links=["node2a", "START"],
|
||||
helptext="Your password cannot contain any characters.",
|
||||
keywords=[CMD_NOINPUT, CMD_NOMATCH],
|
||||
selectcmds=[CmdPasswordCreateBack, CmdPasswordCreate],
|
||||
nodefaultcmds=True)
|
||||
node3 = MenuNode("node3", text=LOGIN_SCREEN_HELP,
|
||||
links=["START"],
|
||||
helptext="",
|
||||
keywords=[CMD_NOINPUT],
|
||||
selectcmds=[CmdBackToStart])
|
||||
|
||||
|
||||
# access commands
|
||||
|
||||
class UnloggedInCmdSet(CmdSet):
|
||||
"Cmdset for the unloggedin state"
|
||||
key = "UnloggedinState"
|
||||
priority = 0
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdUnloggedinLook())
|
||||
|
||||
class CmdUnloggedinLook(Command):
|
||||
"""
|
||||
An unloggedin version of the look command. This is called by the server when the player
|
||||
first connects. It sets up the menu before handing off to the menu's own look command..
|
||||
"""
|
||||
key = CMD_LOGINSTART
|
||||
aliases = ["look", "l"]
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Execute the menu"
|
||||
menu = MenuTree(self.caller, nodes=(START, node1a, node1b, node2a, node2b, node3), exec_end=None)
|
||||
menu.start()
|
||||
|
|
@ -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
|
||||
|
|
@ -150,15 +149,19 @@ class MenuTree(object):
|
|||
'START' and 'END' respectively.
|
||||
|
||||
"""
|
||||
def __init__(self, caller, nodes=None, startnode="START", endnode="END"):
|
||||
def __init__(self, caller, nodes=None, startnode="START", endnode="END", exec_end="look"):
|
||||
"""
|
||||
We specify startnode/endnode so that the system knows where to
|
||||
enter and where to exit the menu tree. If nodes is given, it
|
||||
shuld be a list of valid node objects to add to the tree.
|
||||
|
||||
exec_end - if not None, will execute the given command string
|
||||
directly after the menu system has been exited.
|
||||
"""
|
||||
self.tree = {}
|
||||
self.startnode = startnode
|
||||
self.endnode = endnode
|
||||
self.exec_end = exec_end
|
||||
self.caller = caller
|
||||
if nodes and utils.is_iter(nodes):
|
||||
for node in nodes:
|
||||
|
|
@ -187,7 +190,8 @@ class MenuTree(object):
|
|||
# if we was given the END node key, we clean up immediately.
|
||||
self.caller.cmdset.delete("menucmdset")
|
||||
del self.caller.db._menu_data
|
||||
self.caller.execute_cmd("look")
|
||||
if self.exec_end != None:
|
||||
self.caller.execute_cmd(self.exec_end)
|
||||
return
|
||||
# not exiting, look for a valid code.
|
||||
node = self.tree.get(key, None)
|
||||
|
|
@ -218,18 +222,28 @@ 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, separator=""):
|
||||
"""
|
||||
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
|
||||
separator - this string will be put on the line between menu nodes5B.
|
||||
"""
|
||||
self.key = key
|
||||
self.cmdset = None
|
||||
|
|
@ -237,23 +251,32 @@ class MenuNode(object):
|
|||
self.linktexts = linktexts
|
||||
self.keywords = keywords
|
||||
self.cols = cols
|
||||
self.selectcmds = selectcmds
|
||||
self.code = code
|
||||
|
||||
self.nodefaultcmds = nodefaultcmds
|
||||
self.separator = separator
|
||||
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 +287,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 +307,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 = self.separator + "\n" + string.rstrip()
|
||||
|
||||
def init(self, menutree):
|
||||
"""
|
||||
|
|
@ -292,15 +317,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."
|
||||
|
|
|
|||
|
|
@ -616,6 +616,29 @@ command must be added to a cmdset as well before it will work.
|
|||
def func(self):
|
||||
self.caller.msg("Don't just press return like that, talk to me!")
|
||||
|
||||
Exits
|
||||
-----
|
||||
|
||||
*Note: This is an advanced topic.*
|
||||
|
||||
The functionality of `Exit <Objects.html>`_ objects in Evennia is not
|
||||
hard-coded in the engine. Instead Exits are normal typeclassed objects
|
||||
that auto-creates a ``CmdSet`` on themselves when they are loaded. This
|
||||
cmdset has a single command with the same name (and aliases) as the Exit
|
||||
object itself. So what happens when a Player enters the name of the Exit
|
||||
on the command line is simply that the command handler, in the process
|
||||
of searching all available commands, also picks up the command from the
|
||||
Exit object(s) in the same room. Having found the matching command, it
|
||||
executes it. The command then makes sure to do all checks and eventually
|
||||
move the Player across the exit as appropriate. This allows exits to be
|
||||
extremely flexible - the functionality can be customized just like one
|
||||
would edit any other command.
|
||||
|
||||
Admittedly, you will usually be fine just using the appropriate
|
||||
``traverse_*`` hooks. But if you are interested in really changing how
|
||||
things work under the hood, check out ``src.objects.objects`` for how
|
||||
the default ``Exit`` typeclass is set up.
|
||||
|
||||
How commands actually work
|
||||
--------------------------
|
||||
|
||||
|
|
@ -684,3 +707,14 @@ Call ``func()`` on the command instance. This is the functional body of
|
|||
the command, actually doing useful things.
|
||||
|
||||
Call ``at_post_command()`` on the command instance.
|
||||
|
||||
Assorted notes
|
||||
--------------
|
||||
|
||||
The return value of ``Command.func()`` *is* safely passed on should one
|
||||
have some very specific use case in mind. So one could in principle do
|
||||
``value = obj.execute_cmd(cmdname)``. Evennia does not use this
|
||||
functionality at all by default (all default commands simply returns
|
||||
``None``) and it's probably not relevant to any but the most
|
||||
advanced/exotic designs (one might use it to create a "nested" command
|
||||
structure for example).
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ and running a text-based massively-multiplayer game
|
|||
your very own. You might just be starting to think about it, or you
|
||||
might have lugged around that *perfect* game in your mind for years ...
|
||||
you know *just* how good it would be, if you could only make it come to
|
||||
reality. We know how you feel. That is, after all why Evennia came to
|
||||
reality. We know how you feel. That is, after all, why Evennia came to
|
||||
be.
|
||||
|
||||
Evennia is in principle a MUD-building system: a bare-bones Python
|
||||
|
|
@ -33,11 +33,11 @@ will in that case all be optional.
|
|||
|
||||
What we *do* however, is to provide a solid foundation for all the
|
||||
boring database, networking, and behind-the-scenes administration stuff
|
||||
that all online games need whether they like it or not. Evennia is by
|
||||
default *fully persistent*, that means things you drop on the ground
|
||||
somewhere will still be there a dozen server reboots later. Through
|
||||
Django, we support a large variety of different database systems (the
|
||||
default of which is created for you automatically).
|
||||
that all online games need whether they like it or not. Evennia is
|
||||
*fully persistent*, that means things you drop on the ground somewhere
|
||||
will still be there a dozen server reboots later. Through Django we
|
||||
support a large variety of different database systems (a database is
|
||||
created for you automatically if you use the defaults).
|
||||
|
||||
Using the full power of Python throughout the server offers some
|
||||
distinct advantages. All your coding, from object definitions and custom
|
||||
|
|
@ -104,11 +104,11 @@ manual <http://code.google.com/p/evennia/wiki/Index>`_ with lots of
|
|||
examples. But while Python is a relatively easy programming language, it
|
||||
still represents a learning curve if you are new to programming. You
|
||||
should probably sit down with a Python beginner's
|
||||
`tutorial <http://docs.python.org/tutorial/tutorial>`_ (there are plenty
|
||||
of them on the web if you look around) so you at least know know what
|
||||
you are seeing. To efficiently code your dream game in Evennia you don't
|
||||
need to be a Python guru, but you do need to be able to read example
|
||||
code containing at least these basic Python features:
|
||||
`tutorial <http://docs.python.org/tutorial/>`_ (there are plenty of them
|
||||
on the web if you look around) so you at least know what you are seeing.
|
||||
To efficiently code your dream game in Evennia you don't need to be a
|
||||
Python guru, but you do need to be able to read example code containing
|
||||
at least these basic Python features:
|
||||
|
||||
- Importing python modules
|
||||
- Using variables, `conditional
|
||||
|
|
|
|||
|
|
@ -44,8 +44,7 @@ Evennia:
|
|||
|
||||
**Python** (http://www.python.org)
|
||||
|
||||
- Version 2.5+ strongly recommended, although 2.3 or 2.4 **may** work.
|
||||
Obs- Python3.x is not supported yet.
|
||||
- Version 2.5+. Obs- Python3.x is not supported yet.
|
||||
- The default database system SQLite3 only comes as part of Python2.5
|
||||
and later.
|
||||
- Windows users are recommended to use ActivePython
|
||||
|
|
@ -97,8 +96,8 @@ Installing pre-requisites
|
|||
|
||||
**Linux** package managers should usually handle all this for you.
|
||||
Python itself is definitely available through all distributions. On
|
||||
Debian-derived systems you can do something like this (as root) to get
|
||||
all you need:
|
||||
Debian-derived systems (such as Ubuntu) you can do something like this
|
||||
(as root) to get all you need:
|
||||
|
||||
::
|
||||
|
||||
|
|
@ -164,6 +163,7 @@ In the future, you just do
|
|||
::
|
||||
|
||||
hg pull
|
||||
hg update
|
||||
|
||||
from your ``evennia/`` directory to obtain the latest updates.
|
||||
|
||||
|
|
|
|||
|
|
@ -60,12 +60,12 @@ form.
|
|||
|
||||
::
|
||||
|
||||
django-admin.py compilemessages
|
||||
django-admin compilemessages
|
||||
|
||||
This will go through all languages and create/update compiled files
|
||||
(``*.mo``) for them. This needs to be done whenever a ``*.po`` file is
|
||||
updated.
|
||||
|
||||
When you are done, send the ``*.po`` and \*.mo file to the Evennia
|
||||
When you are done, send the ``*.po`` and ``*.mo`` file to the Evennia
|
||||
developer list (or push it into your own repository clone) so we can
|
||||
integrate your translation into Evennia!
|
||||
|
|
|
|||
|
|
@ -99,6 +99,121 @@ be named exactly like this):
|
|||
parsed. From inside Evennia, ``data_out`` is often called with the
|
||||
alias ``msg`` instead.
|
||||
|
||||
Out-of-band communication
|
||||
-------------------------
|
||||
|
||||
Out-of-band communication (OOB) is data being sent to and fro the
|
||||
player's client and the server on the protocol level, often due to the
|
||||
request of the player's client software rather than any sort of active
|
||||
input by the player. There are two main types:
|
||||
|
||||
- Data requested by the client which the server responds to
|
||||
immediately. This could for example be data that should go into a
|
||||
window that the client just opened up.
|
||||
- Data the server sends to the client to keep ut up-to-date. A common
|
||||
example of this is something like a graphical health bar - *whenever*
|
||||
the character's health status changes the server sends this data to
|
||||
the client so it can update the bar graphic. This sending could also
|
||||
be done on a timer, for example updating a weather map regularly.
|
||||
|
||||
To communicate to the client, there are a range of protocols available
|
||||
for MUDs, supported by different clients, such as MSDP and GMCP. They
|
||||
basically implements custom telnet negotiation sequences and goes into a
|
||||
custom Evennia Portal protocol so Evennia can understand it.
|
||||
|
||||
It then needs to translate each protocol-specific function into an
|
||||
Evennia function name - specifically a name of a module-level function
|
||||
you define in the module given by ``settings.OOB_FUNC_MODULE``. These
|
||||
function will get the session/character as first argument but is
|
||||
otherwise completely free of form. The portal packs all function names
|
||||
and eventual arguments they need in a dictionary and sends them off to
|
||||
the Server by use of the ``sessionhandler.oob_data_in()`` method. On the
|
||||
Server side, the dictionary is parsed, and the correct functions in
|
||||
``settings.OOB_FUNC_MODULE`` are called with the given arguments. The
|
||||
results from this function are again packed in a dictionary (keyed by
|
||||
function name) and sent back to the portal. It will appear in the Portal
|
||||
session's ``oob_data_out(data)`` method.
|
||||
|
||||
So to summarize: To implement a Portal protocol with OOB communication
|
||||
support, you need to first let your normal ``getData`` method somehow
|
||||
parse out the special protocol format format coming in from the client
|
||||
(MSDP, GMCP etc). It needs to translate what the client wants into
|
||||
function names matching that in the ``OOB_FUNC_MODULE`` - these
|
||||
functions need to be created to match too of course. The function name
|
||||
and arguments are packed in a dictionary and sent off to the server via
|
||||
``sessionhandler.oob_data_in()``. Finally, the portal session must
|
||||
implement ``oob_data_out(data)`` to handle the data coming back from
|
||||
Server. It will be a dictionary of return values keyed by the function
|
||||
names.
|
||||
|
||||
Example of out-of-band calling sequence
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Let's say we want our client to be able to request the character's
|
||||
current health. In our Portal protocol we somehow parse the incoming
|
||||
data stream and figure out what the request for health looks like. We
|
||||
map this to the Evennia ``get_health`` function.
|
||||
|
||||
We point ``settings.OOB_FUNC_MODULE`` to someplace in ``game/`` and
|
||||
create a module there with the following function:
|
||||
|
||||
::
|
||||
|
||||
# the caller is always added as first argument
|
||||
# we also assume health is stored as a simple
|
||||
# attribute on the character here.
|
||||
def get_health(character):
|
||||
return character.db.health
|
||||
|
||||
Done, this function will do just what we want. Let's finish up the first
|
||||
part of the portal protocol:
|
||||
|
||||
::
|
||||
|
||||
# this method could be named differently depending on the
|
||||
# protocol you are using (this is telnet)
|
||||
def lineReceived(self, string):
|
||||
# (does stuff to analyze the incoming string) outdict =
|
||||
if GET_HEALTH:
|
||||
# call get_health(char)
|
||||
outdict["get_health"] = ([], )
|
||||
elif GET_MANA:
|
||||
# call get_mana(char)
|
||||
outdict["get_mana"] = ([], )
|
||||
elif GET_CONFIG:
|
||||
# call get_config(char, 2, hidden=True)
|
||||
outdict["get_config"] = ([2], 'hidden':True) [...] self.sessionhandler.oob_data_out(outdict)
|
||||
|
||||
The server will properly accept this and call get\_health and get the
|
||||
right value for the health. We need to define an ``oob_data_out(data)``
|
||||
in our portal protocol to catch the return value:
|
||||
|
||||
::
|
||||
|
||||
def oob_data_out(self, data):
|
||||
# the indata is a dicationary funcname:retval outstring = ""
|
||||
for funcname, retval in data.items():
|
||||
if funcname == 'get_health':
|
||||
# convert to the right format for sending back to client, store
|
||||
# in outstring ...
|
||||
[...]
|
||||
|
||||
Above, once the dict is parsed and the return values properly put in a
|
||||
format the client will understand, send the whole thing off using the
|
||||
protocol's relevant send method.
|
||||
|
||||
Implementing auto-sending
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To have the Server update the client regularly, simply create a global
|
||||
`Script <Scripts.html>`_ that upon each repeat creates the request
|
||||
dictionary (basically faking a request from the portal) and sends it
|
||||
directly to
|
||||
``src.server.sessionhandler.oob_data_in(session.sessid, datadict)``.
|
||||
Repeat for all sessions. All specified OOB functions are called as
|
||||
normal and data will be sent back to be handled by the portal just as if
|
||||
the portal initiated the request.
|
||||
|
||||
Assorted notes
|
||||
--------------
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ root directory and type:
|
|||
::
|
||||
|
||||
hg pull
|
||||
hg update
|
||||
|
||||
Assuming you've got the command line client. If you're using a graphical
|
||||
client, you will probably want to navigate to the ``evennia`` directory
|
||||
|
|
@ -112,8 +113,8 @@ used (you have to give the ``mange.py migrate`` command as well as
|
|||
|
||||
Once you have a database ready and using South, you work as normal.
|
||||
Whenever a new Evennia update tells you that the database schema has
|
||||
changed (check ``hg log`` or the online list), you go to ``game/`` and
|
||||
run this command:
|
||||
changed (check ``hg log`` after you pulled the latest stuff, or read the
|
||||
online list), you go to ``game/`` and run this command:
|
||||
|
||||
::
|
||||
|
||||
|
|
|
|||
|
|
@ -355,6 +355,10 @@ def handle_args(options, mode, service):
|
|||
errmsg = _("The %s does not seem to be running.")
|
||||
|
||||
if mode == 'start':
|
||||
|
||||
# launch the error checker. Best to catch the errors already here.
|
||||
error_check_python_modules()
|
||||
|
||||
# starting one or many services
|
||||
if service == 'server':
|
||||
if inter:
|
||||
|
|
@ -397,6 +401,42 @@ def handle_args(options, mode, service):
|
|||
kill(SERVER_PIDFILE, SIG, _("Server stopped."), errmsg % 'Server', restart=False)
|
||||
return None
|
||||
|
||||
def error_check_python_modules():
|
||||
"""
|
||||
Import settings modules in settings. This will raise exceptions on
|
||||
pure python-syntax issues which are hard to catch gracefully
|
||||
with exceptions in the engine (since they are formatting errors in
|
||||
the python source files themselves). Best they fail already here
|
||||
before we get any further.
|
||||
"""
|
||||
def imp(path, split=True):
|
||||
mod, fromlist = path, "None"
|
||||
if split:
|
||||
mod, fromlist = path.rsplit('.', 1)
|
||||
__import__(mod, fromlist=[fromlist])
|
||||
|
||||
# core modules
|
||||
imp(settings.COMMAND_PARSER)
|
||||
imp(settings.SEARCH_AT_RESULT)
|
||||
imp(settings.SEARCH_AT_MULTIMATCH_INPUT)
|
||||
imp(settings.CONNECTION_SCREEN_MODULE, split=False)
|
||||
#imp(settings.AT_INITIAL_SETUP_HOOK_MODULE, split=False)
|
||||
for path in settings.LOCK_FUNC_MODULES:
|
||||
imp(path, split=False)
|
||||
# cmdsets
|
||||
from src.commands import cmdsethandler
|
||||
cmdsethandler.import_cmdset(settings.CMDSET_UNLOGGEDIN, None)
|
||||
cmdsethandler.import_cmdset(settings.CMDSET_DEFAULT, None)
|
||||
cmdsethandler.import_cmdset(settings.CMDSET_OOC, None)
|
||||
# typeclasses
|
||||
imp(settings.BASE_PLAYER_TYPECLASS)
|
||||
imp(settings.BASE_OBJECT_TYPECLASS)
|
||||
imp(settings.BASE_CHARACTER_TYPECLASS)
|
||||
imp(settings.BASE_ROOM_TYPECLASS)
|
||||
imp(settings.BASE_EXIT_TYPECLASS)
|
||||
imp(settings.BASE_SCRIPT_TYPECLASS)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
This handles command line input.
|
||||
|
|
@ -438,6 +478,7 @@ def main():
|
|||
Popen(cmdstr)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
from src.utils.utils import check_evennia_dependencies
|
||||
if check_evennia_dependencies():
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from game.gamesrc.commands.basecommand import Command
|
|||
|
||||
#from contrib import menusystem, lineeditor
|
||||
#from contrib import misc_commands
|
||||
#from contrib import chargen, menu_login
|
||||
|
||||
class DefaultCmdSet(cmdset_default.DefaultCmdSet):
|
||||
"""
|
||||
|
|
@ -49,7 +50,7 @@ class DefaultCmdSet(cmdset_default.DefaultCmdSet):
|
|||
#
|
||||
#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 +76,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
|
||||
|
|
@ -92,9 +92,8 @@ class OOCCmdSet(cmdset_ooc.OOCCmdSet):
|
|||
|
||||
#
|
||||
# any commands you add below will overload the default ones.
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
class BaseCmdSet(CmdSet):
|
||||
"""
|
||||
Implements an empty, example cmdset.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ class CmdNudge(Command):
|
|||
|
||||
key = "nudge lid" # two-word command name!
|
||||
aliases = ["nudge"]
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
|
|
@ -54,6 +55,7 @@ class CmdPush(Command):
|
|||
"""
|
||||
key = "push button"
|
||||
aliases = ["push", "press button", "press"]
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
|
|
@ -94,6 +96,7 @@ class CmdSmashGlass(Command):
|
|||
|
||||
key = "smash glass"
|
||||
aliases = ["smash lid", "break lid", "smash"]
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
|
|
@ -129,6 +132,7 @@ class CmdOpenLid(Command):
|
|||
|
||||
key = "open lid"
|
||||
aliases = ["open button", 'open']
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"simply call the right function."
|
||||
|
|
@ -159,6 +163,7 @@ class CmdCloseLid(Command):
|
|||
|
||||
key = "close lid"
|
||||
aliases = ["close"]
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Close the lid"
|
||||
|
|
@ -183,6 +188,8 @@ class CmdBlindLook(Command):
|
|||
|
||||
key = "look"
|
||||
aliases = ["l", "get", "examine", "ex", "feel", "listen"]
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"This replaces all the senses when blinded."
|
||||
|
||||
|
|
@ -215,6 +222,8 @@ class CmdBlindHelp(Command):
|
|||
"""
|
||||
key = "help"
|
||||
aliases = "h"
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Give a message."
|
||||
self.caller.msg("You are beyond help ... until you can see again.")
|
||||
|
|
|
|||
|
|
@ -99,37 +99,13 @@ class Object(BaseObject):
|
|||
class Character(BaseCharacter):
|
||||
"""
|
||||
This is the default object created for a new user connecting - the
|
||||
in-game player character representation. Note that it's important
|
||||
that at_object_creation sets up an script that adds the Default
|
||||
command set whenever the player logs in - otherwise they won't be
|
||||
able to use any commands!
|
||||
in-game player character representation. The basetype_setup always
|
||||
assigns the default_cmdset as a fallback to objects of this type.
|
||||
The default hooks also hide the character object away (by moving
|
||||
it to a Null location whenever the player logs off (otherwise the
|
||||
character would remain in the world, "headless" so to say).
|
||||
"""
|
||||
|
||||
def at_disconnect(self):
|
||||
"""
|
||||
We stove away the character when logging off, otherwise the character object will
|
||||
remain in the room also after the player logged off ("headless", so to say).
|
||||
"""
|
||||
if self.location: # have to check, in case of multiple connections closing
|
||||
self.location.msg_contents("%s has left the game." % self.name)
|
||||
self.db.prelogout_location = self.location
|
||||
self.location = None
|
||||
|
||||
def at_post_login(self):
|
||||
"""
|
||||
This recovers the character again after having been "stoved away" at disconnect.
|
||||
"""
|
||||
if self.db.prelogout_location:
|
||||
# try to recover
|
||||
self.location = self.db.prelogout_location
|
||||
if self.location == None:
|
||||
# make sure location is never None (home should always exist)
|
||||
self.location = self.home
|
||||
# save location again to be sure
|
||||
self.db.prelogout_location = self.location
|
||||
|
||||
self.location.msg_contents("%s has entered the game." % self.name)
|
||||
self.location.at_object_receive(self, self.location)
|
||||
pass
|
||||
|
||||
class Room(BaseRoom):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -7,16 +7,18 @@
|
|||
# The names of the string variables doesn't matter (except they
|
||||
# shouldn't start with _), but each should hold a string defining a
|
||||
# connection screen - as seen when first connecting to the game
|
||||
# (before having logged in). If there are more than one string
|
||||
# variable defined, a random one is picked.
|
||||
# (before having logged in).
|
||||
#
|
||||
# After adding new connection screens to this module you must
|
||||
# either reboot or reload the server to make them available.
|
||||
# OBS - If there are more than one string variable viable in this
|
||||
# module, a random one is picked!
|
||||
#
|
||||
# After adding new connection screens to this module you must either
|
||||
# reboot or reload the server to make them available.
|
||||
#
|
||||
|
||||
from src.commands.connection_screen import DEFAULT_SCREEN
|
||||
|
||||
# from src.utils import utils
|
||||
#from src.utils import utils
|
||||
#
|
||||
# CUSTOM_SCREEN = \
|
||||
# """{b=============================================================={n
|
||||
|
|
@ -29,3 +31,12 @@ from src.commands.connection_screen import DEFAULT_SCREEN
|
|||
#
|
||||
# Enter {whelp{n for more info. {wlook{n will re-load this screen.
|
||||
#{b=============================================================={n""" % utils.get_evennia_version()
|
||||
|
||||
|
||||
# # A suggested alternative screen for the Menu login system
|
||||
|
||||
# from src.utils import utils
|
||||
# MENU_SCREEN = \
|
||||
# """{b=============================================================={n
|
||||
# Welcome to {gEvennnia{n, version %s!
|
||||
# {b=============================================================={n""" % utils.get_evennia_version()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -52,11 +50,17 @@ COMMAND_PARSER = utils.mod_import(*settings.COMMAND_PARSER.rsplit('.', 1))
|
|||
# allow for custom behaviour when the command handler hits
|
||||
# special situations -- it then calls a normal Command
|
||||
# that you can customize!
|
||||
# Import these variables and use them rather than trying
|
||||
# to remember the actual string constants.
|
||||
|
||||
CMD_NOINPUT = "__noinput_command"
|
||||
CMD_NOMATCH = "__nomatch_command"
|
||||
CMD_MULTIMATCH = "__multimatch_command"
|
||||
CMD_CHANNEL = "__send_to_channel"
|
||||
CMD_CHANNEL = "__send_to_channel_command"
|
||||
# this is the name of the command the engine calls when the player
|
||||
# connects. It is expected to show the login screen.
|
||||
CMD_LOGINSTART = "__unloggedin_look_command"
|
||||
|
||||
|
||||
class NoCmdSets(Exception):
|
||||
"No cmdsets found. Critical error."
|
||||
|
|
@ -115,13 +119,14 @@ def get_and_merge_cmdsets(caller):
|
|||
try:
|
||||
player_cmdset = caller.player.cmdset.current
|
||||
except AttributeError:
|
||||
player_cmdset = None
|
||||
player_cmdset = None
|
||||
|
||||
cmdsets = [caller_cmdset] + [player_cmdset] + [channel_cmdset] + local_objects_cmdsets
|
||||
# weed out all non-found sets
|
||||
cmdsets = [cmdset for cmdset in cmdsets if cmdset]
|
||||
# sort cmdsets after reverse priority (highest prio are merged in last)
|
||||
cmdsets = sorted(cmdsets, key=lambda x: x.priority)
|
||||
|
||||
if cmdsets:
|
||||
# Merge all command sets into one, beginning with the lowest-prio one
|
||||
cmdset = cmdsets.pop(0)
|
||||
|
|
@ -131,7 +136,7 @@ def get_and_merge_cmdsets(caller):
|
|||
cmdset = merging_cmdset + cmdset
|
||||
else:
|
||||
cmdset = None
|
||||
|
||||
|
||||
for cset in (cset for cset in local_objects_cmdsets if cset):
|
||||
cset.duplicates = cset.old_duplicates
|
||||
|
||||
|
|
@ -140,27 +145,21 @@ 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
|
||||
# print cmdset
|
||||
if not cmdset:
|
||||
# this is bad and shouldn't happen.
|
||||
raise NoCmdSets
|
||||
|
|
@ -171,12 +170,10 @@ def cmdhandler(caller, raw_string, unloggedin=False, testing=False):
|
|||
syscmd = cmdset.get(CMD_NOINPUT)
|
||||
sysarg = ""
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
|
||||
# Parse the input string and match to available cmdset.
|
||||
# This also checks for permissions, so all commands in match
|
||||
# are commands the caller is allowed to call.
|
||||
matches = COMMAND_PARSER(raw_string, cmdset, caller)
|
||||
|
||||
# Deal with matches
|
||||
if not matches:
|
||||
# No commands match our entered command
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
matches.extend([create_match(cmdname, raw_string, cmd)
|
||||
for cmdname in [cmd.key] + cmd.aliases
|
||||
if cmdname and l_raw_string.startswith(cmdname.lower())])
|
||||
|
||||
if not matches:
|
||||
# no matches found.
|
||||
if '-' in raw_string:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
cmd.obj = self.cmdsetobj
|
||||
try:
|
||||
ic = self.commands.index(cmd)
|
||||
self.commands[ic] = cmd # replace
|
||||
except ValueError:
|
||||
self.commands.append(cmd)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -121,7 +121,8 @@ def import_cmdset(python_path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
|||
logger.log_trace()
|
||||
if emit_to_obj and not ServerConfig.objects.conf("server_starting_mode"):
|
||||
object.__getattribute__(emit_to_obj, "msg")(errstring)
|
||||
#raise # have to raise, or we will not see any errors in some situations!
|
||||
logger.log_errmsg("Error: %s" % errstring)
|
||||
raise # have to raise, or we will not see any errors in some situations!
|
||||
|
||||
# classes
|
||||
|
||||
|
|
@ -246,8 +247,8 @@ class CmdSetHandler(object):
|
|||
def add(self, cmdset, emit_to_obj=None, permanent=False):
|
||||
"""
|
||||
Add a cmdset to the handler, on top of the old ones.
|
||||
Default is to not make this permanent (i.e. no script
|
||||
will be added to add the cmdset every server start/login).
|
||||
Default is to not make this permanent, i.e. the set
|
||||
will not survive a server reset.
|
||||
|
||||
cmdset - can be a cmdset object or the python path to
|
||||
such an object.
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -616,7 +616,7 @@ class CmdOOCLook(CmdLook):
|
|||
self.character = None
|
||||
if utils.inherits_from(self.caller, "src.objects.objects.Object"):
|
||||
# An object of some type is calling. Convert to player.
|
||||
print self.caller, self.caller.__class__
|
||||
#print self.caller, self.caller.__class__
|
||||
self.character = self.caller
|
||||
if hasattr(self.caller, "player"):
|
||||
self.caller = self.caller.player
|
||||
|
|
@ -685,6 +685,15 @@ class CmdIC(MuxCommand):
|
|||
if caller.swap_character(new_character):
|
||||
new_character.msg("\n{gYou become {c%s{n.\n" % new_character.name)
|
||||
caller.db.last_puppet = old_char
|
||||
if not new_character.location:
|
||||
# this might be due to being hidden away at logout; check
|
||||
loc = new_character.db.prelogout_location
|
||||
if not loc: # still no location; use home
|
||||
loc = new_character.home
|
||||
new_character.location = loc
|
||||
if new_character.location:
|
||||
new_character.location.msg_contents("%s has entered the game." % new_character.key, exclude=[new_character])
|
||||
new_character.location.at_object_receive(new_character, new_character.location)
|
||||
new_character.execute_cmd("look")
|
||||
else:
|
||||
caller.msg("{rYou cannot become {C%s{n." % new_character.name)
|
||||
|
|
@ -720,11 +729,15 @@ class CmdOOC(MuxCommand):
|
|||
return
|
||||
|
||||
caller.db.last_puppet = caller.character
|
||||
# save location as if we were disconnecting from the game entirely.
|
||||
if caller.character.location:
|
||||
caller.character.location.msg_contents("%s has left the game." % caller.character.key, exclude=[caller.character])
|
||||
caller.character.db.prelogout_location = caller.character.location
|
||||
caller.character.location = None
|
||||
|
||||
# disconnect
|
||||
caller.character.player = None
|
||||
caller.character = None
|
||||
|
||||
|
||||
caller.msg("\n{GYou go OOC.{n\n")
|
||||
caller.execute_cmd("look")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from src.comms.models import Channel
|
|||
|
||||
from src.utils import create, logger, utils, ansi
|
||||
from src.commands.default.muxcommand import MuxCommand
|
||||
from src.commands.cmdhandler import CMD_LOGINSTART
|
||||
|
||||
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
||||
|
||||
|
|
@ -169,20 +170,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(new_player)
|
||||
|
||||
# join the new player to the public channel
|
||||
pchanneldef = settings.CHANNEL_PUBLIC
|
||||
|
|
@ -191,10 +182,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.
|
||||
|
|
@ -221,10 +222,12 @@ class CmdQuit(MuxCommand):
|
|||
class CmdUnconnectedLook(MuxCommand):
|
||||
"""
|
||||
This is an unconnected version of the look command for simplicity.
|
||||
All it does is re-show the connect screen.
|
||||
|
||||
This is called by the server and kicks everything in gear.
|
||||
All it does is display the connect screen.
|
||||
"""
|
||||
key = "look"
|
||||
aliases = "l"
|
||||
key = CMD_LOGINSTART
|
||||
aliases = ["look", "l"]
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
|
|
|
|||
|
|
@ -68,6 +68,13 @@ class ObjectManager(TypedObjectManager):
|
|||
# use the id to find the player
|
||||
return self.get_object_with_user(dbref)
|
||||
|
||||
@returns_typeclass_list
|
||||
def get_objs_with_key_and_typeclass(self, oname, otypeclass_path):
|
||||
"""
|
||||
Returns objects based on simultaneous key and typeclass match.
|
||||
"""
|
||||
return self.filter(db_key__iexact=oname).filter(db_typeclass_path__exact=otypeclass_path)
|
||||
|
||||
# attr/property related
|
||||
|
||||
@returns_typeclass_list
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ class ObjectDB(TypedObject):
|
|||
string += "%s is not a valid home."
|
||||
self.msg(string % home)
|
||||
logger.log_trace(string)
|
||||
raise
|
||||
#raise
|
||||
self.save()
|
||||
#@home.deleter
|
||||
def home_del(self):
|
||||
|
|
|
|||
|
|
@ -409,6 +409,34 @@ class Character(Object):
|
|||
def at_after_move(self, source_location):
|
||||
"Default is to look around after a move."
|
||||
self.execute_cmd('look')
|
||||
|
||||
def at_disconnect(self):
|
||||
"""
|
||||
We stove away the character when logging off, otherwise the character object will
|
||||
remain in the room also after the player logged off ("headless", so to say).
|
||||
"""
|
||||
if self.location: # have to check, in case of multiple connections closing
|
||||
self.location.msg_contents("%s has left the game." % self.name, exclude=[self])
|
||||
self.db.prelogout_location = self.location
|
||||
self.location = None
|
||||
|
||||
def at_post_login(self):
|
||||
"""
|
||||
This recovers the character again after having been "stoved away" at disconnect.
|
||||
"""
|
||||
if self.db.prelogout_location:
|
||||
# try to recover
|
||||
self.location = self.db.prelogout_location
|
||||
if self.location == None:
|
||||
# make sure location is never None (home should always exist)
|
||||
self.location = self.home
|
||||
# save location again to be sure
|
||||
self.db.prelogout_location = self.location
|
||||
|
||||
self.location.msg_contents("%s has entered the game." % self.name, exclude=[self])
|
||||
self.location.at_object_receive(self, self.location)
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Base Room object
|
||||
|
|
|
|||
|
|
@ -34,10 +34,6 @@ SERVER_RESTART = os.path.join(settings.GAME_DIR, "server.restart")
|
|||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
# Signals
|
||||
|
||||
|
||||
|
||||
|
||||
def get_restart_mode(restart_file):
|
||||
"""
|
||||
|
|
@ -141,6 +137,24 @@ class MsgServer2Portal(amp.Command):
|
|||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
class OOBPortal2Server(amp.Command):
|
||||
"""
|
||||
OOB data portal -> server
|
||||
"""
|
||||
arguments = [('sessid', amp.Integer()),
|
||||
('data', amp.String())]
|
||||
errors = [(Exception, "EXCEPTION")]
|
||||
response = []
|
||||
|
||||
class OOBServer2Portal(amp.Command):
|
||||
"""
|
||||
OOB data server -> portal
|
||||
"""
|
||||
arguments = [('sessid', amp.Integer()),
|
||||
('data', amp.String())]
|
||||
errors = [(Exception, "EXCEPTION")]
|
||||
response = []
|
||||
|
||||
class ServerAdmin(amp.Command):
|
||||
"""
|
||||
Portal -> Server
|
||||
|
|
@ -168,6 +182,8 @@ class PortalAdmin(amp.Command):
|
|||
errors = [(Exception, 'EXCEPTION')]
|
||||
response = []
|
||||
|
||||
dumps = lambda data: utils.to_str(pickle.dumps(data, pickle.HIGHEST_PROTOCOL))
|
||||
loads = lambda data: pickle.loads(utils.to_str(data))
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Core AMP protocol for communication Server <-> Portal
|
||||
|
|
@ -220,7 +236,7 @@ class AMPProtocol(amp.AMP):
|
|||
Relays message to server. This method is executed on the Server.
|
||||
"""
|
||||
#print "msg portal -> server (server side):", sessid, msg
|
||||
self.factory.server.sessions.data_in(sessid, msg, pickle.loads(utils.to_str(data)))
|
||||
self.factory.server.sessions.data_in(sessid, msg, loads(data))
|
||||
return {}
|
||||
MsgPortal2Server.responder(amp_msg_portal2server)
|
||||
|
||||
|
|
@ -232,7 +248,7 @@ class AMPProtocol(amp.AMP):
|
|||
self.callRemote(MsgPortal2Server,
|
||||
sessid=sessid,
|
||||
msg=msg,
|
||||
data=utils.to_str(pickle.dumps(data))).addErrback(self.errback, "MsgPortal2Server")
|
||||
data=dumps(data)).addErrback(self.errback, "MsgPortal2Server")
|
||||
|
||||
# Server -> Portal message
|
||||
|
||||
|
|
@ -241,7 +257,7 @@ class AMPProtocol(amp.AMP):
|
|||
Relays message to Portal. This method is executed on the Portal.
|
||||
"""
|
||||
#print "msg server->portal (portal side):", sessid, msg
|
||||
self.factory.portal.sessions.data_out(sessid, msg, pickle.loads(utils.to_str(data)))
|
||||
self.factory.portal.sessions.data_out(sessid, msg, loads(data))
|
||||
return {}
|
||||
MsgServer2Portal.responder(amp_msg_server2portal)
|
||||
|
||||
|
|
@ -253,8 +269,50 @@ class AMPProtocol(amp.AMP):
|
|||
self.callRemote(MsgServer2Portal,
|
||||
sessid=sessid,
|
||||
msg=utils.to_str(msg),
|
||||
data=utils.to_str(pickle.dumps(data))).addErrback(self.errback, "MsgServer2Portal")
|
||||
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
|
||||
|
||||
# OOB Portal -> Server
|
||||
|
||||
# Portal -> Server Msg
|
||||
|
||||
def amp_oob_portal2server(self, sessid, data):
|
||||
"""
|
||||
Relays out-of-band data to server. This method is executed on the Server.
|
||||
"""
|
||||
#print "oob portal -> server (server side):", sessid, loads(data)
|
||||
self.factory.server.sessions.oob_data_in(sessid, loads(data))
|
||||
return {}
|
||||
OOBPortal2Server.responder(amp_oob_portal2server)
|
||||
|
||||
def call_remote_OOBPortal2Server(self, sessid, data=""):
|
||||
"""
|
||||
Access method called by the Portal and executed on the Portal.
|
||||
"""
|
||||
#print "oob portal->server (portal side):", sessid, data
|
||||
self.callRemote(OOBPortal2Server,
|
||||
sessid=sessid,
|
||||
data=dumps(data)).addErrback(self.errback, "OOBPortal2Server")
|
||||
|
||||
# Server -> Portal message
|
||||
|
||||
def amp_oob_server2portal(self, sessid, data):
|
||||
"""
|
||||
Relays out-of-band data to Portal. This method is executed on the Portal.
|
||||
"""
|
||||
#print "oob server->portal (portal side):", sessid, data
|
||||
self.factory.portal.sessions.oob_data_out(sessid, loads(data))
|
||||
return {}
|
||||
OOBServer2Portal.responder(amp_oob_server2portal)
|
||||
|
||||
def call_remote_OOBServer2Portal(self, sessid, data=""):
|
||||
"""
|
||||
Access method called by the Server and executed on the Server.
|
||||
"""
|
||||
#print "oob server->portal (server side):", sessid, data
|
||||
self.callRemote(OOBServer2Portal,
|
||||
sessid=sessid,
|
||||
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
|
||||
|
||||
|
||||
# Server administration from the Portal side
|
||||
|
||||
|
|
@ -264,7 +322,7 @@ class AMPProtocol(amp.AMP):
|
|||
operations on the server. This is executed on the Server.
|
||||
|
||||
"""
|
||||
data = pickle.loads(utils.to_str(data))
|
||||
data = loads(data)
|
||||
|
||||
#print "serveradmin (server side):", sessid, operation, data
|
||||
|
||||
|
|
@ -276,7 +334,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)
|
||||
|
||||
|
|
@ -315,7 +373,7 @@ class AMPProtocol(amp.AMP):
|
|||
Access method called by the Portal and Executed on the Portal.
|
||||
"""
|
||||
#print "serveradmin (portal side):", sessid, operation, data
|
||||
data = utils.to_str(pickle.dumps(data))
|
||||
data = dumps(data)
|
||||
|
||||
self.callRemote(ServerAdmin,
|
||||
sessid=sessid,
|
||||
|
|
@ -329,7 +387,7 @@ class AMPProtocol(amp.AMP):
|
|||
This allows the server to perform admin
|
||||
operations on the portal. This is executed on the Portal.
|
||||
"""
|
||||
data = pickle.loads(utils.to_str(data))
|
||||
data = loads(data)
|
||||
|
||||
#print "portaladmin (portal side):", sessid, operation, data
|
||||
if operation == 'SLOGIN': # 'server_session_login'
|
||||
|
|
@ -376,7 +434,7 @@ class AMPProtocol(amp.AMP):
|
|||
Access method called by the server side.
|
||||
"""
|
||||
#print "portaladmin (server side):", sessid, operation, data
|
||||
data = utils.to_str(pickle.dumps(data))
|
||||
data = dumps(data)
|
||||
|
||||
self.callRemote(PortalAdmin,
|
||||
sessid=sessid,
|
||||
|
|
|
|||
|
|
@ -59,8 +59,7 @@ def create_objects():
|
|||
character_typeclass=character_typeclass)
|
||||
|
||||
if not god_character:
|
||||
print _("#1 could not be created. Check the Player/Character typeclass for bugs.")
|
||||
raise Exception
|
||||
raise Exception(_("#1 could not be created. Check the Player/Character typeclass for bugs."))
|
||||
|
||||
god_character.id = 1
|
||||
god_character.db.desc = _('This is User #1.')
|
||||
|
|
|
|||
|
|
@ -12,12 +12,16 @@ from datetime import datetime
|
|||
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.utils import logger, utils
|
||||
from src.commands import cmdhandler, cmdsethandler
|
||||
from src.server.session import Session
|
||||
|
||||
IDLE_COMMAND = settings.IDLE_COMMAND
|
||||
|
||||
from src.server.session import Session
|
||||
|
||||
# load optional out-of-band function module
|
||||
OOB_FUNC_MODULE = settings.OOB_FUNC_MODULE
|
||||
if OOB_FUNC_MODULE:
|
||||
OOB_FUNC_MODULE = utils.mod_import(settings.OOB_FUNC_MODULE)
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
|
@ -37,7 +41,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 +51,18 @@ 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 session_login(self, player):
|
||||
"""
|
||||
Startup mechanisms that need to run at login. This is called
|
||||
|
|
@ -87,11 +96,10 @@ class ServerSession(Session):
|
|||
player.at_pre_login()
|
||||
|
||||
character = player.character
|
||||
#print "at_init() - character"
|
||||
character.at_init()
|
||||
if character:
|
||||
# this player has a character. Check if it's the
|
||||
# first time *this character* logs in
|
||||
character.at_init()
|
||||
if character.db.FIRST_LOGIN:
|
||||
character.at_first_login()
|
||||
del character.db.FIRST_LOGIN
|
||||
|
|
@ -181,7 +189,7 @@ class ServerSession(Session):
|
|||
if str(command_string).strip() == IDLE_COMMAND:
|
||||
self.update_session_counters(idle=True)
|
||||
return
|
||||
|
||||
|
||||
# all other inputs, including empty inputs
|
||||
character = self.get_character()
|
||||
|
||||
|
|
@ -193,8 +201,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):
|
||||
|
|
@ -203,9 +212,57 @@ 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.
|
||||
|
||||
This method parses the data input (a dict) and uses
|
||||
it to launch correct methods from those plugged into
|
||||
the system.
|
||||
|
||||
data = {funcname: ( [args], {kwargs]),
|
||||
funcname: ( [args], {kwargs}), ...}
|
||||
|
||||
example:
|
||||
data = {"get_hp": ([], {}),
|
||||
"update_counter", (["counter1"], {"now":True}) }
|
||||
"""
|
||||
|
||||
print "server: "
|
||||
outdata = {}
|
||||
|
||||
entity = self.get_character()
|
||||
if not entity:
|
||||
entity = self.get_player()
|
||||
if not entity:
|
||||
entity = self
|
||||
|
||||
for funcname, argtuple in data.items():
|
||||
# loop through the data, calling available functions.
|
||||
func = OOB_FUNC_MODULE.__dict__.get(funcname, None)
|
||||
if func:
|
||||
try:
|
||||
outdata[funcname] = func(entity, *argtuple[0], **argtuple[1])
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
else:
|
||||
logger.log_errmsg("oob_data_in error: funcname '%s' not found in OOB_FUNC_MODULE." % funcname)
|
||||
if outdata:
|
||||
self.oob_data_out(outdata)
|
||||
|
||||
|
||||
def oob_data_out(self, data):
|
||||
"""
|
||||
This sends data from Server to the Portal across the AMP connection.
|
||||
"""
|
||||
self.sessionhandler.oob_data_out(self, data)
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.address == other.address
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
String representation of the user session class. We use
|
||||
|
|
@ -239,3 +296,57 @@ class ServerSession(Session):
|
|||
def msg(self, string='', data=None):
|
||||
"alias for at_data_out"
|
||||
self.data_out(string, data=data)
|
||||
|
||||
|
||||
# Dummy API hooks for use a non-loggedin operation
|
||||
|
||||
def at_cmdset_get(self):
|
||||
"dummy hook all objects with cmdsets need to have"
|
||||
pass
|
||||
|
||||
# Mock db/ndb properties for allowing easy storage on the session
|
||||
# (note that no databse is involved at all here. session.db.attr =
|
||||
# value just saves a normal property in memory, just like ndb).
|
||||
|
||||
#@property
|
||||
def ndb_get(self):
|
||||
"""
|
||||
A non-persistent store (ndb: NonDataBase). Everything stored
|
||||
to this is guaranteed to be cleared when a server is shutdown.
|
||||
Syntax is same as for the _get_db_holder() method and
|
||||
property, e.g. obj.ndb.attr = value etc.
|
||||
"""
|
||||
try:
|
||||
return self._ndb_holder
|
||||
except AttributeError:
|
||||
class NdbHolder(object):
|
||||
"Holder for storing non-persistent attributes."
|
||||
def all(self):
|
||||
return [val for val in self.__dict__.keys()
|
||||
if not val.startswith['_']]
|
||||
def __getattribute__(self, key):
|
||||
# return None if no matching attribute was found.
|
||||
try:
|
||||
return object.__getattribute__(self, key)
|
||||
except AttributeError:
|
||||
return None
|
||||
self._ndb_holder = NdbHolder()
|
||||
return self._ndb_holder
|
||||
#@ndb.setter
|
||||
def ndb_set(self, value):
|
||||
"Stop accidentally replacing the db object"
|
||||
string = "Cannot assign directly to ndb object! "
|
||||
string = "Use ndb.attr=value instead."
|
||||
raise Exception(string)
|
||||
#@ndb.deleter
|
||||
def ndb_del(self):
|
||||
"Stop accidental deletion."
|
||||
raise Exception("Cannot delete the ndb object!")
|
||||
ndb = property(ndb_get, ndb_set, ndb_del)
|
||||
db = property(ndb_get, ndb_set, ndb_del)
|
||||
|
||||
# Mock access method for the session (there is no lock info
|
||||
# at this stage, so we just present a uniform API)
|
||||
def access(self, *args, **kwargs):
|
||||
"Dummy method."
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -123,3 +123,20 @@ class Session(object):
|
|||
"""
|
||||
pass
|
||||
|
||||
def oob_data_out(self, data):
|
||||
"""
|
||||
for Portal, this receives out-of-band data from Server across the AMP.
|
||||
for Server, this sends out-of-band data to Portal.
|
||||
|
||||
data is a dictionary
|
||||
"""
|
||||
pass
|
||||
|
||||
def oob_data_in(self, data):
|
||||
"""
|
||||
for Portal, this sends out-of-band requests to Server over the AMP.
|
||||
for Server, this receives data from Portal.
|
||||
|
||||
data is a dictionary
|
||||
"""
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ from django.contrib.auth.models import User
|
|||
from src.server.models import ServerConfig
|
||||
from src.utils import utils
|
||||
|
||||
from src.commands.cmdhandler import CMD_LOGINSTART
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
|
@ -94,7 +96,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
Creates a new, unlogged-in game session.
|
||||
"""
|
||||
self.sessions[sessid] = session
|
||||
session.execute_cmd('look')
|
||||
session.execute_cmd(CMD_LOGINSTART)
|
||||
|
||||
def portal_disconnect(self, sessid):
|
||||
"""
|
||||
|
|
@ -293,6 +295,20 @@ class ServerSessionHandler(SessionHandler):
|
|||
# to put custom effects on the server due to data input, e.g.
|
||||
# from a custom client.
|
||||
|
||||
def oob_data_in(self, sessid, data):
|
||||
"""
|
||||
OOB (Out-of-band) Data Portal -> Server
|
||||
"""
|
||||
session = self.sessions.get(sessid, None)
|
||||
if session:
|
||||
session.oob_data_in(data)
|
||||
|
||||
def oob_data_out(self, session, data):
|
||||
"""
|
||||
OOB (Out-of-band) Data Server -> Portal
|
||||
"""
|
||||
self.server.amp_protocol.call_remote_OOBServer2Portal(session.sessid,
|
||||
data=data)
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Portal-SessionHandler class
|
||||
|
|
@ -390,5 +406,20 @@ class PortalSessionHandler(SessionHandler):
|
|||
if session:
|
||||
session.data_out(string, data=data)
|
||||
|
||||
def oob_data_in(self, session, data):
|
||||
"""
|
||||
OOB (Out-of-band) data Portal -> Server
|
||||
"""
|
||||
self.portal.amp_protocol.call_remote_OOBPortal2Server(session.sessid,
|
||||
data=data)
|
||||
|
||||
def oob_data_out(self, sessid, data):
|
||||
"""
|
||||
OOB (Out-of-band) data Server -> Portal
|
||||
"""
|
||||
session = self.sessions.get(sessid, None)
|
||||
if session:
|
||||
session.oob_data_out(data)
|
||||
|
||||
SESSIONS = ServerSessionHandler()
|
||||
PORTAL_SESSIONS = PortalSessionHandler()
|
||||
|
|
|
|||
|
|
@ -167,6 +167,12 @@ AT_INITIAL_SETUP_HOOK_MODULE = "game.gamesrc.world.at_initial_setup"
|
|||
###################################################
|
||||
# Default command sets
|
||||
###################################################
|
||||
# Note that with the exception of the unloggedin set (which is not
|
||||
# stored anywhere), changing these paths will only affect NEW created
|
||||
# characters, not those already in play. So if you plan to change
|
||||
# this, it's recommended you do it on a pristine setup only. To
|
||||
# dynamically add new commands to a running server, extend/overload
|
||||
# these existing sets instead.
|
||||
|
||||
# Command set used before player has logged in
|
||||
CMDSET_UNLOGGEDIN = "game.gamesrc.commands.basecmdset.UnloggedinCmdSet"
|
||||
|
|
@ -246,6 +252,8 @@ PERMISSION_PLAYER_DEFAULT = "Players"
|
|||
# Tuple of modules implementing lock functions. All callable functions
|
||||
# inside these modules will be available as lock functions.
|
||||
LOCK_FUNC_MODULES = ("src.locks.lockfuncs",)
|
||||
# Module holding server-side functions for out-of-band protocols to call.
|
||||
OOB_FUNC_MODULE = ""
|
||||
|
||||
|
||||
###################################################
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ def create_object(typeclass, key=None, location=None,
|
|||
# this will either load the typeclass or the default one
|
||||
new_object = new_db_object.typeclass
|
||||
|
||||
|
||||
if not object.__getattribute__(new_db_object, "is_typeclass")(typeclass, exact=True):
|
||||
# this will fail if we gave a typeclass as input and it still gave us a default
|
||||
SharedMemoryModel.delete(new_db_object)
|
||||
|
|
@ -105,6 +106,10 @@ def create_object(typeclass, key=None, location=None,
|
|||
# perform a move_to in order to display eventual messages.
|
||||
if home:
|
||||
new_object.home = home
|
||||
else:
|
||||
new_object.home = settings.CHARACTER_DEFAULT_HOME
|
||||
|
||||
|
||||
if location:
|
||||
new_object.move_to(location, quiet=True)
|
||||
else:
|
||||
|
|
@ -389,6 +394,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:
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ def has_parent(basepath, obj):
|
|||
|
||||
def mod_import(mod_path, propname=None):
|
||||
"""
|
||||
Takes filename of a module, converts it to a python path
|
||||
Takes filename of a module (a python path or a full pathname)
|
||||
and imports it. If property is given, return the named
|
||||
property from this module instead of the module itself.
|
||||
"""
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue