More whitespace cleanup.

This commit is contained in:
Griatch 2012-03-30 23:57:04 +02:00
parent c0322c9eae
commit 45c5be8468
43 changed files with 1116 additions and 1131 deletions

View file

@ -2,7 +2,7 @@
""" """
EVENNIA SERVER STARTUP SCRIPT EVENNIA SERVER STARTUP SCRIPT
This is the start point for running Evennia. This is the start point for running Evennia.
Sets the appropriate environmental variables and launches the server Sets the appropriate environmental variables and launches the server
and portal through the runner. Run without arguments to get a and portal through the runner. Run without arguments to get a
@ -47,12 +47,12 @@ if not os.path.exists('settings.py'):
When you are set up, run evennia.py again to start the server.""" When you are set up, run evennia.py again to start the server."""
sys.exit() sys.exit()
# signal processing # signal processing
SIG = signal.SIGINT SIG = signal.SIGINT
HELPENTRY = \ HELPENTRY = \
""" """
(version %s) (version %s)
This program launches Evennia with various options. You can access all This program launches Evennia with various options. You can access all
this functionality directly from the command line; for example option this functionality directly from the command line; for example option
@ -83,7 +83,7 @@ directly see tracebacks on standard output, so starting with options
server (option 5) to make it available to users. server (option 5) to make it available to users.
Reload and stop is not well supported in Windows. If you have issues, log Reload and stop is not well supported in Windows. If you have issues, log
into the game to stop or restart the server instead. into the game to stop or restart the server instead.
""" """
MENU = \ MENU = \
@ -160,7 +160,7 @@ except DatabaseError:
Your database does not seem to be set up correctly. Your database does not seem to be set up correctly.
Please run: Please run:
python manage.py syncdb python manage.py syncdb
(make sure to create an admin user when prompted). If you use (make sure to create an admin user when prompted). If you use
@ -231,7 +231,7 @@ if os.name == 'nt':
# Functions # Functions
def get_pid(pidfile): def get_pid(pidfile):
""" """
Get the PID (Process ID) by trying to access Get the PID (Process ID) by trying to access
@ -319,7 +319,7 @@ def run_menu():
if inp == 5: if inp == 5:
if os.name == 'nt': if os.name == 'nt':
print "This operation is not supported under Windows. Log into the game to restart/reload the server." print "This operation is not supported under Windows. Log into the game to restart/reload the server."
return return
kill(SERVER_PIDFILE, SIG, "Server reloaded.", errmsg % "Server") kill(SERVER_PIDFILE, SIG, "Server reloaded.", errmsg % "Server")
elif inp == 6: elif inp == 6:
if os.name == 'nt': if os.name == 'nt':
@ -353,8 +353,8 @@ def handle_args(options, mode, service):
errmsg = "The %s does not seem to be running." errmsg = "The %s does not seem to be running."
if mode == 'start': if mode == 'start':
# launch the error checker. Best to catch the errors already here. # launch the error checker. Best to catch the errors already here.
error_check_python_modules() error_check_python_modules()
# starting one or many services # starting one or many services
@ -376,13 +376,13 @@ def handle_args(options, mode, service):
# restarting services # restarting services
if os.name == 'nt': if os.name == 'nt':
print "Restarting from command line is not supported under Windows. Log into the game to restart." print "Restarting from command line is not supported under Windows. Log into the game to restart."
return return
if service == 'server': if service == 'server':
kill(SERVER_PIDFILE, SIG, "Server reloaded.", errmsg % 'Server') kill(SERVER_PIDFILE, SIG, "Server reloaded.", errmsg % 'Server')
elif service == 'portal': elif service == 'portal':
print """ print """
Note: Portal usually don't need to be reloaded unless you are debugging in interactive mode. Note: Portal usually don't need to be reloaded unless you are debugging in interactive mode.
If Portal was running in default Daemon mode, it cannot be restarted. In that case you have If Portal was running in default Daemon mode, it cannot be restarted. In that case you have
to restart it manually with 'evennia.py start portal' to restart it manually with 'evennia.py start portal'
""" """
kill(PORTAL_PIDFILE, SIG, "Portal reloaded (or stopped, if it was in daemon mode).", errmsg % 'Portal', PORTAL_RESTART) kill(PORTAL_PIDFILE, SIG, "Portal reloaded (or stopped, if it was in daemon mode).", errmsg % 'Portal', PORTAL_RESTART)
@ -408,7 +408,7 @@ def error_check_python_modules():
with exceptions in the engine (since they are formatting errors in with exceptions in the engine (since they are formatting errors in
the python source files themselves). Best they fail already here the python source files themselves). Best they fail already here
before we get any further. before we get any further.
""" """
def imp(path, split=True): def imp(path, split=True):
mod, fromlist = path, "None" mod, fromlist = path, "None"
if split: if split:
@ -473,12 +473,12 @@ def main():
# handle command-line arguments # handle command-line arguments
cmdstr = handle_args(options, mode, service) cmdstr = handle_args(options, mode, service)
if cmdstr: if cmdstr:
# call the runner. # call the runner.
cmdstr.append('start') cmdstr.append('start')
Popen(cmdstr) Popen(cmdstr)
if __name__ == '__main__': if __name__ == '__main__':
# start Evennia # start Evennia
from src.utils.utils import check_evennia_dependencies from src.utils.utils import check_evennia_dependencies
if check_evennia_dependencies(): if check_evennia_dependencies():
main() main()

View file

@ -1,10 +1,10 @@
""" """
Example command set template module. Example command set template module.
To create new commands to populate the cmdset, see To create new commands to populate the cmdset, see
examples/command.py. examples/command.py.
To extend the default command set: To extend the default command set:
- copy this file up one level to gamesrc/commands and name it - copy this file up one level to gamesrc/commands and name it
something fitting. something fitting.
- change settings.CMDSET_DEFAULT to point to the new module's - change settings.CMDSET_DEFAULT to point to the new module's
@ -36,12 +36,12 @@ class ExampleCmdSet(CmdSet):
""" """
Implements an empty, example cmdset. Implements an empty, example cmdset.
""" """
key = "ExampleSet" key = "ExampleSet"
def at_cmdset_creation(self): def at_cmdset_creation(self):
""" """
This is the only method defined in a cmdset, called during This is the only method defined in a cmdset, called during
its creation. It should populate the set with command instances. its creation. It should populate the set with command instances.
Here we just add the empty base Command object. It prints some info. Here we just add the empty base Command object. It prints some info.
@ -51,7 +51,7 @@ class ExampleCmdSet(CmdSet):
class DefaultCmdSet(default_cmds.DefaultCmdSet): class DefaultCmdSet(default_cmds.DefaultCmdSet):
""" """
This is an example of how to overload the default command This is an example of how to overload the default command
set defined in src/commands/default/cmdset_default.py. set defined in src/commands/default/cmdset_default.py.
Here we copy everything by calling the parent, but you can Here we copy everything by calling the parent, but you can
@ -60,7 +60,7 @@ class DefaultCmdSet(default_cmds.DefaultCmdSet):
to this class. to this class.
""" """
key = "DefaultMUX" key = "DefaultMUX"
def at_cmdset_creation(self): def at_cmdset_creation(self):
""" """
Populates the cmdset Populates the cmdset
@ -72,8 +72,8 @@ class DefaultCmdSet(default_cmds.DefaultCmdSet):
# any commands you add below will overload the default ones. # any commands you add below will overload the default ones.
# #
#self.add(menusystem.CmdMenuTest()) #self.add(menusystem.CmdMenuTest())
#self.add(lineeditor.CmdEditor()) #self.add(lineeditor.CmdEditor())
#self.add(misc_commands.CmdQuell()) #self.add(misc_commands.CmdQuell())
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet): class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
""" """
@ -87,25 +87,25 @@ class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
point to this class. point to this class.
""" """
key = "Unloggedin" key = "Unloggedin"
def at_cmdset_creation(self): def at_cmdset_creation(self):
""" """
Populates the cmdset Populates the cmdset
""" """
# calling setup in src.commands.default.cmdset_unloggedin # calling setup in src.commands.default.cmdset_unloggedin
super(UnloggedinCmdSet, self).at_cmdset_creation() super(UnloggedinCmdSet, self).at_cmdset_creation()
# #
# any commands you add below will overload the default ones. # any commands you add below will overload the default ones.
# #
class OOCCmdSet(default_cmds.OOCCmdSet): class OOCCmdSet(default_cmds.OOCCmdSet):
""" """
This is set is available to the player when they have no This is set is available to the player when they have no
character connected to them (i.e. they are out-of-character, ooc). character connected to them (i.e. they are out-of-character, ooc).
""" """
key = "OOC" key = "OOC"
def at_cmdset_creation(self): def at_cmdset_creation(self):
""" """
Populates the cmdset Populates the cmdset
@ -114,9 +114,4 @@ class OOCCmdSet(default_cmds.OOCCmdSet):
super(OOCCmdSet, self).at_cmdset_creation() super(OOCCmdSet, self).at_cmdset_creation()
# #
# any commands you add below will overload the default ones. # any commands you add below will overload the default ones.
# #

View file

@ -1,13 +1,13 @@
""" """
This defines the cmdset for the red_button. Here we have defined This defines the cmdset for the red_button. Here we have defined
the commands and the cmdset in the same module, but if you the commands and the cmdset in the same module, but if you
have many different commands to merge it is often better have many different commands to merge it is often better
to define the cmdset separately, picking and choosing from to define the cmdset separately, picking and choosing from
among the available commands as to what should be included in the among the available commands as to what should be included in the
cmdset - this way you can often re-use the commands too. cmdset - this way you can often re-use the commands too.
""" """
import random import random
from ev import Command, CmdSet from ev import Command, CmdSet
# Some simple commands for the red button # Some simple commands for the red button
@ -19,14 +19,14 @@ from ev import Command, CmdSet
class CmdNudge(Command): class CmdNudge(Command):
""" """
Try to nudge the button's lid Try to nudge the button's lid
Usage: Usage:
nudge lid nudge lid
This command will have you try to This command will have you try to
push the lid of the button away. push the lid of the button away.
""" """
key = "nudge lid" # two-word command name! key = "nudge lid" # two-word command name!
aliases = ["nudge"] aliases = ["nudge"]
locks = "cmd:all()" locks = "cmd:all()"
@ -43,10 +43,10 @@ class CmdNudge(Command):
else: else:
self.caller.msg("You manage to get a nail under the lid.") self.caller.msg("You manage to get a nail under the lid.")
self.caller.execute_cmd("open lid") self.caller.execute_cmd("open lid")
class CmdPush(Command): class CmdPush(Command):
""" """
Push the red button Push the red button
Usage: Usage:
push button push button
@ -55,7 +55,7 @@ class CmdPush(Command):
key = "push button" key = "push button"
aliases = ["push", "press button", "press"] aliases = ["push", "press button", "press"]
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
""" """
Note that we choose to implement this with checking for Note that we choose to implement this with checking for
@ -64,7 +64,7 @@ class CmdPush(Command):
An alternative would be to make two versions of this command An alternative would be to make two versions of this command
and tuck them into the cmdset linked to the Open and Closed and tuck them into the cmdset linked to the Open and Closed
lid-state respectively. lid-state respectively.
""" """
@ -72,15 +72,15 @@ class CmdPush(Command):
string = "You reach out to press the big red button ..." string = "You reach out to press the big red button ..."
string += "\n\nA BOOM! A bright light blinds you!" string += "\n\nA BOOM! A bright light blinds you!"
string += "\nThe world goes dark ..." string += "\nThe world goes dark ..."
self.caller.msg(string) self.caller.msg(string)
self.caller.location.msg_contents("%s presses the button. BOOM! %s is blinded by a flash!" % self.caller.location.msg_contents("%s presses the button. BOOM! %s is blinded by a flash!" %
(self.caller.name, self.caller.name), exclude=self.caller) (self.caller.name, self.caller.name), exclude=self.caller)
# the button's method will handle all setup of scripts etc. # the button's method will handle all setup of scripts etc.
self.obj.press_button(self.caller) self.obj.press_button(self.caller)
else: else:
string = "You cannot push the button - there is a glass lid covering it." string = "You cannot push the button - there is a glass lid covering it."
self.caller.msg(string) self.caller.msg(string)
class CmdSmashGlass(Command): class CmdSmashGlass(Command):
@ -92,15 +92,15 @@ class CmdSmashGlass(Command):
Try to smash the glass of the button. Try to smash the glass of the button.
""" """
key = "smash glass" key = "smash glass"
aliases = ["smash lid", "break lid", "smash"] aliases = ["smash lid", "break lid", "smash"]
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
""" """
The lid won't open, but there is a small chance The lid won't open, but there is a small chance
of causing the lamp to break. of causing the lamp to break.
""" """
rand = random.random() rand = random.random()
@ -109,11 +109,11 @@ class CmdSmashGlass(Command):
string += " with all your might. The lid won't budge" string += " with all your might. The lid won't budge"
string += " but you cause quite the tremor through the button's mount." string += " but you cause quite the tremor through the button's mount."
string += "\nIt looks like the button's lamp stopped working for the time being." string += "\nIt looks like the button's lamp stopped working for the time being."
self.obj.lamp_works = False self.obj.lamp_works = False
elif rand < 0.6: elif rand < 0.6:
string = "You hit the lid hard. It doesn't move an inch." string = "You hit the lid hard. It doesn't move an inch."
else: else:
string = "You place a well-aimed fist against the glass of the lid." string = "You place a well-aimed fist against the glass of the lid."
string += " Unfortunately all you get is a pain in your hand. Maybe" string += " Unfortunately all you get is a pain in your hand. Maybe"
string += " you should just try to open the lid instead?" string += " you should just try to open the lid instead?"
self.caller.msg(string) self.caller.msg(string)
@ -125,7 +125,7 @@ class CmdOpenLid(Command):
open lid open lid
Usage: Usage:
open lid open lid
""" """
@ -135,21 +135,21 @@ class CmdOpenLid(Command):
def func(self): def func(self):
"simply call the right function." "simply call the right function."
if self.obj.db.lid_locked: if self.obj.db.lid_locked:
self.caller.msg("This lid seems locked in place for the moment.") self.caller.msg("This lid seems locked in place for the moment.")
return return
string = "\nA ticking sound is heard, like a winding mechanism. Seems " string = "\nA ticking sound is heard, like a winding mechanism. Seems "
string += "the lid will soon close again." string += "the lid will soon close again."
self.caller.msg(string) self.caller.msg(string)
self.caller.location.msg_contents("%s opens the lid of the button." % self.caller.location.msg_contents("%s opens the lid of the button." %
(self.caller.name), exclude=self.caller) (self.caller.name), exclude=self.caller)
# add the relevant cmdsets to button # add the relevant cmdsets to button
self.obj.cmdset.add(LidClosedCmdSet) self.obj.cmdset.add(LidClosedCmdSet)
# call object method # call object method
self.obj.open_lid() self.obj.open_lid()
class CmdCloseLid(Command): class CmdCloseLid(Command):
""" """
close the lid close the lid
@ -163,7 +163,7 @@ class CmdCloseLid(Command):
key = "close lid" key = "close lid"
aliases = ["close"] aliases = ["close"]
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"Close the lid" "Close the lid"
@ -171,9 +171,9 @@ class CmdCloseLid(Command):
# this will clean out scripts dependent on lid being open. # this will clean out scripts dependent on lid being open.
self.caller.msg("You close the button's lid. It clicks back into place.") self.caller.msg("You close the button's lid. It clicks back into place.")
self.caller.location.msg_contents("%s closes the button's lid." % self.caller.location.msg_contents("%s closes the button's lid." %
(self.caller.name), exclude=self.caller) (self.caller.name), exclude=self.caller)
class CmdBlindLook(Command): class CmdBlindLook(Command):
""" """
Looking around in darkness Looking around in darkness
@ -185,14 +185,14 @@ class CmdBlindLook(Command):
""" """
key = "look" key = "look"
aliases = ["l", "get", "examine", "ex", "feel", "listen"] aliases = ["l", "get", "examine", "ex", "feel", "listen"]
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"This replaces all the senses when blinded." "This replaces all the senses when blinded."
# we decide what to reply based on which command was # we decide what to reply based on which command was
# actually tried # actually tried
if self.cmdstring == "get": if self.cmdstring == "get":
@ -208,7 +208,7 @@ class CmdBlindLook(Command):
string = "You are temporarily blinded by the flash. " string = "You are temporarily blinded by the flash. "
string += "Until it wears off, all you can do is feel around blindly." string += "Until it wears off, all you can do is feel around blindly."
self.caller.msg(string) self.caller.msg(string)
self.caller.location.msg_contents("%s stumbles around, blinded." % self.caller.location.msg_contents("%s stumbles around, blinded." %
(self.caller.name), exclude=self.caller) (self.caller.name), exclude=self.caller)
class CmdBlindHelp(Command): class CmdBlindHelp(Command):
@ -220,7 +220,7 @@ class CmdBlindHelp(Command):
""" """
key = "help" key = "help"
aliases = "h" aliases = "h"
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
@ -242,13 +242,13 @@ class DefaultCmdSet(CmdSet):
The default cmdset always sits The default cmdset always sits
on the button object and whereas other on the button object and whereas other
command sets may be added/merge onto it command sets may be added/merge onto it
and hide it, removing them will always and hide it, removing them will always
bring it back. It's added to the object bring it back. It's added to the object
using obj.cmdset.add_default(). using obj.cmdset.add_default().
""" """
key = "RedButtonDefault" key = "RedButtonDefault"
mergetype = "Union" # this is default, we don't really need to put it here. mergetype = "Union" # this is default, we don't really need to put it here.
def at_cmdset_creation(self): def at_cmdset_creation(self):
"Init the cmdset" "Init the cmdset"
self.add(CmdPush()) self.add(CmdPush())
@ -260,13 +260,13 @@ class LidClosedCmdSet(CmdSet):
It contains the commands that launches the other It contains the commands that launches the other
command sets, making the red button a self-contained command sets, making the red button a self-contained
item (i.e. you don't have to manually add any item (i.e. you don't have to manually add any
scripts etc to it when creating it). scripts etc to it when creating it).
""" """
key = "LidClosedCmdSet" key = "LidClosedCmdSet"
# default Union is used *except* if we are adding to a # default Union is used *except* if we are adding to a
# cmdset named LidOpenCmdSet - this one we replace # cmdset named LidOpenCmdSet - this one we replace
# completely. # completely.
key_mergetype = {"LidOpenCmdSet": "Replace"} key_mergetype = {"LidOpenCmdSet": "Replace"}
def at_cmdset_creation(self): def at_cmdset_creation(self):
"Populates the cmdset when it is instantiated." "Populates the cmdset when it is instantiated."
@ -276,14 +276,14 @@ class LidClosedCmdSet(CmdSet):
class LidOpenCmdSet(CmdSet): class LidOpenCmdSet(CmdSet):
""" """
This is the opposite of the Closed cmdset. This is the opposite of the Closed cmdset.
""" """
key = "LidOpenCmdSet" key = "LidOpenCmdSet"
# default Union is used *except* if we are adding to a # default Union is used *except* if we are adding to a
# cmdset named LidClosedCmdSet - this one we replace # cmdset named LidClosedCmdSet - this one we replace
# completely. # completely.
key_mergetype = {"LidClosedCmdSet": "Replace"} key_mergetype = {"LidClosedCmdSet": "Replace"}
def at_cmdset_creation(self): def at_cmdset_creation(self):
"setup the cmdset (just one command)" "setup the cmdset (just one command)"
self.add(CmdCloseLid()) self.add(CmdCloseLid())
@ -295,13 +295,13 @@ class BlindCmdSet(CmdSet):
""" """
key = "BlindCmdSet" key = "BlindCmdSet"
# we want it to completely replace all normal commands # we want it to completely replace all normal commands
# until the timed script removes it again. # until the timed script removes it again.
mergetype = "Replace" mergetype = "Replace"
# we want to stop the player from walking around # we want to stop the player from walking around
# in this blinded state, so we hide all exits too. # in this blinded state, so we hide all exits too.
# (channel commands will still work). # (channel commands will still work).
no_exits = True # keep player in the same room no_exits = True # keep player in the same room
no_objs = True # don't allow object commands no_objs = True # don't allow object commands
def at_cmdset_creation(self): def at_cmdset_creation(self):
"Setup the blind cmdset" "Setup the blind cmdset"

View file

@ -1,5 +1,5 @@
""" """
Example command module template Example command module template
Copy this module up one level to gamesrc/commands/ and name it as Copy this module up one level to gamesrc/commands/ and name it as
befits your use. You can then use it as a template to define your new befits your use. You can then use it as a template to define your new
@ -15,30 +15,30 @@ from ev import utils
class Command(BaseCommand): class Command(BaseCommand):
""" """
Inherit from this if you want to create your own Inherit from this if you want to create your own
command styles. Note that Evennia's default commands command styles. Note that Evennia's default commands
use MuxCommand instead (next in this module) use MuxCommand instead (next in this module)
Note that the class's __doc__ string (this text) is Note that the class's __doc__ string (this text) is
used by Evennia to create the automatic help entry for used by Evennia to create the automatic help entry for
the command, so make sure to document consistently here. the command, so make sure to document consistently here.
""" """
# these need to be specified # these need to be specified
key = "MyCommand" key = "MyCommand"
aliases = ["mycmd", "myc"] aliases = ["mycmd", "myc"]
locks = "cmd:all()" locks = "cmd:all()"
help_category = "General" help_category = "General"
# auto_help = False # uncomment to deactive auto-help for this command. # auto_help = False # uncomment to deactive auto-help for this command.
# arg_regex = r"\s.*?|$" # optional regex detailing how the part after # arg_regex = r"\s.*?|$" # optional regex detailing how the part after
# the cmdname must look to match this command. # the cmdname must look to match this command.
# (we don't implement hook method access() here, you don't need to # (we don't implement hook method access() here, you don't need to
# modify that unless you want to change how the lock system works # modify that unless you want to change how the lock system works
# (in that case see src.commands.command.Command)) # (in that case see src.commands.command.Command))
def at_pre_cmd(self): def at_pre_cmd(self):
""" """
This hook is called before self.parse() on all commands This hook is called before self.parse() on all commands
@ -51,15 +51,15 @@ class Command(BaseCommand):
has been identified. It creates a new set of member variables has been identified. It creates a new set of member variables
that can be later accessed from self.func() (see below) that can be later accessed from self.func() (see below)
The following variables are available to us: The following variables are available to us:
# class variables: # class variables:
self.key - the name of this command ('mycommand') self.key - the name of this command ('mycommand')
self.aliases - the aliases of this cmd ('mycmd','myc') self.aliases - the aliases of this cmd ('mycmd','myc')
self.locks - lock string for this command ("cmd:all()") self.locks - lock string for this command ("cmd:all()")
self.help_category - overall category of command ("General") self.help_category - overall category of command ("General")
# added at run-time by cmdhandler: # added at run-time by cmdhandler:
self.caller - the object calling this command self.caller - the object calling this command
self.cmdstring - the actual command name used to call this self.cmdstring - the actual command name used to call this
@ -67,10 +67,10 @@ class Command(BaseCommand):
for example) for example)
self.args - the raw input; everything following self.cmdstring. self.args - the raw input; everything following self.cmdstring.
self.cmdset - the cmdset from which this command was picked. Not self.cmdset - the cmdset from which this command was picked. Not
often used (useful for commands like 'help' or to often used (useful for commands like 'help' or to
list all available commands etc) list all available commands etc)
self.obj - the object on which this command was defined. It is often self.obj - the object on which this command was defined. It is often
the same as self.caller. the same as self.caller.
""" """
pass pass
@ -78,13 +78,13 @@ class Command(BaseCommand):
""" """
This is the hook function that actually does all the work. It is called This is the hook function that actually does all the work. It is called
by the cmdhandler right after self.parser() finishes, and so has access by the cmdhandler right after self.parser() finishes, and so has access
to all the variables defined therein. to all the variables defined therein.
""" """
self.caller.msg("Command called!") self.caller.msg("Command called!")
def at_post_cmd(self): def at_post_cmd(self):
""" """
This hook is called after self.func(). This hook is called after self.func().
""" """
pass pass
@ -101,32 +101,32 @@ class MuxCommand(default_cmd.MuxCommand):
name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]] name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]]
The 'name[ with several words]' part is already dealt with by the The 'name[ with several words]' part is already dealt with by the
cmdhandler at this point, and stored in self.cmdname. The rest is stored cmdhandler at this point, and stored in self.cmdname. The rest is stored
in self.args. in self.args.
The MuxCommand parser breaks self.args into its constituents and stores them in the The MuxCommand parser breaks self.args into its constituents and stores them in the
following variables: following variables:
self.switches = optional list of /switches (without the /) self.switches = optional list of /switches (without the /)
self.raw = This is the raw argument input, including switches self.raw = This is the raw argument input, including switches
self.args = This is re-defined to be everything *except* the switches self.args = This is re-defined to be everything *except* the switches
self.lhs = Everything to the left of = (lhs:'left-hand side'). If self.lhs = Everything to the left of = (lhs:'left-hand side'). If
no = is found, this is identical to self.args. no = is found, this is identical to self.args.
self.rhs: Everything to the right of = (rhs:'right-hand side'). self.rhs: Everything to the right of = (rhs:'right-hand side').
If no '=' is found, this is None. If no '=' is found, this is None.
self.lhslist - self.lhs split into a list by comma self.lhslist - self.lhs split into a list by comma
self.rhslist - list of self.rhs split into a list by comma self.rhslist - list of self.rhs split into a list by comma
self.arglist = list of space-separated args (including '=' if it exists) self.arglist = list of space-separated args (including '=' if it exists)
All args and list members are stripped of excess whitespace around the All args and list members are stripped of excess whitespace around the
strings, but case is preserved. strings, but case is preserved.
""" """
def func(self): def func(self):
""" """
This is the hook function that actually does all the work. It is called This is the hook function that actually does all the work. It is called
by the cmdhandler right after self.parser() finishes, and so has access by the cmdhandler right after self.parser() finishes, and so has access
to all the variables defined therein. to all the variables defined therein.
""" """
# this can be removed in your child class, it's just # this can be removed in your child class, it's just
# printing the ingoing variables as a demo. # printing the ingoing variables as a demo.
super(MuxCommand, self).func() super(MuxCommand, self).func()

View file

@ -2,7 +2,7 @@
At_initial_setup module template At_initial_setup module template
Copy this module up one level to /gamesrc/conf, name it what you like Copy this module up one level to /gamesrc/conf, name it what you like
and then use it as a template to modify. and then use it as a template to modify.
Then edit settings.AT_INITIAL_SETUP_HOOK_MODULE to point to your new Then edit settings.AT_INITIAL_SETUP_HOOK_MODULE to point to your new
module. module.
@ -21,4 +21,4 @@ does what you expect it to.
""" """
def at_initial_setup(): def at_initial_setup():
pass pass

View file

@ -3,7 +3,7 @@
At_server_startstop module template At_server_startstop module template
Copy this module one level up, to gamesrc/conf/, name it what you Copy this module one level up, to gamesrc/conf/, name it what you
will and use it as a template for your modifications. will and use it as a template for your modifications.
Then edit settings.AT_SERVER_STARTSTOP_MODULE to point to your new Then edit settings.AT_SERVER_STARTSTOP_MODULE to point to your new
module. module.
@ -15,10 +15,10 @@ already been executed. The main purpose of this is module is to have a
safe place to initialize eventual custom modules that your game needs safe place to initialize eventual custom modules that your game needs
to start up or load. to start up or load.
The module should define at least these global functions: The module should define at least these global functions:
at_server_start() at_server_start()
at_server_stop() at_server_stop()
""" """

View file

@ -1,8 +1,8 @@
""" """
Connect screen module template Connect screen module template
Copy this module one level up, to gamesrc/conf/, name it what Copy this module one level up, to gamesrc/conf/, name it what
you want and modify it to your liking. you want and modify it to your liking.
Then you set settings.CONNECTION_SCREEN_MODULE to point to your Then you set settings.CONNECTION_SCREEN_MODULE to point to your
new module. new module.
@ -10,7 +10,7 @@ new module.
This module holds textual connection screen definitions. All global This module holds textual connection screen definitions. All global
string variables (only) in this module are read by Evennia and string variables (only) in this module are read by Evennia and
assumed to define a Connection screen. assumed to define a Connection screen.
The names of the string variables doesn't matter (except they The names of the string variables doesn't matter (except they
shouldn't start with _), but each should hold a string defining a shouldn't start with _), but each should hold a string defining a
@ -25,13 +25,13 @@ new module.
""" """
from src.utils import utils from src.utils import utils
from src.commands.connection_screen import DEFAULT_SCREEN from src.commands.connection_screen import DEFAULT_SCREEN
# #
# CUSTOM_SCREEN = \ # CUSTOM_SCREEN = \
# """{b=============================================================={n # """{b=============================================================={n
# Welcome to {gEvennia{n, version %s! # Welcome to {gEvennia{n, version %s!
# #
# If you have an existing account, connect to it by typing: # If you have an existing account, connect to it by typing:
# {wconnect <email> <password>{n # {wconnect <email> <password>{n
@ -45,5 +45,5 @@ from src.commands.connection_screen import DEFAULT_SCREEN
# MENU_SCREEN = \ # MENU_SCREEN = \
# """{b=============================================================={n # """{b=============================================================={n
# Welcome to {gEvennnia{n, version %s! # Welcome to {gEvennnia{n, version %s!
# {b=============================================================={n""" % utils.get_evennia_version() # {b=============================================================={n""" % utils.get_evennia_version()

View file

@ -3,9 +3,9 @@
Lockfuncs module template Lockfuncs module template
Copy this module one level up, to gamesrc/conf/, name it what Copy this module one level up, to gamesrc/conf/, name it what
you will and edit it to your liking. you will and edit it to your liking.
Then add the new module's path to the end of the tuple Then add the new module's path to the end of the tuple
defined in settings.LOCK_FUNC_MODULES. defined in settings.LOCK_FUNC_MODULES.
All functions defined globally in this module are assumed to be All functions defined globally in this module are assumed to be
@ -18,15 +18,15 @@ arguments should be handled (excess ones calling magic (*args,
**kwargs) to avoid errors). The lock function should handle all **kwargs) to avoid errors). The lock function should handle all
eventual tracebacks by logging the error and returning False. eventual tracebacks by logging the error and returning False.
See many more examples of lock functions in src.locks.lockfuncs. See many more examples of lock functions in src.locks.lockfuncs.
""" """
def myfalse(accessing_obj, accessed_obj, *args, **kwargs): def myfalse(accessing_obj, accessed_obj, *args, **kwargs):
""" """
called in lockstring with myfalse(). called in lockstring with myfalse().
A simple logger that always returns false. Prints to stdout A simple logger that always returns false. Prints to stdout
for simplicity, should use utils.logger for real operation. for simplicity, should use utils.logger for real operation.
""" """
print "%s tried to access %s. Access denied." % (accessing_obj, accessed_obj) print "%s tried to access %s. Access denied." % (accessing_obj, accessed_obj)
return False return False

View file

@ -1,22 +1,22 @@
""" """
MSSP module template MSSP module template
Copy this module one level up, to gamesrc/conf/, name it Copy this module one level up, to gamesrc/conf/, name it
what you want and edit it to your satisfaction. what you want and edit it to your satisfaction.
Then change settings.MSSP_META_MODULE to point to your new module. Then change settings.MSSP_META_MODULE to point to your new module.
MSSP (Mud Server Status Protocol) meta information MSSP (Mud Server Status Protocol) meta information
MUD website listings (that you have registered with) can use this MUD website listings (that you have registered with) can use this
information to keep up-to-date with your game stats as you change information to keep up-to-date with your game stats as you change
them. Also number of currently active players and uptime will them. Also number of currently active players and uptime will
automatically be reported. You don't have to fill in everything automatically be reported. You don't have to fill in everything
(and most are not used by all crawlers); leave the default (and most are not used by all crawlers); leave the default
if so needed. You need to @reload the game before updated if so needed. You need to @reload the game before updated
information is made available to crawlers (reloading does not information is made available to crawlers (reloading does not
affect uptime). affect uptime).
""" """
MSSPTable = { MSSPTable = {
@ -29,16 +29,16 @@ MSSPTable = {
"CRAWL DELAY": "-1", # limit how often crawler updates the listing. -1 for no limit "CRAWL DELAY": "-1", # limit how often crawler updates the listing. -1 for no limit
"HOSTNAME": "", # current or new hostname "HOSTNAME": "", # current or new hostname
"PORT": ["4000"], # most important port should be last in list "PORT": ["4000"], # most important port should be last in list
"CODEBASE": "Evennia", "CODEBASE": "Evennia",
"CONTACT": "", # email for contacting the mud "CONTACT": "", # email for contacting the mud
"CREATED": "", # year MUD was created "CREATED": "", # year MUD was created
"ICON": "", # url to icon 32x32 or larger; <32kb. "ICON": "", # url to icon 32x32 or larger; <32kb.
"IP": "", # current or new IP address "IP": "", # current or new IP address
"LANGUAGE": "", # name of language used, e.g. English "LANGUAGE": "", # name of language used, e.g. English
"LOCATION": "", # full English name of server country "LOCATION": "", # full English name of server country
"MINIMUM AGE": "0", # set to 0 if not applicable "MINIMUM AGE": "0", # set to 0 if not applicable
"WEBSITE": "www.evennia.com", "WEBSITE": "www.evennia.com",
# Categorisation # Categorisation
@ -50,14 +50,14 @@ MSSPTable = {
# Roleplaying, Simulation, Social or Strategy # Roleplaying, Simulation, Social or Strategy
"STATUS": "Open Beta", # Alpha, Closed Beta, Open Beta, Live "STATUS": "Open Beta", # Alpha, Closed Beta, Open Beta, Live
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew "GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
"INTERMUD": "IMC2", # evennia supports IMC2. "INTERMUD": "IMC2", # evennia supports IMC2.
"SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein, "SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein,
# Cyberpunk, Dragonlance, etc. Or None if not available. # Cyberpunk, Dragonlance, etc. Or None if not available.
# World # World
"AREAS": "0", "AREAS": "0",
"HELPFILES": "0", "HELPFILES": "0",
"MOBILES": "0", "MOBILES": "0",
"OBJECTS": "0", "OBJECTS": "0",
"ROOMS": "0", # use 0 if room-less "ROOMS": "0", # use 0 if room-less
@ -121,7 +121,7 @@ MSSPTable = {
# Protocols (only change if you added/removed something manually) # Protocols (only change if you added/removed something manually)
"ATCP": "0", "ATCP": "0",
"MSDP": "0", "MSDP": "0",
"MCCP": "1", "MCCP": "1",
"SSL": "1", "SSL": "1",
"UTF-8": "1", "UTF-8": "1",

View file

@ -3,14 +3,14 @@
** OBS This module is not yet used by Evennia ** ** OBS This module is not yet used by Evennia **
Example module holding functions for out-of-band protocols to Example module holding functions for out-of-band protocols to
import and map to given commands from the client. This module import and map to given commands from the client. This module
is selected by settings.OOB_FUNC_MODULE. is selected by settings.OOB_FUNC_MODULE.
All functions defined global in this module will be available All functions defined global in this module will be available
for the oob system to call. They will be called with a session/character for the oob system to call. They will be called with a session/character
as first argument (depending on if the session is logged in or not), as first argument (depending on if the session is logged in or not),
following by any number of extra arguments. The return value will following by any number of extra arguments. The return value will
be packed and returned to the oob protocol and can be on any form. be packed and returned to the oob protocol and can be on any form.
""" """
def testoob(character, *args, **kwargs): def testoob(character, *args, **kwargs):

View file

@ -3,16 +3,16 @@
Template for Characters Template for Characters
Copy this module up one level and name it as you like, then Copy this module up one level and name it as you like, then
use it as a template to create your own Character class. use it as a template to create your own Character class.
To make new logins default to creating characters To make new logins default to creating characters
of your new type, change settings.BASE_CHARACTER_TYPECLASS to point to of your new type, change settings.BASE_CHARACTER_TYPECLASS to point to
your new class, e.g. your new class, e.g.
settings.BASE_CHARACTER_TYPECLASS = "game.gamesrc.objects.mychar.MyChar" settings.BASE_CHARACTER_TYPECLASS = "game.gamesrc.objects.mychar.MyChar"
Note that objects already created in the database will not notice Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the this change, you have to convert them manually e.g. with the
@typeclass command. @typeclass command.
""" """
@ -20,22 +20,21 @@ from ev import Character
class ExampleCharacter(Character): class ExampleCharacter(Character):
""" """
The Character is like any normal Object (see example/object.py for The Character is like any normal Object (see example/object.py for
a list of properties and methods), except it actually implements a list of properties and methods), except it actually implements
some of its hook methods to do some work: some of its hook methods to do some work:
at_basetype_setup - always assigns the default_cmdset to this object type at_basetype_setup - always assigns the default_cmdset to this object type
(important!)sets locks so character cannot be picked up (important!)sets locks so character cannot be picked up
and its commands only be called by itself, not anyone else. and its commands only be called by itself, not anyone else.
(to change things, use at_object_creation() instead) (to change things, use at_object_creation() instead)
at_after_move - launches the "look" command at_after_move - launches the "look" command
at_disconnect - stores the current location, so the "unconnected" character at_disconnect - stores the current location, so the "unconnected" character
object does not need to stay on grid but can be given a object does not need to stay on grid but can be given a
None-location while offline. None-location while offline.
at_post_login - retrieves the character's old location and puts it back at_post_login - retrieves the character's old location and puts it back
on the grid with a "charname has connected" message echoed on the grid with a "charname has connected" message echoed
to the room to the room
""" """
pass pass

View file

@ -12,7 +12,7 @@ your new class, e.g.
settings.BASE_EXIT_TYPECLASS = "game.gamesrc.objects.myexit.MyExit" settings.BASE_EXIT_TYPECLASS = "game.gamesrc.objects.myexit.MyExit"
Note that objects already created in the database will not notice Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the this change, you have to convert them manually e.g. with the
@typeclass command. @typeclass command.
""" """
@ -21,8 +21,8 @@ from ev import Exit
class ExampleExit(Exit): class ExampleExit(Exit):
""" """
Exits are connectors between rooms. Exits are normal Objects except Exits are connectors between rooms. Exits are normal Objects except
they defines the 'destination' property. It also does work in the they defines the 'destination' property. It also does work in the
following methods: following methods:
basetype_setup() - sets default exit locks (to change, use at_object_creation instead) basetype_setup() - sets default exit locks (to change, use at_object_creation instead)
at_cmdset_get() - this auto-creates and caches a command and a command set on itself at_cmdset_get() - this auto-creates and caches a command and a command set on itself
@ -33,11 +33,11 @@ class ExampleExit(Exit):
go there") if exit traversal fails and an go there") if exit traversal fails and an
attribute err_traverse is not defined. attribute err_traverse is not defined.
Relevant hooks to overload (compared to other types of Objects): Relevant hooks to overload (compared to other types of Objects):
at_before_traverse(traveller) - called just before traversing at_before_traverse(traveller) - called just before traversing
at_after_traverse(traveller, source_loc) - called just after traversing at_after_traverse(traveller, source_loc) - called just after traversing
at_failed_traverse(traveller) - called if traversal failed for some reason. Will at_failed_traverse(traveller) - called if traversal failed for some reason. Will
not be called if the attribute 'err_traverse' is not be called if the attribute 'err_traverse' is
defined, in which case that will simply be echoed. defined, in which case that will simply be echoed.
""" """
pass pass

View file

@ -3,7 +3,7 @@
Template for Objects Template for Objects
Copy this module up one level and name it as you like, then Copy this module up one level and name it as you like, then
use it as a template to create your own Objects. use it as a template to create your own Objects.
To make the default commands default to creating objects of your new To make the default commands default to creating objects of your new
type (and also change the "fallback" object used when typeclass type (and also change the "fallback" object used when typeclass
@ -13,7 +13,7 @@ your new class, e.g.
settings.BASE_OBJECT_TYPECLASS = "game.gamesrc.objects.myobj.MyObj" settings.BASE_OBJECT_TYPECLASS = "game.gamesrc.objects.myobj.MyObj"
Note that objects already created in the database will not notice Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the this change, you have to convert them manually e.g. with the
@typeclass command. @typeclass command.
""" """
@ -34,19 +34,19 @@ class ExampleObject(Object):
methods, such as __init__ and especially never __getattribute__ and methods, such as __init__ and especially never __getattribute__ and
__setattr__ since these are used heavily by the typeclass system __setattr__ since these are used heavily by the typeclass system
of Evennia and messing with them might well break things for you. of Evennia and messing with them might well break things for you.
* Base properties defined/available on all Objects
key (string) - name of object * Base properties defined/available on all Objects
key (string) - name of object
name (string)- same as key name (string)- same as key
aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings.
dbref (int, read-only) - unique #id-number. Also "id" can be used. dbref (int, read-only) - unique #id-number. Also "id" can be used.
dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class
typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch. typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch.
date_created (string) - time stamp of object creation date_created (string) - time stamp of object creation
permissions (list of strings) - list of permission strings permissions (list of strings) - list of permission strings
player (Player) - controlling player (will also return offline player) player (Player) - controlling player (will also return offline player)
location (Object) - current location. Is None if this is a room location (Object) - current location. Is None if this is a room
home (Object) - safety start-location home (Object) - safety start-location
@ -54,19 +54,19 @@ class ExampleObject(Object):
has_player (bool, read-only)- will only return *connected* players has_player (bool, read-only)- will only return *connected* players
contents (list of Objects, read-only) - returns all objects inside this object (including exits) contents (list of Objects, read-only) - returns all objects inside this object (including exits)
exits (list of Objects, read-only) - returns all exits from this object, if any exits (list of Objects, read-only) - returns all exits from this object, if any
destination (Object) - only set if this object is an exit. destination (Object) - only set if this object is an exit.
is_superuser (bool, read-only) - True/False if this user is a superuser is_superuser (bool, read-only) - True/False if this user is a superuser
* Handlers available * Handlers available
locks - lock-handler: use locks.add() to add new lock strings locks - lock-handler: use locks.add() to add new lock strings
db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr
ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data
scripts - script-handler. Add new scripts to object with scripts.add() scripts - script-handler. Add new scripts to object with scripts.add()
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
nicks - nick-handler. New nicks with nicks.add(). nicks - nick-handler. New nicks with nicks.add().
* Helper methods (see src.objects.objects.py for full headers) * Helper methods (see src.objects.objects.py for full headers)
search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False) search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False)
execute_cmd(raw_string) execute_cmd(raw_string)
@ -79,21 +79,21 @@ class ExampleObject(Object):
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True) swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
access(accessing_obj, access_type='read', default=False) access(accessing_obj, access_type='read', default=False)
check_permstring(permstring) check_permstring(permstring)
* Hooks (these are class methods, so their arguments should also start with self): * Hooks (these are class methods, so their arguments should also start with self):
basetype_setup() - only called once, used for behind-the-scenes setup. Normally not modified. basetype_setup() - only called once, used for behind-the-scenes setup. Normally not modified.
basetype_posthook_setup() - customization in basetype, after the object has been created; Normally not modified. basetype_posthook_setup() - customization in basetype, after the object has been created; Normally not modified.
at_object_creation() - only called once, when object is first created. Object customizations go here. at_object_creation() - only called once, when object is first created. Object customizations go here.
at_object_delete() - called just before deleting an object. If returning False, deletion is aborted. Note that all objects at_object_delete() - called just before deleting an object. If returning False, deletion is aborted. Note that all objects
inside a deleted object are automatically moved to their <home>, they don't need to be removed here. inside a deleted object are automatically moved to their <home>, they don't need to be removed here.
at_init() - called whenever typeclass is cached from memory, at least once every server restart/reload at_init() - called whenever typeclass is cached from memory, at least once every server restart/reload
at_cmdset_get() - this is called just before the command handler requests a cmdset from this object at_cmdset_get() - this is called just before the command handler requests a cmdset from this object
at_first_login() - (player-controlled objects only) called once, the very first time user logs in. at_first_login() - (player-controlled objects only) called once, the very first time user logs in.
at_pre_login() - (player-controlled objects only) called every time the user connects, after they have identified, before other setup at_pre_login() - (player-controlled objects only) called every time the user connects, after they have identified, before other setup
at_post_login() - (player-controlled objects only) called at the end of login, just before setting the player loose in the world. at_post_login() - (player-controlled objects only) called at the end of login, just before setting the player loose in the world.
at_disconnect() - (player-controlled objects only) called just before the user disconnects (or goes linkless) at_disconnect() - (player-controlled objects only) called just before the user disconnects (or goes linkless)
at_server_reload() - called before server is reloaded at_server_reload() - called before server is reloaded
at_server_shutdown() - called just before server is fully shut down at_server_shutdown() - called just before server is fully shut down
@ -106,15 +106,15 @@ class ExampleObject(Object):
at_object_receive(obj, source_location) - called when this object receives another object at_object_receive(obj, source_location) - called when this object receives another object
at_before_traverse(traversing_object) - (exit-objects only) called just before an object traverses this object at_before_traverse(traversing_object) - (exit-objects only) called just before an object traverses this object
at_after_traverse(traversing_object, source_location) - (exit-objects only) called just after a traversal has happened. at_after_traverse(traversing_object, source_location) - (exit-objects only) called just after a traversal has happened.
at_failed_traverse(traversing_object) - (exit-objects only) called if traversal fails and property err_traverse is not defined. at_failed_traverse(traversing_object) - (exit-objects only) called if traversal fails and property err_traverse is not defined.
at_msg_receive(self, msg, from_obj=None, data=None) - called when a message (via self.msg()) is sent to this obj. at_msg_receive(self, msg, from_obj=None, data=None) - called when a message (via self.msg()) is sent to this obj.
If returns false, aborts send. If returns false, aborts send.
at_msg_send(self, msg, to_obj=None, data=None) - called when this objects sends a message to someone via self.msg(). at_msg_send(self, msg, to_obj=None, data=None) - called when this objects sends a message to someone via self.msg().
return_appearance(looker) - describes this object. Used by "look" command by default return_appearance(looker) - describes this object. Used by "look" command by default
at_desc(looker=None) - called by 'look' whenever the appearance is requested. at_desc(looker=None) - called by 'look' whenever the appearance is requested.
at_get(getter) - called after object has been picked up. Does not stop pickup. at_get(getter) - called after object has been picked up. Does not stop pickup.
at_drop(dropper) - called when this object has been dropped. at_drop(dropper) - called when this object has been dropped.
at_say(speaker, message) - by default, called if an object inside this object speaks at_say(speaker, message) - by default, called if an object inside this object speaks

View file

@ -3,16 +3,16 @@
Template module for Players Template module for Players
Copy this module up one level and name it as you like, then Copy this module up one level and name it as you like, then
use it as a template to create your own Player class. use it as a template to create your own Player class.
To make the default account login default to using a Player To make the default account login default to using a Player
of your new type, change settings.BASE_PLAYER_TYPECLASS to point to of your new type, change settings.BASE_PLAYER_TYPECLASS to point to
your new class, e.g. your new class, e.g.
settings.BASE_PLAYER_TYPECLASS = "game.gamesrc.objects.myplayer.MyPlayer" settings.BASE_PLAYER_TYPECLASS = "game.gamesrc.objects.myplayer.MyPlayer"
Note that objects already created in the database will not notice Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the this change, you have to convert them manually e.g. with the
@typeclass command. @typeclass command.
""" """
@ -20,19 +20,19 @@ from ev import Player
class ExamplePlayer(Player): class ExamplePlayer(Player):
""" """
This class describes the actual OOC player (i.e. the user connecting This class describes the actual OOC player (i.e. the user connecting
to the MUD). It does NOT have visual appearance in the game world (that to the MUD). It does NOT have visual appearance in the game world (that
is handled by the character which is connected to this). Comm channels is handled by the character which is connected to this). Comm channels
are attended/joined using this object. are attended/joined using this object.
It can be useful e.g. for storing configuration options for your game, but It can be useful e.g. for storing configuration options for your game, but
should generally not hold any character-related info (that's best handled should generally not hold any character-related info (that's best handled
on the character level). on the character level).
Can be set using BASE_PLAYER_TYPECLASS. Can be set using BASE_PLAYER_TYPECLASS.
* available properties * available properties
key (string) - name of player key (string) - name of player
name (string)- wrapper for user.username name (string)- wrapper for user.username
@ -41,18 +41,18 @@ class ExamplePlayer(Player):
dbobj (Player, read-only) - link to database model. dbobj.typeclass points back to this class dbobj (Player, read-only) - link to database model. dbobj.typeclass points back to this class
typeclass (Player, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch. typeclass (Player, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch.
date_created (string) - time stamp of object creation date_created (string) - time stamp of object creation
permissions (list of strings) - list of permission strings permissions (list of strings) - list of permission strings
user (User, read-only) - django User authorization object user (User, read-only) - django User authorization object
obj (Object) - game object controlled by player. 'character' can also be used. obj (Object) - game object controlled by player. 'character' can also be used.
sessions (list of Sessions) - sessions connected to this player sessions (list of Sessions) - sessions connected to this player
is_superuser (bool, read-only) - if the connected user is a superuser is_superuser (bool, read-only) - if the connected user is a superuser
* Handlers * Handlers
locks - lock-handler: use locks.add() to add new lock strings locks - lock-handler: use locks.add() to add new lock strings
db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr
ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data
scripts - script-handler. Add new scripts to object with scripts.add() scripts - script-handler. Add new scripts to object with scripts.add()
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
nicks - nick-handler. New nicks with nicks.add(). nicks - nick-handler. New nicks with nicks.add().
@ -71,12 +71,12 @@ class ExamplePlayer(Player):
* Hook methods (when re-implementation, remember methods need to have self as first arg) * Hook methods (when re-implementation, remember methods need to have self as first arg)
basetype_setup() basetype_setup()
at_player_creation() at_player_creation()
- note that the following hooks are also found on Objects and are - note that the following hooks are also found on Objects and are
usually handled on the character level: usually handled on the character level:
at_init() at_init()
at_cmdset_get() at_cmdset_get()
at_first_login() at_first_login()
at_post_login() at_post_login()

View file

@ -4,13 +4,13 @@ This is a more advanced example object. It combines functions from
script.examples as well as commands.examples to make an interactive script.examples as well as commands.examples to make an interactive
button typeclass. button typeclass.
Create this button with Create this button with
@create/drop examples.red_button.RedButton @create/drop examples.red_button.RedButton
Note that if you must drop the button before you can see its messages! Note that if you must drop the button before you can see its messages!
""" """
import random import random
from ev import Object from ev import Object
from game.gamesrc.scripts.examples import red_button_scripts as scriptexamples from game.gamesrc.scripts.examples import red_button_scripts as scriptexamples
from game.gamesrc.commands.examples import cmdset_red_button as cmdsetexamples from game.gamesrc.commands.examples import cmdset_red_button as cmdsetexamples
@ -18,7 +18,7 @@ from game.gamesrc.commands.examples import cmdset_red_button as cmdsetexamples
# #
# Definition of the object itself # Definition of the object itself
# #
class RedButton(Object): class RedButton(Object):
""" """
This class describes an evil red button. It will use the script This class describes an evil red button. It will use the script
@ -36,30 +36,30 @@ class RedButton(Object):
""" """
This function is called when object is created. Use this This function is called when object is created. Use this
instead of e.g. __init__. instead of e.g. __init__.
""" """
# store desc (default, you can change this at creation time) # store desc (default, you can change this at creation time)
desc = "This is a large red button, inviting yet evil-looking. " desc = "This is a large red button, inviting yet evil-looking. "
desc += "A closed glass lid protects it." desc += "A closed glass lid protects it."
self.db.desc = desc self.db.desc = desc
# We have to define all the variables the scripts # We have to define all the variables the scripts
# are checking/using *before* adding the scripts or # are checking/using *before* adding the scripts or
# they might be deactivated before even starting! # they might be deactivated before even starting!
self.db.lid_open = False self.db.lid_open = False
self.db.lamp_works = True self.db.lamp_works = True
self.db.lid_locked = False self.db.lid_locked = False
self.cmdset.add_default(cmdsetexamples.DefaultCmdSet, permanent=True) self.cmdset.add_default(cmdsetexamples.DefaultCmdSet, permanent=True)
# since the cmdsets relevant to the button are added 'on the fly', # since the cmdsets relevant to the button are added 'on the fly',
# we need to setup custom scripts to do this for us (also, these scripts # we need to setup custom scripts to do this for us (also, these scripts
# check so they are valid (i.e. the lid is actually still closed)). # check so they are valid (i.e. the lid is actually still closed)).
# The AddClosedCmdSet script makes sure to add the Closed-cmdset. # The AddClosedCmdSet script makes sure to add the Closed-cmdset.
self.scripts.add(scriptexamples.ClosedLidState) self.scripts.add(scriptexamples.ClosedLidState)
# the script EventBlinkButton makes the button blink regularly. # the script EventBlinkButton makes the button blink regularly.
self.scripts.add(scriptexamples.BlinkButtonEvent) self.scripts.add(scriptexamples.BlinkButtonEvent)
# state-changing methods # state-changing methods
def open_lid(self): def open_lid(self):
""" """
@ -68,12 +68,12 @@ class RedButton(Object):
""" """
if self.db.lid_open: if self.db.lid_open:
return return
desc = self.db.desc_lid_open desc = self.db.desc_lid_open
if not desc: if not desc:
desc = "This is a large red button, inviting yet evil-looking. " desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is open and the button exposed." desc += "Its glass cover is open and the button exposed."
self.db.desc = desc self.db.desc = desc
self.db.lid_open = True self.db.lid_open = True
# with the lid open, we validate scripts; this will clean out # with the lid open, we validate scripts; this will clean out
@ -93,13 +93,13 @@ class RedButton(Object):
""" """
if not self.db.lid_open: if not self.db.lid_open:
return return
desc = self.db.desc_lid_closed desc = self.db.desc_lid_closed
if not desc: if not desc:
desc = "This is a large red button, inviting yet evil-looking. " desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is closed, protecting it." desc += "Its glass cover is closed, protecting it."
self.db.desc = desc self.db.desc = desc
self.db.lid_open = False self.db.lid_open = False
# clean out scripts depending on lid to be open # clean out scripts depending on lid to be open
self.scripts.validate() self.scripts.validate()
@ -109,17 +109,17 @@ class RedButton(Object):
def break_lamp(self, feedback=True): def break_lamp(self, feedback=True):
""" """
Breaks the lamp in the button, stopping it from blinking. Breaks the lamp in the button, stopping it from blinking.
""" """
self.db.lamp_works = False self.db.lamp_works = False
desc = self.db.desc_lamp_broken desc = self.db.desc_lamp_broken
if not desc: if not desc:
self.db.desc += "\nThe big red button has stopped blinking for the time being." self.db.desc += "\nThe big red button has stopped blinking for the time being."
else: else:
self.db.desc = desc self.db.desc = desc
if feedback and self.location: if feedback and self.location:
self.location.msg_contents("The lamp flickers, the button going dark.") self.location.msg_contents("The lamp flickers, the button going dark.")
self.scripts.validate() self.scripts.validate()
def press_button(self, pobject): def press_button(self, pobject):
@ -129,7 +129,7 @@ class RedButton(Object):
""" """
# deactivate the button so it won't flash/close lid etc. # deactivate the button so it won't flash/close lid etc.
self.scripts.add(scriptexamples.DeactivateButtonEvent) self.scripts.add(scriptexamples.DeactivateButtonEvent)
# blind the person pressing the button. Note that this # blind the person pressing the button. Note that this
# script is set on the *character* pressing the button! # script is set on the *character* pressing the button!
pobject.scripts.add(scriptexamples.BlindedState) pobject.scripts.add(scriptexamples.BlindedState)
@ -140,19 +140,18 @@ class RedButton(Object):
The script system will regularly call this The script system will regularly call this
function to make the button blink. Now and then function to make the button blink. Now and then
it won't blink at all though, to add some randomness it won't blink at all though, to add some randomness
to how often the message is echoed. to how often the message is echoed.
""" """
loc = self.location loc = self.location
if loc: if loc:
rand = random.random() rand = random.random()
if rand < 0.2: if rand < 0.2:
string = "The red button flashes briefly." string = "The red button flashes briefly."
elif rand < 0.4: elif rand < 0.4:
string = "The red button blinks invitingly." string = "The red button blinks invitingly."
elif rand < 0.6: elif rand < 0.6:
string = "The red button flashes. You know you wanna push it!" string = "The red button flashes. You know you wanna push it!"
else: else:
# no blink # no blink
return return
loc.msg_contents(string) loc.msg_contents(string)

View file

@ -3,7 +3,7 @@
Template module for Rooms Template module for Rooms
Copy this module up one level and name it as you like, then Copy this module up one level and name it as you like, then
use it as a template to create your own Objects. use it as a template to create your own Objects.
To make the default commands (such as @dig) default to creating rooms To make the default commands (such as @dig) default to creating rooms
of your new type, change settings.BASE_ROOM_TYPECLASS to point to of your new type, change settings.BASE_ROOM_TYPECLASS to point to
@ -12,7 +12,7 @@ your new class, e.g.
settings.BASE_ROOM_TYPECLASS = "game.gamesrc.objects.myroom.MyRoom" settings.BASE_ROOM_TYPECLASS = "game.gamesrc.objects.myroom.MyRoom"
Note that objects already created in the database will not notice Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the this change, you have to convert them manually e.g. with the
@typeclass command. @typeclass command.
""" """
@ -22,11 +22,11 @@ from ev import Room
class ExampleRoom(Room): class ExampleRoom(Room):
""" """
Rooms are like any Object, except their location is None Rooms are like any Object, except their location is None
(which is default). They also use basetype_setup() to (which is default). They also use basetype_setup() to
add locks so they cannot be puppeted or picked up. add locks so they cannot be puppeted or picked up.
(to change that, use at_object_creation instead) (to change that, use at_object_creation instead)
See examples/object.py for a list of See examples/object.py for a list of
properties and methods available on all Objects. properties and methods available on all Objects.
""" """
pass pass

View file

@ -1,9 +1,9 @@
""" """
Example script for testing. This adds a simple timer that Example script for testing. This adds a simple timer that
has your character make observations and noices at irregular has your character make observations and noices at irregular
intervals. intervals.
To test, use To test, use
@script me = examples.bodyfunctions.BodyFunctions @script me = examples.bodyfunctions.BodyFunctions
The script will only send messages to the object it The script will only send messages to the object it
@ -11,7 +11,7 @@ is stored on, so make sure to put it on yourself
or you won't see any messages! or you won't see any messages!
""" """
import random import random
from ev import Script from ev import Script
class BodyFunctions(Script): class BodyFunctions(Script):
@ -19,26 +19,26 @@ class BodyFunctions(Script):
This class defines the script itself This class defines the script itself
""" """
def at_script_creation(self): def at_script_creation(self):
self.key = "bodyfunction" self.key = "bodyfunction"
self.desc = "Adds various timed events to a character." self.desc = "Adds various timed events to a character."
self.interval = 20 # seconds self.interval = 20 # seconds
#self.repeats = 5 # repeat only a certain number of times #self.repeats = 5 # repeat only a certain number of times
self.start_delay = True # wait self.interval until first call self.start_delay = True # wait self.interval until first call
#self.persistent = True #self.persistent = True
def at_repeat(self): def at_repeat(self):
""" """
This gets called every self.interval seconds. We make This gets called every self.interval seconds. We make
a random check here so as to only return 33% of the time. a random check here so as to only return 33% of the time.
""" """
if random.random() < 0.66: if random.random() < 0.66:
# no message this time # no message this time
return return
rand = random.random() rand = random.random()
# return a random message # return a random message
if rand < 0.1: if rand < 0.1:
string = "You tap your foot, looking around." string = "You tap your foot, looking around."
elif rand < 0.2: elif rand < 0.2:
string = "You have an itch. Hard to reach too." string = "You have an itch. Hard to reach too."
@ -58,6 +58,6 @@ class BodyFunctions(Script):
string = "You get a great idea. Of course you won't tell anyone." string = "You get a great idea. Of course you won't tell anyone."
else: else:
string = "You suddenly realize how much you love Evennia!" string = "You suddenly realize how much you love Evennia!"
# echo the message to the object # echo the message to the object
self.obj.msg(string) self.obj.msg(string)

View file

@ -1,7 +1,7 @@
""" """
Example of scripts. Example of scripts.
These are scripts intended for a particular object - the These are scripts intended for a particular object - the
red_button object type in gamesrc/types/examples. A few variations red_button object type in gamesrc/types/examples. A few variations
on uses of scripts are included. on uses of scripts are included.
@ -11,45 +11,45 @@ from game.gamesrc.commands.examples import cmdset_red_button as cmdsetexamples
# #
# Scripts as state-managers # Scripts as state-managers
# #
# Scripts have many uses, one of which is to statically # Scripts have many uses, one of which is to statically
# make changes when a particular state of an object changes. # make changes when a particular state of an object changes.
# There is no "timer" involved in this case (although there could be), # There is no "timer" involved in this case (although there could be),
# whenever the script determines it is "invalid", it simply shuts down # whenever the script determines it is "invalid", it simply shuts down
# along with all the things it controls. # along with all the things it controls.
# #
# To show as many features as possible of the script and cmdset systems, # To show as many features as possible of the script and cmdset systems,
# we will use three scripts controlling one state each of the red_button, # we will use three scripts controlling one state each of the red_button,
# each with its own set of commands, handled by cmdsets - one for when # each with its own set of commands, handled by cmdsets - one for when
# the button has its lid open, and one for when it is closed and a # the button has its lid open, and one for when it is closed and a
# last one for when the player pushed the button and gets blinded by # last one for when the player pushed the button and gets blinded by
# a bright light. The last one also has a timer component that allows it # a bright light. The last one also has a timer component that allows it
# to remove itself after a while (and the player recovers their eyesight). # to remove itself after a while (and the player recovers their eyesight).
class ClosedLidState(Script): class ClosedLidState(Script):
""" """
This manages the cmdset for the "closed" button state. What this This manages the cmdset for the "closed" button state. What this
means is that while this script is valid, we add the RedButtonClosed means is that while this script is valid, we add the RedButtonClosed
cmdset to it (with commands like open, nudge lid etc) cmdset to it (with commands like open, nudge lid etc)
""" """
def at_script_creation(self): def at_script_creation(self):
"Called when script first created." "Called when script first created."
self.desc = "Script that manages the closed-state cmdsets for red button." self.desc = "Script that manages the closed-state cmdsets for red button."
self.persistent = True self.persistent = True
def at_start(self): def at_start(self):
""" """
This is called once every server restart, so we want to add the This is called once every server restart, so we want to add the
(memory-resident) cmdset to the object here. is_valid is automatically (memory-resident) cmdset to the object here. is_valid is automatically
checked so we don't need to worry about adding the script to an checked so we don't need to worry about adding the script to an
open lid. open lid.
""" """
#All we do is add the cmdset for the closed state. #All we do is add the cmdset for the closed state.
self.obj.cmdset.add(cmdsetexamples.LidClosedCmdSet) self.obj.cmdset.add(cmdsetexamples.LidClosedCmdSet)
def is_valid(self): def is_valid(self):
""" """
The script is only valid while the lid is closed. The script is only valid while the lid is closed.
self.obj is the red_button on which this script is defined. self.obj is the red_button on which this script is defined.
""" """
return not self.obj.db.lid_open return not self.obj.db.lid_open
@ -57,7 +57,7 @@ class ClosedLidState(Script):
def at_stop(self): def at_stop(self):
""" """
When the script stops we must make sure to clean up after us. When the script stops we must make sure to clean up after us.
""" """
self.obj.cmdset.delete(cmdsetexamples.LidClosedCmdSet) self.obj.cmdset.delete(cmdsetexamples.LidClosedCmdSet)
@ -68,23 +68,23 @@ class OpenLidState(Script):
the RedButtonOpen the RedButtonOpen
""" """
def at_script_creation(self): def at_script_creation(self):
"Called when script first created." "Called when script first created."
self.desc = "Script that manages the opened-state cmdsets for red button." self.desc = "Script that manages the opened-state cmdsets for red button."
self.persistent = True self.persistent = True
def at_start(self): def at_start(self):
""" """
This is called once every server restart, so we want to add the This is called once every server restart, so we want to add the
(memory-resident) cmdset to the object here. is_valid is (memory-resident) cmdset to the object here. is_valid is
automatically checked, so we don't need to worry about automatically checked, so we don't need to worry about
adding the cmdset to a closed lid-button. adding the cmdset to a closed lid-button.
""" """
#print "In Open at_start (should add cmdset)" #print "In Open at_start (should add cmdset)"
self.obj.cmdset.add(cmdsetexamples.LidOpenCmdSet) self.obj.cmdset.add(cmdsetexamples.LidOpenCmdSet)
def is_valid(self): def is_valid(self):
""" """
The script is only valid while the lid is open. The script is only valid while the lid is open.
self.obj is the red_button on which this script is defined. self.obj is the red_button on which this script is defined.
""" """
return self.obj.db.lid_open return self.obj.db.lid_open
@ -99,12 +99,12 @@ class OpenLidState(Script):
class BlindedState(Script): class BlindedState(Script):
""" """
This is a timed state. This is a timed state.
This adds a (very limited) cmdset TO THE PLAYER, during a certain time, This adds a (very limited) cmdset TO THE PLAYER, during a certain time,
after which the script will close and all functions are after which the script will close and all functions are
restored. It's up to the function starting the script to actually restored. It's up to the function starting the script to actually
set it on the right player object. set it on the right player object.
""" """
def at_script_creation(self): def at_script_creation(self):
""" """
@ -114,13 +114,13 @@ class BlindedState(Script):
self.desc = "Temporarily blinds the player for a little while." self.desc = "Temporarily blinds the player for a little while."
self.interval = 20 # seconds self.interval = 20 # seconds
self.start_delay = True # we don't want it to stop until after 20s. self.start_delay = True # we don't want it to stop until after 20s.
self.repeats = 1 # this will go away after interval seconds. self.repeats = 1 # this will go away after interval seconds.
self.persistent = False # we will ditch this if server goes down self.persistent = False # we will ditch this if server goes down
def at_start(self): def at_start(self):
""" """
We want to add the cmdset to the linked object. We want to add the cmdset to the linked object.
Note that the RedButtonBlind cmdset is defined to completly Note that the RedButtonBlind cmdset is defined to completly
replace the other cmdsets on the stack while it is active replace the other cmdsets on the stack while it is active
(this means that while blinded, only operations in this cmdset (this means that while blinded, only operations in this cmdset
@ -133,39 +133,39 @@ class BlindedState(Script):
def at_stop(self): def at_stop(self):
""" """
It's important that we clear out that blinded cmdset It's important that we clear out that blinded cmdset
when we are done! when we are done!
""" """
self.obj.msg("You blink feverishly as your eyesight slowly returns.") self.obj.msg("You blink feverishly as your eyesight slowly returns.")
self.obj.location.msg_contents("%s seems to be recovering their eyesight." self.obj.location.msg_contents("%s seems to be recovering their eyesight."
% self.obj.name, % self.obj.name,
exclude=self.obj) exclude=self.obj)
self.obj.cmdset.delete() # this will clear the latest added cmdset, self.obj.cmdset.delete() # this will clear the latest added cmdset,
# (which is the blinded one). # (which is the blinded one).
# #
# Timer/Event-like Scripts # Timer/Event-like Scripts
# #
# Scripts can also work like timers, or "events". Below we # Scripts can also work like timers, or "events". Below we
# define three such timed events that makes the button a little # define three such timed events that makes the button a little
# more "alive" - one that makes the button blink menacingly, another # more "alive" - one that makes the button blink menacingly, another
# that makes the lid covering the button slide back after a while. # that makes the lid covering the button slide back after a while.
# #
class CloseLidEvent(Script): class CloseLidEvent(Script):
""" """
This event closes the glass lid over the button This event closes the glass lid over the button
some time after it was opened. It's a one-off some time after it was opened. It's a one-off
script that should be started/created when the script that should be started/created when the
lid is opened. lid is opened.
""" """
def at_script_creation(self): def at_script_creation(self):
""" """
Called when script object is first created. Sets things up. Called when script object is first created. Sets things up.
We want to have a lid on the button that the user can pull We want to have a lid on the button that the user can pull
aside in order to make the button 'pressable'. But after a set aside in order to make the button 'pressable'. But after a set
time that lid should auto-close again, making the button safe time that lid should auto-close again, making the button safe
from pressing (and deleting this command). from pressing (and deleting this command).
""" """
self.key = "lid_closer" self.key = "lid_closer"
self.desc = "Closes lid on a red buttons" self.desc = "Closes lid on a red buttons"
@ -177,61 +177,61 @@ class CloseLidEvent(Script):
def is_valid(self): def is_valid(self):
""" """
This script can only operate if the lid is open; if it This script can only operate if the lid is open; if it
is already closed, the script is clearly invalid. is already closed, the script is clearly invalid.
Note that we are here relying on an self.obj being Note that we are here relying on an self.obj being
defined (and being a RedButton object) - this we should be able to defined (and being a RedButton object) - this we should be able to
expect since this type of script is always tied to one individual expect since this type of script is always tied to one individual
red button object and not having it would be an error. red button object and not having it would be an error.
""" """
return self.obj.db.lid_open return self.obj.db.lid_open
def at_repeat(self): def at_repeat(self):
""" """
Called after self.interval seconds. It closes the lid. Before this method is Called after self.interval seconds. It closes the lid. Before this method is
called, self.is_valid() is automatically checked, so there is no need to called, self.is_valid() is automatically checked, so there is no need to
check this manually. check this manually.
""" """
self.obj.close_lid() self.obj.close_lid()
class BlinkButtonEvent(Script): class BlinkButtonEvent(Script):
""" """
This timed script lets the button flash at regular intervals. This timed script lets the button flash at regular intervals.
""" """
def at_script_creation(self): def at_script_creation(self):
""" """
Sets things up. We want the button's lamp to blink at Sets things up. We want the button's lamp to blink at
regular intervals, unless it's broken (can happen regular intervals, unless it's broken (can happen
if you try to smash the glass, say). if you try to smash the glass, say).
""" """
self.key = "blink_button" self.key = "blink_button"
self.desc = "Blinks red buttons" self.desc = "Blinks red buttons"
self.interval = 35 #seconds self.interval = 35 #seconds
self.start_delay = False #blink right away self.start_delay = False #blink right away
self.persistent = True #keep blinking also after server reboot self.persistent = True #keep blinking also after server reboot
def is_valid(self): def is_valid(self):
""" """
Button will keep blinking unless it is broken. Button will keep blinking unless it is broken.
""" """
#print "self.obj.db.lamp_works:", self.obj.db.lamp_works #print "self.obj.db.lamp_works:", self.obj.db.lamp_works
return self.obj.db.lamp_works return self.obj.db.lamp_works
def at_repeat(self): def at_repeat(self):
""" """
Called every self.interval seconds. Makes the lamp in Called every self.interval seconds. Makes the lamp in
the button blink. the button blink.
""" """
self.obj.blink() self.obj.blink()
class DeactivateButtonEvent(Script): class DeactivateButtonEvent(Script):
""" """
This deactivates the button for a short while (it won't blink, won't This deactivates the button for a short while (it won't blink, won't
close its lid etc). It is meant to be called when the button is pushed close its lid etc). It is meant to be called when the button is pushed
and run as long as the blinded effect lasts. We cannot put these methods and run as long as the blinded effect lasts. We cannot put these methods
in the AddBlindedCmdSet script since that script is defined on the *player* in the AddBlindedCmdSet script since that script is defined on the *player*
whereas this one must be defined on the *button*. whereas this one must be defined on the *button*.
""" """
def at_script_creation(self): def at_script_creation(self):
""" """
@ -241,9 +241,9 @@ class DeactivateButtonEvent(Script):
self.desc = "Deactivate red button temporarily" self.desc = "Deactivate red button temporarily"
self.interval = 21 #seconds self.interval = 21 #seconds
self.start_delay = True # wait with the first repeat for self.interval seconds. self.start_delay = True # wait with the first repeat for self.interval seconds.
self.persistent = True self.persistent = True
self.repeats = 1 # only do this once self.repeats = 1 # only do this once
def at_start(self): def at_start(self):
""" """
Deactivate the button. Observe that this method is always Deactivate the button. Observe that this method is always
@ -252,9 +252,9 @@ class DeactivateButtonEvent(Script):
""" """
# closing the lid will also add the ClosedState script # closing the lid will also add the ClosedState script
self.obj.close_lid() self.obj.close_lid()
# lock the lid so other players can't access it until the # lock the lid so other players can't access it until the
# first one's effect has worn off. # first one's effect has worn off.
self.obj.db.lid_locked = True self.obj.db.lid_locked = True
# breaking the lamp also sets a correct desc # breaking the lamp also sets a correct desc
self.obj.break_lamp(feedback=False) self.obj.break_lamp(feedback=False)
@ -263,11 +263,11 @@ class DeactivateButtonEvent(Script):
When this is called, reset the functionality of the button. When this is called, reset the functionality of the button.
""" """
# restore button's desc. # restore button's desc.
self.obj.db.lamp_works = True self.obj.db.lamp_works = True
desc = "This is a large red button, inviting yet evil-looking. " desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is closed, protecting it." desc += "Its glass cover is closed, protecting it."
self.db.desc = desc self.db.desc = desc
# re-activate the blink button event. # re-activate the blink button event.
self.obj.scripts.add(BlinkButtonEvent) self.obj.scripts.add(BlinkButtonEvent)
# unlock the lid # unlock the lid

View file

@ -1,6 +1,6 @@
""" """
Template module for Scripts Template module for Scripts
Copy this module up one level to gamesrc/scripts and name it Copy this module up one level to gamesrc/scripts and name it
appropriately, then use that as a template to create your own script. appropriately, then use that as a template to create your own script.
@ -13,10 +13,10 @@ Scripts are objects that handle everything in the game having
a time-component (i.e. that may change with time, with or without a time-component (i.e. that may change with time, with or without
a player being involved in the change). Scripts can work like "events", a player being involved in the change). Scripts can work like "events",
in that they are triggered at regular intervals to do a certain script, in that they are triggered at regular intervals to do a certain script,
but an Script set on an object can also be responsible for silently but an Script set on an object can also be responsible for silently
checking if its state changes, so as to update it. Evennia use several checking if its state changes, so as to update it. Evennia use several
in-built scripts to keep track of things like time, to clean out in-built scripts to keep track of things like time, to clean out
dropped connections etc. dropped connections etc.
""" """
@ -26,32 +26,32 @@ class ExampleScript(BaseScript):
""" """
A script type is customized by redefining some or all of its hook methods and variables. A script type is customized by redefining some or all of its hook methods and variables.
* available properties * available properties
key (string) - name of object key (string) - name of object
name (string)- same as key name (string)- same as key
aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings. aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings.
dbref (int, read-only) - unique #id-number. Also "id" can be used. dbref (int, read-only) - unique #id-number. Also "id" can be used.
dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class
typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch. typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch.
date_created (string) - time stamp of object creation date_created (string) - time stamp of object creation
permissions (list of strings) - list of permission strings permissions (list of strings) - list of permission strings
desc (string) - optional description of script, shown in listings desc (string) - optional description of script, shown in listings
obj (Object) - optional object that this script is connected to and acts on (set automatically by obj.scripts.add()) obj (Object) - optional object that this script is connected to and acts on (set automatically by obj.scripts.add())
interval (int) - how often script should run, in seconds. <0 turns off ticker interval (int) - how often script should run, in seconds. <0 turns off ticker
start_delay (bool) - if the script should start repeating right away or wait self.interval seconds start_delay (bool) - if the script should start repeating right away or wait self.interval seconds
repeats (int) - how many times the script should repeat before stopping. 0 means infinite repeats repeats (int) - how many times the script should repeat before stopping. 0 means infinite repeats
persistent (bool) - if script should survive a server shutdown or not persistent (bool) - if script should survive a server shutdown or not
is_active (bool) - if script is currently running is_active (bool) - if script is currently running
* Handlers * Handlers
locks - lock-handler: use locks.add() to add new lock strings locks - lock-handler: use locks.add() to add new lock strings
db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr
ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data
* Helper methods * Helper methods
start() - start script (this usually happens automatically at creation and obj.script.add() etc) start() - start script (this usually happens automatically at creation and obj.script.add() etc)
stop() - stop script, and delete it stop() - stop script, and delete it
@ -66,22 +66,22 @@ class ExampleScript(BaseScript):
is_valid() - is called to check if the script is valid to be running is_valid() - is called to check if the script is valid to be running
at the current time. If is_valid() returns False, the running at the current time. If is_valid() returns False, the running
script is stopped and removed from the game. You can use this script is stopped and removed from the game. You can use this
to check state changes (i.e. an script tracking some combat to check state changes (i.e. an script tracking some combat
stats at regular intervals is only valid to run while there is stats at regular intervals is only valid to run while there is
actual combat going on). actual combat going on).
at_start() - Called every time the script is started, which for persistent at_start() - Called every time the script is started, which for persistent
scripts is at least once every server start. Note that this is scripts is at least once every server start. Note that this is
unaffected by self.delay_start, which only delays the first call unaffected by self.delay_start, which only delays the first call
to at_repeat(). to at_repeat().
at_repeat() - Called every self.interval seconds. It will be called immediately at_repeat() - Called every self.interval seconds. It will be called immediately
upon launch unless self.delay_start is True, which will delay upon launch unless self.delay_start is True, which will delay
the first call of this method by self.interval seconds. If the first call of this method by self.interval seconds. If
self.interval==0, this method will never be called. self.interval==0, this method will never be called.
at_stop() - Called as the script object is stopped and is about to be removed from at_stop() - Called as the script object is stopped and is about to be removed from
the game, e.g. because is_valid() returned False. the game, e.g. because is_valid() returned False.
at_server_reload() - Called when server reloads. Can be used to save temporary at_server_reload() - Called when server reloads. Can be used to save temporary
variables you want should survive a reload. variables you want should survive a reload.
at_server_shutdown() - called at a full server shutdown. at_server_shutdown() - called at a full server shutdown.
""" """
pass pass

View file

@ -1,5 +1,5 @@
# #
# This is an example batch build file for Evennia. # This is an example batch build file for Evennia.
# #
# It allows batch processing of normal Evennia commands. # It allows batch processing of normal Evennia commands.
# Test it by loading it with the @batchprocess command # Test it by loading it with the @batchprocess command
@ -10,9 +10,9 @@
# marks the end of a previous command definition (important!). # marks the end of a previous command definition (important!).
# #
# All supplied commands are given as normal, on their own line # All supplied commands are given as normal, on their own line
# and accepts arguments in any format up until the first next # and accepts arguments in any format up until the first next
# comment line begins. Extra whitespace is removed; an empty # comment line begins. Extra whitespace is removed; an empty
# line in a command definition translates into a newline. # line in a command definition translates into a newline.
# #
# This creates a red button # This creates a red button
@ -20,32 +20,32 @@
@create button:examples.red_button.RedButton @create button:examples.red_button.RedButton
# This comment ends input for @create # This comment ends input for @create
# Next command: # Next command:
@set button/desc = @set button/desc =
This is a large red button. Now and then This is a large red button. Now and then
it flashes in an evil, yet strangely tantalizing way. it flashes in an evil, yet strangely tantalizing way.
A big sign sits next to it. It says: A big sign sits next to it. It says:
----------- -----------
Press me! Press me!
----------- -----------
... It really begs to be pressed, doesn't it? You ... It really begs to be pressed, doesn't it? You
know you want to! know you want to!
# This ends the @set command. Note that line breaks and extra spaces # This ends the @set command. Note that line breaks and extra spaces
# in the argument are not considered. A completely empty line # in the argument are not considered. A completely empty line
# translates to a \n newline in the command; two empty lines will thus # translates to a \n newline in the command; two empty lines will thus
# create a new paragraph. (note that few commands support it though, you # create a new paragraph. (note that few commands support it though, you
# mainly want to use it for descriptions) # mainly want to use it for descriptions)
# Now let's place the button where it belongs (let's say limbo #2 is # Now let's place the button where it belongs (let's say limbo #2 is
# the evil lair in our example) # the evil lair in our example)
@teleport #2 @teleport #2

View file

@ -1,6 +1,6 @@
# #
# Batchcode script # Batchcode script
# #
# #
# The Batch-code processor accepts full python modules (e.g. "batch.py") that # The Batch-code processor accepts full python modules (e.g. "batch.py") that
# looks identical to normal Python files with a few exceptions that allows them # looks identical to normal Python files with a few exceptions that allows them
@ -8,29 +8,29 @@
# of the file and allows for features like stepping from block to block # of the file and allows for features like stepping from block to block
# (without executing those coming before), as well as automatic deletion # (without executing those coming before), as well as automatic deletion
# of created objects etc. You can however also run a batch-code python file # of created objects etc. You can however also run a batch-code python file
# directly using Python (and can also be de). # directly using Python (and can also be de).
# Code blocks are separated by python comments starting with special code words. # Code blocks are separated by python comments starting with special code words.
# #HEADER - this denotes commands global to the entire file, such as # #HEADER - this denotes commands global to the entire file, such as
# import statements and global variables. They will # import statements and global variables. They will
# automatically be made available for each block. Observe # automatically be made available for each block. Observe
# that changes to these variables made in one block is not # that changes to these variables made in one block is not
# preserved between blocks!) # preserved between blocks!)
# #CODE (infotext) [objname, objname, ...] - This designates a code block that will be executed like a # #CODE (infotext) [objname, objname, ...] - This designates a code block that will be executed like a
# stand-alone piece of code together with any #HEADER # stand-alone piece of code together with any #HEADER
# defined. # defined.
# infotext is a describing text about what goes in in this block. It will be # infotext is a describing text about what goes in in this block. It will be
# shown by the batchprocessing command. # shown by the batchprocessing command.
# <objname>s mark the (variable-)names of objects created in the code, # <objname>s mark the (variable-)names of objects created in the code,
# and which may be auto-deleted by the processor if desired (such as when # and which may be auto-deleted by the processor if desired (such as when
# debugging the script). E.g., if the code contains the command # debugging the script). E.g., if the code contains the command
# myobj = create.create_object(...), you could put 'myobj' in the #CODE header # myobj = create.create_object(...), you could put 'myobj' in the #CODE header
# regardless of what the created object is actually called in-game. # regardless of what the created object is actually called in-game.
# #INSERT filename - this includes another code batch file. The named file will be loaded and # #INSERT filename - this includes another code batch file. The named file will be loaded and
# run at this point. Note that code from the inserted file will NOT share #HEADERs # run at this point. Note that code from the inserted file will NOT share #HEADERs
# with the importing file, but will only use the headers in the importing file. # with the importing file, but will only use the headers in the importing file.
# make sure to not create a cyclic import here! # make sure to not create a cyclic import here!
# The following variable is automatically made available for the script: # The following variable is automatically made available for the script:
@ -38,9 +38,9 @@
# #
#HEADER #HEADER
# everything in this block will be appended to the beginning of # everything in this block will be appended to the beginning of
# all other #CODE blocks when they are executed. # all other #CODE blocks when they are executed.
from ev import create, search from ev import create, search
@ -53,13 +53,13 @@ limbo = search.objects('Limbo', global_search=True)[0]
#CODE (create red button) #CODE (create red button)
# This is the first code block. Within each block, python # This is the first code block. Within each block, python
# code works as normal. Note how we make use if imports and # code works as normal. Note how we make use if imports and
# 'limbo' defined in the #HEADER block. This block's header # 'limbo' defined in the #HEADER block. This block's header
# offers no information about red_button variable, so it # offers no information about red_button variable, so it
# won't be able to be deleted in debug mode. # won't be able to be deleted in debug mode.
# create a red button in limbo # create a red button in limbo
red_button = create.create_object(red_button.RedButton, key="Red button", red_button = create.create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"]) location=limbo, aliases=["button"])
# we take a look at what we created # we take a look at what we created
@ -74,10 +74,10 @@ caller.msg("A %s was created." % red_button.key)
# times). # times).
# the python variables we assign to must match the ones given in the # the python variables we assign to must match the ones given in the
# header for the system to be able to delete them afterwards during a # header for the system to be able to delete them afterwards during a
# debugging run. # debugging run.
table = create.create_object(baseobjects.Object, key="Table", location=limbo) table = create.create_object(baseobjects.Object, key="Table", location=limbo)
chair = create.create_object(baseobjects.Object, key="Chair", location=limbo) chair = create.create_object(baseobjects.Object, key="Chair", location=limbo)
string = "A %s and %s were created. If debug was active, they were deleted again." string = "A %s and %s were created. If debug was active, they were deleted again."
caller.msg(string % (table, chair)) caller.msg(string % (table, chair))

View file

@ -2,7 +2,7 @@
""" """
Set up the evennia system. A first startup consists of giving Set up the evennia system. A first startup consists of giving
the command './manage syncdb' to setup the system and create the command './manage syncdb' to setup the system and create
the database. the database.
""" """
import sys import sys
@ -21,10 +21,10 @@ except IOError:
VERSION = "Unknown version" VERSION = "Unknown version"
#------------------------------------------------------------ #------------------------------------------------------------
# Check so session file exists in the current dir- if not, create it. # Check so session file exists in the current dir- if not, create it.
#------------------------------------------------------------ #------------------------------------------------------------
_CREATED_SETTINGS = False _CREATED_SETTINGS = False
if not os.path.exists('settings.py'): if not os.path.exists('settings.py'):
# If settings.py doesn't already exist, create it and populate it with some # If settings.py doesn't already exist, create it and populate it with some
# basic stuff. # basic stuff.
@ -33,7 +33,7 @@ if not os.path.exists('settings.py'):
_CREATED_SETTINGS = True _CREATED_SETTINGS = True
string = \ string = \
"""# """#
# Evennia MU* server configuration file # Evennia MU* server configuration file
# #
# You may customize your setup by copy&pasting the variables you want # You may customize your setup by copy&pasting the variables you want
@ -44,14 +44,14 @@ if not os.path.exists('settings.py'):
# (also, the master config file may change with server updates). # (also, the master config file may change with server updates).
# #
from src.settings_default import * from src.settings_default import *
################################################### ###################################################
# Evennia base server config # Evennia base server config
################################################### ###################################################
################################################### ###################################################
# Evennia Database config # Evennia Database config
################################################### ###################################################
################################################### ###################################################
@ -59,15 +59,15 @@ from src.settings_default import *
################################################### ###################################################
################################################### ###################################################
# Default command sets # Default command sets
################################################### ###################################################
################################################### ###################################################
# Typeclasses # Typeclasses
################################################### ###################################################
################################################### ###################################################
# Batch processors # Batch processors
################################################### ###################################################
################################################### ###################################################
@ -100,8 +100,8 @@ from src.settings_default import *
# obs - this string cannot be under i18n since settings didn't exist yet. # obs - this string cannot be under i18n since settings didn't exist yet.
print """ print """
Welcome to Evennia (version %(version)s)! Welcome to Evennia (version %(version)s)!
We created a fresh settings.py file for you.""" % {'version': VERSION} We created a fresh settings.py file for you.""" % {'version': VERSION}
#------------------------------------------------------------ #------------------------------------------------------------
@ -118,18 +118,18 @@ except Exception:
# note - if this fails, ugettext will also fail, so we cannot translate this string. # note - if this fails, ugettext will also fail, so we cannot translate this string.
string += """\n string += """\n
Error: Couldn't import the file 'settings.py' in the directory Error: Couldn't import the file 'settings.py' in the directory
containing %(file)r. There are usually two reasons for this: containing %(file)r. There are usually two reasons for this:
1) You moved your settings.py elsewhere. In that case move it back or 1) You moved your settings.py elsewhere. In that case move it back or
create a link to it from this folder. create a link to it from this folder.
2) The settings module is where it's supposed to be, but contains errors. 2) The settings module is where it's supposed to be, but contains errors.
Review the traceback above to resolve the problem, then try again. Review the traceback above to resolve the problem, then try again.
3) If you get errors on finding DJANGO_SETTINGS_MODULE you might have 3) If you get errors on finding DJANGO_SETTINGS_MODULE you might have
set up django wrong in some way. If you run a virtual machine, it might be worth set up django wrong in some way. If you run a virtual machine, it might be worth
to restart it to see if this resolves the issue. Evennia should not require you to restart it to see if this resolves the issue. Evennia should not require you
to define any environment variables manually. to define any environment variables manually.
""" % {'file': __file__} """ % {'file': __file__}
print string print string
sys.exit(1) sys.exit(1)
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
@ -139,8 +139,8 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
#------------------------------------------------------------ #------------------------------------------------------------
if __name__ == "__main__": if __name__ == "__main__":
# checks if the settings file was created this run # checks if the settings file was created this run
if _CREATED_SETTINGS: if _CREATED_SETTINGS:
print _(""" print _("""
Edit your new settings.py file as needed, then run Edit your new settings.py file as needed, then run
'python manage syncdb' and follow the prompts to 'python manage syncdb' and follow the prompts to
@ -149,7 +149,7 @@ if __name__ == "__main__":
sys.exit() sys.exit()
# run the standard django manager, if dependencies match # run the standard django manager, if dependencies match
from src.utils.utils import check_evennia_dependencies from src.utils.utils import check_evennia_dependencies
if check_evennia_dependencies(): if check_evennia_dependencies():
from django.core.management import execute_manager from django.core.management import execute_manager
execute_manager(settings) execute_manager(settings)

View file

@ -14,7 +14,7 @@ upon returning, or not. A process returning != 0 will always stop, no
matter the value of this file. matter the value of this file.
""" """
import os import os
import sys import sys
from optparse import OptionParser from optparse import OptionParser
from subprocess import Popen, call from subprocess import Popen, call
@ -22,7 +22,7 @@ import Queue, thread, subprocess
# #
# System Configuration # System Configuration
# #
SERVER_PIDFILE = "server.pid" SERVER_PIDFILE = "server.pid"
@ -39,7 +39,7 @@ if not os.path.exists('settings.py'):
print "No settings.py file found. Run evennia.py to create it." print "No settings.py file found. Run evennia.py to create it."
sys.exit() sys.exit()
# Get the settings # Get the settings
from django.conf import settings from django.conf import settings
@ -58,26 +58,26 @@ if 'PYTHONPATH' in os.environ:
else: else:
os.environ['PYTHONPATH'] = currpath os.environ['PYTHONPATH'] = currpath
TWISTED_BINARY = 'twistd' TWISTED_BINARY = 'twistd'
if os.name == 'nt': if os.name == 'nt':
TWISTED_BINARY = 'twistd.bat' TWISTED_BINARY = 'twistd.bat'
err = False err = False
try: try:
import win32api # Test for for win32api import win32api # Test for for win32api
except ImportError: except ImportError:
err = True err = True
if not os.path.exists(TWISTED_BINARY): if not os.path.exists(TWISTED_BINARY):
err = True err = True
if err: if err:
print "Twisted binary for Windows is not ready to use. Please run evennia.py." print "Twisted binary for Windows is not ready to use. Please run evennia.py."
sys.exit() sys.exit()
# Functions # Functions
def set_restart_mode(restart_file, flag=True): def set_restart_mode(restart_file, flag=True):
""" """
This sets a flag file for the restart mode. This sets a flag file for the restart mode.
""" """
f = open(restart_file, 'w') f = open(restart_file, 'w')
f.write(str(flag)) f.write(str(flag))
f.close() f.close()
@ -89,24 +89,24 @@ def get_restart_mode(restart_file):
if os.path.exists(restart_file): if os.path.exists(restart_file):
flag = open(restart_file, 'r').read() flag = open(restart_file, 'r').read()
return flag == "True" return flag == "True"
return False return False
def get_pid(pidfile): def get_pid(pidfile):
""" """
Get the PID (Process ID) by trying to access Get the PID (Process ID) by trying to access
an PID file. an PID file.
""" """
pid = None pid = None
if os.path.exists(pidfile): if os.path.exists(pidfile):
f = open(pidfile, 'r') f = open(pidfile, 'r')
pid = f.read() pid = f.read()
return pid return pid
def cycle_logfile(logfile): def cycle_logfile(logfile):
""" """
Move the old log files to <filename>.old Move the old log files to <filename>.old
""" """
logfile_old = logfile + '.old' logfile_old = logfile + '.old'
if os.path.exists(logfile): if os.path.exists(logfile):
# Cycle the old logfiles to *.old # Cycle the old logfiles to *.old
@ -122,70 +122,70 @@ def cycle_logfile(logfile):
if os.path.exists(logfile_old): if os.path.exists(logfile_old):
# E.g. Windows don't support rename-replace # E.g. Windows don't support rename-replace
os.remove(logfile_old) os.remove(logfile_old)
os.rename(logfile, logfile_old) os.rename(logfile, logfile_old)
# Start program management # Start program management
SERVER = None SERVER = None
PORTAL = None PORTAL = None
def start_services(server_argv, portal_argv): def start_services(server_argv, portal_argv):
""" """
This calls a threaded loop that launces the Portal and Server This calls a threaded loop that launces the Portal and Server
and then restarts them when they finish. and then restarts them when they finish.
""" """
global SERVER, PORTAL global SERVER, PORTAL
processes = Queue.Queue() processes = Queue.Queue()
def server_waiter(queue): def server_waiter(queue):
try: try:
rc = Popen(server_argv).wait() rc = Popen(server_argv).wait()
except Exception, e: except Exception, e:
print "Server process error: %(e)s" % {'e': e} print "Server process error: %(e)s" % {'e': e}
queue.put(("server_stopped", rc)) # this signals the controller that the program finished queue.put(("server_stopped", rc)) # this signals the controller that the program finished
def portal_waiter(queue): def portal_waiter(queue):
try: try:
rc = Popen(portal_argv).wait() rc = Popen(portal_argv).wait()
except Exception, e: except Exception, e:
print "Portal process error: %(e)s" % {'e': e} print "Portal process error: %(e)s" % {'e': e}
queue.put(("portal_stopped", rc)) # this signals the controller that the program finished queue.put(("portal_stopped", rc)) # this signals the controller that the program finished
if server_argv: if server_argv:
# start server as a reloadable thread # start server as a reloadable thread
SERVER = thread.start_new_thread(server_waiter, (processes, )) SERVER = thread.start_new_thread(server_waiter, (processes, ))
if portal_argv: if portal_argv:
if get_restart_mode(PORTAL_RESTART): if get_restart_mode(PORTAL_RESTART):
# start portal as interactive, reloadable thread # start portal as interactive, reloadable thread
PORTAL = thread.start_new_thread(portal_waiter, (processes, )) PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
else: else:
# normal operation: start portal as a daemon; we don't care to monitor it for restart # normal operation: start portal as a daemon; we don't care to monitor it for restart
PORTAL = Popen(portal_argv) PORTAL = Popen(portal_argv)
if not SERVER: if not SERVER:
# if portal is daemon and no server is running, we have no reason to continue to the loop. # if portal is daemon and no server is running, we have no reason to continue to the loop.
return return
# Reload loop # Reload loop
while True: while True:
# this blocks until something is actually returned. # this blocks until something is actually returned.
message, rc = processes.get() message, rc = processes.get()
# restart only if process stopped cleanly # restart only if process stopped cleanly
if message == "server_stopped" and int(rc) == 0 and get_restart_mode(SERVER_RESTART): if message == "server_stopped" and int(rc) == 0 and get_restart_mode(SERVER_RESTART):
print "Evennia Server stopped. Restarting ..." print "Evennia Server stopped. Restarting ..."
SERVER = thread.start_new_thread(server_waiter, (processes, )) SERVER = thread.start_new_thread(server_waiter, (processes, ))
continue continue
# normally the portal is not reloaded since it's run as a daemon. # normally the portal is not reloaded since it's run as a daemon.
if message == "portal_stopped" and int(rc) == 0 and get_restart_mode(PORTAL_RESTART): if message == "portal_stopped" and int(rc) == 0 and get_restart_mode(PORTAL_RESTART):
print "Evennia Portal stopped in interactive mode. Restarting ..." print "Evennia Portal stopped in interactive mode. Restarting ..."
PORTAL = thread.start_new_thread(portal_waiter, (processes, )) PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
continue continue
break break
# Setup signal handling # Setup signal handling
@ -193,19 +193,19 @@ def main():
""" """
This handles the command line input of the runner (it's most often called by evennia.py) This handles the command line input of the runner (it's most often called by evennia.py)
""" """
parser = OptionParser(usage="%prog [options] start", parser = OptionParser(usage="%prog [options] start",
description="This runner should normally *not* be called directly - it is called automatically from the evennia.py main program. It manages the Evennia game server and portal processes an hosts a threaded loop to restart the Server whenever it is stopped (this constitues Evennia's reload mechanism).") description="This runner should normally *not* be called directly - it is called automatically from the evennia.py main program. It manages the Evennia game server and portal processes an hosts a threaded loop to restart the Server whenever it is stopped (this constitues Evennia's reload mechanism).")
parser.add_option('-s', '--noserver', action='store_true', parser.add_option('-s', '--noserver', action='store_true',
dest='noserver', default=False, dest='noserver', default=False,
help='Do not start Server process') help='Do not start Server process')
parser.add_option('-p', '--noportal', action='store_true', parser.add_option('-p', '--noportal', action='store_true',
dest='noportal', default=False, dest='noportal', default=False,
help='Do not start Portal process') help='Do not start Portal process')
parser.add_option('-i', '--iserver', action='store_true', parser.add_option('-i', '--iserver', action='store_true',
dest='iserver', default=False, dest='iserver', default=False,
help='output server log to stdout instead of logfile') help='output server log to stdout instead of logfile')
parser.add_option('-d', '--iportal', action='store_true', parser.add_option('-d', '--iportal', action='store_true',
dest='iportal', default=False, dest='iportal', default=False,
help='output portal log to stdout. Does not make portal a daemon.') help='output portal log to stdout. Does not make portal a daemon.')
parser.add_option('-S', '--profile-server', action='store_true', parser.add_option('-S', '--profile-server', action='store_true',
@ -222,17 +222,17 @@ def main():
parser.print_help() parser.print_help()
sys.exit() sys.exit()
# set up default project calls # set up default project calls
server_argv = [TWISTED_BINARY, server_argv = [TWISTED_BINARY,
'--nodaemon', '--nodaemon',
'--logfile=%s' % SERVER_LOGFILE, '--logfile=%s' % SERVER_LOGFILE,
'--pidfile=%s' % SERVER_PIDFILE, '--pidfile=%s' % SERVER_PIDFILE,
'--python=%s' % SERVER_PY_FILE] '--python=%s' % SERVER_PY_FILE]
portal_argv = [TWISTED_BINARY, portal_argv = [TWISTED_BINARY,
'--logfile=%s' % PORTAL_LOGFILE, '--logfile=%s' % PORTAL_LOGFILE,
'--pidfile=%s' % PORTAL_PIDFILE, '--pidfile=%s' % PORTAL_PIDFILE,
'--python=%s' % PORTAL_PY_FILE] '--python=%s' % PORTAL_PY_FILE]
# Profiling settings (read file from python shell e.g with # Profiling settings (read file from python shell e.g with
# p = pstats.Stats('server.prof') # p = pstats.Stats('server.prof')
sprof_argv = ['--savestats', sprof_argv = ['--savestats',
@ -242,14 +242,14 @@ def main():
'--profiler=cprofile', '--profiler=cprofile',
'--profile=portal.prof'] '--profile=portal.prof']
# Server # Server
pid = get_pid(SERVER_PIDFILE) pid = get_pid(SERVER_PIDFILE)
if pid and not options.noserver: if pid and not options.noserver:
print "\nEvennia Server is already running as process %(pid)s. Not restarted." % {'pid': pid} print "\nEvennia Server is already running as process %(pid)s. Not restarted." % {'pid': pid}
options.noserver = True options.noserver = True
if options.noserver: if options.noserver:
server_argv = None server_argv = None
else: else:
set_restart_mode(SERVER_RESTART, True) set_restart_mode(SERVER_RESTART, True)
if options.iserver: if options.iserver:
@ -264,19 +264,19 @@ def main():
cycle_logfile(SERVER_LOGFILE) cycle_logfile(SERVER_LOGFILE)
# Portal # Portal
pid = get_pid(PORTAL_PIDFILE) pid = get_pid(PORTAL_PIDFILE)
if pid and not options.noportal: if pid and not options.noportal:
print "\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid} print "\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid}
options.noportal = True options.noportal = True
if options.noportal: if options.noportal:
portal_argv = None portal_argv = None
else: else:
if options.iportal: if options.iportal:
# make portal interactive # make portal interactive
portal_argv[1] = '--nodaemon' portal_argv[1] = '--nodaemon'
PORTAL_INTERACTIVE = True PORTAL_INTERACTIVE = True
set_restart_mode(PORTAL_RESTART, True) set_restart_mode(PORTAL_RESTART, True)
print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)." print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)."
else: else:
@ -297,7 +297,7 @@ def main():
# Start processes # Start processes
start_services(server_argv, portal_argv) start_services(server_argv, portal_argv)
if __name__ == '__main__': if __name__ == '__main__':
from src.utils.utils import check_evennia_dependencies from src.utils.utils import check_evennia_dependencies
if check_evennia_dependencies(): if check_evennia_dependencies():

View file

@ -1,6 +1,6 @@
# #
# This sets up how models are displayed # This sets up how models are displayed
# in the web admin interface. # in the web admin interface.
# #
from django.contrib import admin from django.contrib import admin
@ -12,7 +12,7 @@ class ServerConfigAdmin(admin.ModelAdmin):
list_display_links = ('db_key',) list_display_links = ('db_key',)
ordering = ['db_key', 'db_value'] ordering = ['db_key', 'db_value']
search_fields = ['db_key'] search_fields = ['db_key']
save_as = True save_as = True
save_on_top = True save_on_top = True
list_select_related = True list_select_related = True
admin.site.register(ServerConfig, ServerConfigAdmin) admin.site.register(ServerConfig, ServerConfigAdmin)

View file

@ -4,11 +4,11 @@ to service the MUD portal proxy.
The separation works like this: The separation works like this:
Portal - (AMP client) handles protocols. It contains a list of connected sessions in a Portal - (AMP client) handles protocols. It contains a list of connected sessions in a
dictionary for identifying the respective player connected. If it looses the AMP connection dictionary for identifying the respective player connected. If it looses the AMP connection
it will automatically try to reconnect. it will automatically try to reconnect.
Server - (AMP server) Handles all mud operations. The server holds its own list Server - (AMP server) Handles all mud operations. The server holds its own list
of sessions tied to player objects. This is synced against the portal at startup of sessions tied to player objects. This is synced against the portal at startup
and when a session connects/disconnects and when a session connects/disconnects
@ -32,15 +32,15 @@ from src.server.serversession import ServerSession
PORTAL_RESTART = os.path.join(settings.GAME_DIR, "portal.restart") PORTAL_RESTART = os.path.join(settings.GAME_DIR, "portal.restart")
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server.restart") SERVER_RESTART = os.path.join(settings.GAME_DIR, "server.restart")
# communication bits # communication bits
PCONN = chr(1) # portal session connect PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync SSYNC = chr(8) # server session sync
# i18n # i18n
@ -54,7 +54,7 @@ def get_restart_mode(restart_file):
if os.path.exists(restart_file): if os.path.exists(restart_file):
flag = open(restart_file, 'r').read() flag = open(restart_file, 'r').read()
return flag == "True" return flag == "True"
return False return False
class AmpServerFactory(protocol.ServerFactory): class AmpServerFactory(protocol.ServerFactory):
""" """
@ -66,8 +66,8 @@ class AmpServerFactory(protocol.ServerFactory):
server: The Evennia server service instance server: The Evennia server service instance
protocol: The protocol the factory creates instances of. protocol: The protocol the factory creates instances of.
""" """
self.server = server self.server = server
self.protocol = AMPProtocol self.protocol = AMPProtocol
def buildProtocol(self, addr): def buildProtocol(self, addr):
""" """
@ -91,7 +91,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
maxDelay = 1 maxDelay = 1
def __init__(self, portal): def __init__(self, portal):
self.portal = portal self.portal = portal
self.protocol = AMPProtocol self.protocol = AMPProtocol
def startedConnecting(self, connector): def startedConnecting(self, connector):
@ -100,10 +100,10 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
""" """
pass pass
#print 'AMP started to connect:', connector #print 'AMP started to connect:', connector
def buildProtocol(self, addr): def buildProtocol(self, addr):
""" """
Creates an AMPProtocol instance when connecting to the server. Creates an AMPProtocol instance when connecting to the server.
""" """
#print "Portal connected to Evennia server at %s." % addr #print "Portal connected to Evennia server at %s." % addr
self.resetDelay() self.resetDelay()
@ -114,7 +114,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
""" """
Called when the AMP connection to the MUD server is lost. Called when the AMP connection to the MUD server is lost.
""" """
if not get_restart_mode(SERVER_RESTART): if not get_restart_mode(SERVER_RESTART):
self.portal.sessions.announce_all(_(" Portal lost connection to Server.")) self.portal.sessions.announce_all(_(" Portal lost connection to Server."))
protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason) protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
@ -128,7 +128,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
class MsgPortal2Server(amp.Command): class MsgPortal2Server(amp.Command):
""" """
Message portal -> server Message portal -> server
""" """
arguments = [('sessid', amp.Integer()), arguments = [('sessid', amp.Integer()),
@ -138,7 +138,7 @@ class MsgPortal2Server(amp.Command):
response = [] response = []
class MsgServer2Portal(amp.Command): class MsgServer2Portal(amp.Command):
""" """
Message server -> portal Message server -> portal
""" """
arguments = [('sessid', amp.Integer()), arguments = [('sessid', amp.Integer()),
@ -155,7 +155,7 @@ class OOBPortal2Server(amp.Command):
('data', amp.String())] ('data', amp.String())]
errors = [(Exception, "EXCEPTION")] errors = [(Exception, "EXCEPTION")]
response = [] response = []
class OOBServer2Portal(amp.Command): class OOBServer2Portal(amp.Command):
""" """
OOB data server -> portal OOB data server -> portal
@ -164,13 +164,13 @@ class OOBServer2Portal(amp.Command):
('data', amp.String())] ('data', amp.String())]
errors = [(Exception, "EXCEPTION")] errors = [(Exception, "EXCEPTION")]
response = [] response = []
class ServerAdmin(amp.Command): class ServerAdmin(amp.Command):
""" """
Portal -> Server Portal -> Server
Sent when the portal needs to perform admin Sent when the portal needs to perform admin
operations on the server, such as when a new operations on the server, such as when a new
session connects or resyncs session connects or resyncs
""" """
arguments = [('sessid', amp.Integer()), arguments = [('sessid', amp.Integer()),
@ -178,13 +178,13 @@ class ServerAdmin(amp.Command):
('data', amp.String())] ('data', amp.String())]
errors = [(Exception, 'EXCEPTION')] errors = [(Exception, 'EXCEPTION')]
response = [] response = []
class PortalAdmin(amp.Command): class PortalAdmin(amp.Command):
""" """
Server -> Portal Server -> Portal
Sent when the server needs to perform admin Sent when the server needs to perform admin
operations on the portal. operations on the portal.
""" """
arguments = [('sessid', amp.Integer()), arguments = [('sessid', amp.Integer()),
('operation', amp.String()), ('operation', amp.String()),
@ -209,27 +209,27 @@ class AMPProtocol(amp.AMP):
subclasses that specify the datatypes of the input/output of these methods. subclasses that specify the datatypes of the input/output of these methods.
""" """
# helper methods # helper methods
def connectionMade(self): def connectionMade(self):
""" """
This is called when a connection is established This is called when a connection is established
between server and portal. It is called on both sides, between server and portal. It is called on both sides,
so we need to make sure to only trigger resync from the so we need to make sure to only trigger resync from the
server side. server side.
""" """
if hasattr(self.factory, "portal"): if hasattr(self.factory, "portal"):
sessdata = self.factory.portal.sessions.get_all_sync_data() sessdata = self.factory.portal.sessions.get_all_sync_data()
#print sessdata #print sessdata
self.call_remote_ServerAdmin(0, self.call_remote_ServerAdmin(0,
PSYNC, PSYNC,
data=sessdata) data=sessdata)
if get_restart_mode(SERVER_RESTART): if get_restart_mode(SERVER_RESTART):
msg = _(" ... Server restarted.") msg = _(" ... Server restarted.")
self.factory.portal.sessions.announce_all(msg) self.factory.portal.sessions.announce_all(msg)
self.factory.portal.sessions.at_server_connection() self.factory.portal.sessions.at_server_connection()
# Error handling # Error handling
def errback(self, e, info): def errback(self, e, info):
"error handler, to avoid dropping connections on server tracebacks." "error handler, to avoid dropping connections on server tracebacks."
@ -240,8 +240,8 @@ class AMPProtocol(amp.AMP):
# Message definition + helper methods to call/create each message type # Message definition + helper methods to call/create each message type
# Portal -> Server Msg # Portal -> Server Msg
def amp_msg_portal2server(self, sessid, msg, data): def amp_msg_portal2server(self, sessid, msg, data):
""" """
Relays message to server. This method is executed on the Server. Relays message to server. This method is executed on the Server.
""" """
@ -253,14 +253,14 @@ class AMPProtocol(amp.AMP):
def call_remote_MsgPortal2Server(self, sessid, msg, data=""): def call_remote_MsgPortal2Server(self, sessid, msg, data=""):
""" """
Access method called by the Portal and executed on the Portal. Access method called by the Portal and executed on the Portal.
""" """
#print "msg portal->server (portal side):", sessid, msg #print "msg portal->server (portal side):", sessid, msg
self.callRemote(MsgPortal2Server, self.callRemote(MsgPortal2Server,
sessid=sessid, sessid=sessid,
msg=msg, msg=msg,
data=dumps(data)).addErrback(self.errback, "MsgPortal2Server") data=dumps(data)).addErrback(self.errback, "MsgPortal2Server")
# Server -> Portal message # Server -> Portal message
def amp_msg_server2portal(self, sessid, msg, data): def amp_msg_server2portal(self, sessid, msg, data):
""" """
@ -281,11 +281,11 @@ class AMPProtocol(amp.AMP):
msg=to_str(msg), msg=to_str(msg),
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal") data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
# OOB Portal -> Server # OOB Portal -> Server
# Portal -> Server Msg # Portal -> Server Msg
def amp_oob_portal2server(self, sessid, data): def amp_oob_portal2server(self, sessid, data):
""" """
Relays out-of-band data to server. This method is executed on the Server. Relays out-of-band data to server. This method is executed on the Server.
""" """
@ -297,13 +297,13 @@ class AMPProtocol(amp.AMP):
def call_remote_OOBPortal2Server(self, sessid, data=""): def call_remote_OOBPortal2Server(self, sessid, data=""):
""" """
Access method called by the Portal and executed on the Portal. Access method called by the Portal and executed on the Portal.
""" """
#print "oob portal->server (portal side):", sessid, data #print "oob portal->server (portal side):", sessid, data
self.callRemote(OOBPortal2Server, self.callRemote(OOBPortal2Server,
sessid=sessid, sessid=sessid,
data=dumps(data)).addErrback(self.errback, "OOBPortal2Server") data=dumps(data)).addErrback(self.errback, "OOBPortal2Server")
# Server -> Portal message # Server -> Portal message
def amp_oob_server2portal(self, sessid, data): def amp_oob_server2portal(self, sessid, data):
""" """
@ -318,13 +318,13 @@ class AMPProtocol(amp.AMP):
""" """
Access method called by the Server and executed on the Server. Access method called by the Server and executed on the Server.
""" """
#print "oob server->portal (server side):", sessid, data #print "oob server->portal (server side):", sessid, data
self.callRemote(OOBServer2Portal, self.callRemote(OOBServer2Portal,
sessid=sessid, sessid=sessid,
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal") data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
# Server administration from the Portal side
# Server administration from the Portal side
def amp_server_admin(self, sessid, operation, data): def amp_server_admin(self, sessid, operation, data):
""" """
This allows the portal to perform admin This allows the portal to perform admin
@ -334,12 +334,12 @@ class AMPProtocol(amp.AMP):
data = loads(data) data = loads(data)
#print "serveradmin (server side):", sessid, operation, data #print "serveradmin (server side):", sessid, operation, data
if operation == PCONN: #portal_session_connect if operation == PCONN: #portal_session_connect
# create a new session and sync it # create a new session and sync it
sess = ServerSession() sess = ServerSession()
sess.sessionhandler = self.factory.server.sessions sess.sessionhandler = self.factory.server.sessions
sess.load_sync_data(data) sess.load_sync_data(data)
if sess.logged_in and sess.uid: if sess.logged_in and sess.uid:
# this can happen in the case of auto-authenticating protocols like SSH # this can happen in the case of auto-authenticating protocols like SSH
sess.player = PlayerDB.objects.get_player_from_uid(sess.uid) sess.player = PlayerDB.objects.get_player_from_uid(sess.uid)
@ -348,24 +348,24 @@ class AMPProtocol(amp.AMP):
self.factory.server.sessions.portal_connect(sessid, sess) self.factory.server.sessions.portal_connect(sessid, sess)
elif operation == PDISCONN: #'portal_session_disconnect' elif operation == PDISCONN: #'portal_session_disconnect'
# session closed from portal side # session closed from portal side
self.factory.server.sessions.portal_disconnect(sessid) self.factory.server.sessions.portal_disconnect(sessid)
elif operation == PSYNC: #'portal_session_sync' elif operation == PSYNC: #'portal_session_sync'
# force a resync of sessions when portal reconnects to server (e.g. after a server reboot) # force a resync of sessions when portal reconnects to server (e.g. after a server reboot)
# the data kwarg contains a dict {sessid: {arg1:val1,...}} representing the attributes # the data kwarg contains a dict {sessid: {arg1:val1,...}} representing the attributes
# to sync for each session. # to sync for each session.
sesslist = [] sesslist = []
server_sessionhandler = self.factory.server.sessions server_sessionhandler = self.factory.server.sessions
for sessid, sessdict in data.items(): for sessid, sessdict in data.items():
sess = ServerSession() sess = ServerSession()
sess.sessionhandler = server_sessionhandler sess.sessionhandler = server_sessionhandler
sess.load_sync_data(sessdict) sess.load_sync_data(sessdict)
if sess.uid: if sess.uid:
sess.player = PlayerDB.objects.get_player_from_uid(sess.uid) sess.player = PlayerDB.objects.get_player_from_uid(sess.uid)
sesslist.append(sess) sesslist.append(sess)
# replace sessions on server # replace sessions on server
server_sessionhandler.portal_session_sync(sesslist) server_sessionhandler.portal_session_sync(sesslist)
# after sync is complete we force-validate all scripts (this starts everything) # after sync is complete we force-validate all scripts (this starts everything)
init_mode = ServerConfig.objects.conf("server_restart_mode", default=None) init_mode = ServerConfig.objects.conf("server_restart_mode", default=None)
ScriptDB.objects.validate(init_mode=init_mode) ScriptDB.objects.validate(init_mode=init_mode)
@ -373,7 +373,7 @@ class AMPProtocol(amp.AMP):
else: else:
raise Exception(_("operation %(op)s not recognized.") % {'op': operation}) raise Exception(_("operation %(op)s not recognized.") % {'op': operation})
return {} return {}
ServerAdmin.responder(amp_server_admin) ServerAdmin.responder(amp_server_admin)
@ -393,7 +393,7 @@ class AMPProtocol(amp.AMP):
def amp_portal_admin(self, sessid, operation, data): def amp_portal_admin(self, sessid, operation, data):
""" """
This allows the server to perform admin This allows the server to perform admin
operations on the portal. This is executed on the Portal. operations on the portal. This is executed on the Portal.
""" """
data = loads(data) data = loads(data)
@ -401,7 +401,7 @@ class AMPProtocol(amp.AMP):
#print "portaladmin (portal side):", sessid, operation, data #print "portaladmin (portal side):", sessid, operation, data
if operation == SLOGIN: # 'server_session_login' if operation == SLOGIN: # 'server_session_login'
# a session has authenticated; sync it. # a session has authenticated; sync it.
sess = self.factory.portal.sessions.get_session(sessid) sess = self.factory.portal.sessions.get_session(sessid)
sess.load_sync_data(data) sess.load_sync_data(data)
elif operation == SDISCONN: #'server_session_disconnect' elif operation == SDISCONN: #'server_session_disconnect'
@ -415,14 +415,14 @@ class AMPProtocol(amp.AMP):
elif operation == SSHUTD: #server_shutdown' elif operation == SSHUTD: #server_shutdown'
# the server orders the portal to shut down # the server orders the portal to shut down
self.factory.portal.shutdown(restart=False) self.factory.portal.shutdown(restart=False)
elif operation == SSYNC: #'server_session_sync' elif operation == SSYNC: #'server_session_sync'
# server wants to save session data to the portal, maybe because # server wants to save session data to the portal, maybe because
# it's about to shut down. We don't overwrite any sessions, # it's about to shut down. We don't overwrite any sessions,
# just update data on them and remove eventual ones that are # just update data on them and remove eventual ones that are
# out of sync (shouldn't happen normally). # out of sync (shouldn't happen normally).
portal_sessionhandler = self.factory.portal.sessions.sessions portal_sessionhandler = self.factory.portal.sessions.sessions
to_save = [sessid for sessid in data if sessid in portal_sessionhandler.sessions] to_save = [sessid for sessid in data if sessid in portal_sessionhandler.sessions]
to_delete = [sessid for sessid in data if sessid not in to_save] to_delete = [sessid for sessid in data if sessid not in to_save]
@ -449,10 +449,3 @@ class AMPProtocol(amp.AMP):
sessid=sessid, sessid=sessid,
operation=operation, operation=operation,
data=data).addErrback(self.errback, "PortalAdmin") data=data).addErrback(self.errback, "PortalAdmin")

View file

@ -11,7 +11,7 @@ from django.core import management
from django.conf import settings from django.conf import settings
from src.server.models import ServerConfig from src.server.models import ServerConfig
from src.help.models import HelpEntry from src.help.models import HelpEntry
from src.utils import create from src.utils import create
# i18n # i18n
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -19,7 +19,7 @@ from django.utils.translation import ugettext as _
def create_config_values(): def create_config_values():
""" """
Creates the initial config values. Creates the initial config values.
""" """
ServerConfig.objects.conf("site_name", settings.SERVERNAME) ServerConfig.objects.conf("site_name", settings.SERVERNAME)
ServerConfig.objects.conf("idle_timeout", settings.IDLE_TIMEOUT) ServerConfig.objects.conf("idle_timeout", settings.IDLE_TIMEOUT)
@ -33,28 +33,28 @@ def create_objects():
""" """
Creates the #1 player and Limbo room. Creates the #1 player and Limbo room.
""" """
print _(" Creating objects (Player #1 and Limbo room) ...") print _(" Creating objects (Player #1 and Limbo room) ...")
# Set the initial User's account object's username on the #1 object. # Set the initial User's account object's username on the #1 object.
# This object is pure django and only holds name, email and password. # This object is pure django and only holds name, email and password.
god_user = get_god_user() god_user = get_god_user()
# Create a Player 'user profile' object to hold eventual # Create a Player 'user profile' object to hold eventual
# mud-specific settings for the bog standard User object. This is # mud-specific settings for the bog standard User object. This is
# accessed by user.get_profile() and can also store attributes. # accessed by user.get_profile() and can also store attributes.
# It also holds mud permissions, but for a superuser these # It also holds mud permissions, but for a superuser these
# have no effect anyhow. # have no effect anyhow.
character_typeclass = settings.BASE_CHARACTER_TYPECLASS character_typeclass = settings.BASE_CHARACTER_TYPECLASS
# Create the Player object as well as the in-game god-character # Create the Player object as well as the in-game god-character
# for user #1. We can't set location and home yet since nothing # for user #1. We can't set location and home yet since nothing
# exists. Also, all properties (name, email, password, is_superuser) # exists. Also, all properties (name, email, password, is_superuser)
# is inherited from the user so we don't specify it again here. # is inherited from the user so we don't specify it again here.
god_character = create.create_player(god_user.username, None, None, god_character = create.create_player(god_user.username, None, None,
user=god_user, user=god_user,
create_character=True, create_character=True,
character_typeclass=character_typeclass) character_typeclass=character_typeclass)
@ -66,7 +66,7 @@ def create_objects():
god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()") god_character.locks.add("examine:perm(Immortals);edit:false();delete:false();boot:false();msg:all();puppet:false()")
god_character.save() god_character.save()
# Limbo is the default "nowhere" starting room # Limbo is the default "nowhere" starting room
room_typeclass = settings.BASE_ROOM_TYPECLASS room_typeclass = settings.BASE_ROOM_TYPECLASS
@ -83,11 +83,11 @@ def create_objects():
# Now that Limbo exists, try to set the user up in Limbo (unless # Now that Limbo exists, try to set the user up in Limbo (unless
# the creation hooks already fixed this). # the creation hooks already fixed this).
if not god_character.location: if not god_character.location:
god_character.location = limbo_obj god_character.location = limbo_obj
if not god_character.home: if not god_character.home:
god_character.home = limbo_obj god_character.home = limbo_obj
def create_channels(): def create_channels():
""" """
Creates some sensible default channels. Creates some sensible default channels.
@ -97,7 +97,7 @@ def create_channels():
# public channel # public channel
key, aliases, desc, locks = settings.CHANNEL_PUBLIC key, aliases, desc, locks = settings.CHANNEL_PUBLIC
pchan = create.create_channel(key, aliases, desc, locks=locks) pchan = create.create_channel(key, aliases, desc, locks=locks)
# mudinfo channel # mudinfo channel
key, aliases, desc, locks = settings.CHANNEL_MUDINFO key, aliases, desc, locks = settings.CHANNEL_MUDINFO
ichan = create.create_channel(key, aliases, desc, locks=locks) ichan = create.create_channel(key, aliases, desc, locks=locks)
# connectinfo channel # connectinfo channel
@ -110,29 +110,29 @@ def create_channels():
PlayerChannelConnection.objects.create_connection(goduser, pchan) PlayerChannelConnection.objects.create_connection(goduser, pchan)
PlayerChannelConnection.objects.create_connection(goduser, ichan) PlayerChannelConnection.objects.create_connection(goduser, ichan)
PlayerChannelConnection.objects.create_connection(goduser, cchan) PlayerChannelConnection.objects.create_connection(goduser, cchan)
def import_MUX_help_files(): def import_MUX_help_files():
""" """
Imports the MUX help files. Imports the MUX help files.
""" """
print _(" Importing MUX help database (devel reference only) ...") print _(" Importing MUX help database (devel reference only) ...")
management.call_command('loaddata', '../src/help/mux_help_db.json', verbosity=0) management.call_command('loaddata', '../src/help/mux_help_db.json', verbosity=0)
# categorize the MUX help files into its own category. # categorize the MUX help files into its own category.
default_category = "MUX" default_category = "MUX"
print _(" Moving imported help db to help category '%(default)s'." \ print _(" Moving imported help db to help category '%(default)s'." \
% {'default': default_category}) % {'default': default_category})
HelpEntry.objects.all_to_category(default_category) HelpEntry.objects.all_to_category(default_category)
def create_system_scripts(): def create_system_scripts():
""" """
Setup the system repeat scripts. They are automatically started Setup the system repeat scripts. They are automatically started
by the create_script function. by the create_script function.
""" """
from src.scripts import scripts from src.scripts import scripts
print _(" Creating and starting global scripts ...") print _(" Creating and starting global scripts ...")
# check so that all sessions are alive. # check so that all sessions are alive.
script1 = create.create_script(scripts.CheckSessions) script1 = create.create_script(scripts.CheckSessions)
# validate all scripts in script table. # validate all scripts in script table.
script2 = create.create_script(scripts.ValidateScripts) script2 = create.create_script(scripts.ValidateScripts)
@ -140,7 +140,7 @@ def create_system_scripts():
script3 = create.create_script(scripts.ValidateChannelHandler) script3 = create.create_script(scripts.ValidateChannelHandler)
if not script1 or not script2 or not script3: if not script1 or not script2 or not script3:
print _(" Error creating system scripts.") print _(" Error creating system scripts.")
def start_game_time(): def start_game_time():
""" """
This starts a persistent script that keeps track of the This starts a persistent script that keeps track of the
@ -155,7 +155,7 @@ def start_game_time():
def create_admin_media_links(): def create_admin_media_links():
""" """
This traverses to src/web/media and tries to create a symbolic This traverses to src/web/media and tries to create a symbolic
link to the django media files from within the MEDIA_ROOT. link to the django media files from within the MEDIA_ROOT.
These are files we normally don't These are files we normally don't
want to mess with (use templates to customize the admin want to mess with (use templates to customize the admin
look). Linking is needed since the Twisted webserver otherwise has no look). Linking is needed since the Twisted webserver otherwise has no
@ -168,8 +168,8 @@ def create_admin_media_links():
apath = os.path.join(settings.ADMIN_MEDIA_ROOT) apath = os.path.join(settings.ADMIN_MEDIA_ROOT)
if os.path.isdir(apath): if os.path.isdir(apath):
print _(" ADMIN_MEDIA_ROOT already exists. Ignored.") print _(" ADMIN_MEDIA_ROOT already exists. Ignored.")
return return
if os.name == 'nt': if os.name == 'nt':
print _(" Admin-media files copied to ADMIN_MEDIA_ROOT (Windows mode).") print _(" Admin-media files copied to ADMIN_MEDIA_ROOT (Windows mode).")
os.mkdir(apath) os.mkdir(apath)
os.system('xcopy "%s" "%s" /e /q /c' % (dpath, apath)) os.system('xcopy "%s" "%s" /e /q /c' % (dpath, apath))
@ -188,34 +188,34 @@ def at_initial_setup():
""" """
modname = settings.AT_INITIAL_SETUP_HOOK_MODULE modname = settings.AT_INITIAL_SETUP_HOOK_MODULE
if not modname: if not modname:
return return
try: try:
mod = __import__(modname, fromlist=[None]) mod = __import__(modname, fromlist=[None])
except ImportError, ValueError: except ImportError, ValueError:
return return
print _(" Running at_initial_setup() hook.") print _(" Running at_initial_setup() hook.")
if mod.__dict__.get("at_initial_setup", None): if mod.__dict__.get("at_initial_setup", None):
mod.at_initial_setup() mod.at_initial_setup()
def handle_setup(last_step): def handle_setup(last_step):
""" """
Main logic for the module. It allows for restarting Main logic for the module. It allows for restarting
the initialization at any point if one of the modules the initialization at any point if one of the modules
should crash. should crash.
""" """
if last_step < 0: if last_step < 0:
# this means we don't need to handle setup since # this means we don't need to handle setup since
# it already ran sucessfully once. # it already ran sucessfully once.
return return
elif last_step == None: elif last_step == None:
# config doesn't exist yet. First start of server # config doesn't exist yet. First start of server
last_step = 0 last_step = 0
# setting up the list of functions to run # setting up the list of functions to run
setup_queue = [ setup_queue = [
create_config_values, create_config_values,
create_objects, create_objects,
create_channels, create_channels,
create_system_scripts, create_system_scripts,
start_game_time, start_game_time,
@ -224,17 +224,17 @@ def handle_setup(last_step):
at_initial_setup] at_initial_setup]
if not settings.IMPORT_MUX_HELP: if not settings.IMPORT_MUX_HELP:
# skip importing of the MUX helpfiles, they are # skip importing of the MUX helpfiles, they are
# not interesting except for developers. # not interesting except for developers.
del setup_queue[-2] del setup_queue[-2]
#print " Initial setup: %s steps." % (len(setup_queue)) #print " Initial setup: %s steps." % (len(setup_queue))
# step through queue, from last completed function # step through queue, from last completed function
for num, setup_func in enumerate(setup_queue[last_step:]): for num, setup_func in enumerate(setup_queue[last_step:]):
# run the setup function. Note that if there is a # run the setup function. Note that if there is a
# traceback we let it stop the system so the config # traceback we let it stop the system so the config
# step is not saved. # step is not saved.
#print "%s..." % num #print "%s..." % num
try: try:
@ -255,10 +255,10 @@ def handle_setup(last_step):
chan.delete() chan.delete()
for conn in PlayerChannelConnection.objects.all(): for conn in PlayerChannelConnection.objects.all():
conn.delete() conn.delete()
raise
ServerConfig.objects.conf("last_initial_setup_step", last_step + num + 1) raise
ServerConfig.objects.conf("last_initial_setup_step", last_step + num + 1)
# We got through the entire list. Set last_step to -1 so we don't # We got through the entire list. Set last_step to -1 so we don't
# have to run this again. # have to run this again.
ServerConfig.objects.conf("last_initial_setup_step", -1) ServerConfig.objects.conf("last_initial_setup_step", -1)

View file

@ -5,17 +5,17 @@ from django.db import models
class ServerConfigManager(models.Manager): class ServerConfigManager(models.Manager):
""" """
This ServerConfigManager implements methods for searching This ServerConfigManager implements methods for searching
and manipulating ServerConfigs directly from the database. and manipulating ServerConfigs directly from the database.
These methods will all return database objects These methods will all return database objects
(or QuerySets) directly. (or QuerySets) directly.
ServerConfigs are used to store certain persistent settings for the ServerConfigs are used to store certain persistent settings for the
server at run-time. server at run-time.
Evennia-specific: Evennia-specific:
conf conf
""" """
def conf(self, key=None, value=None, delete=False, default=None): def conf(self, key=None, value=None, delete=False, default=None):
@ -27,13 +27,13 @@ class ServerConfigManager(models.Manager):
elif delete == True: elif delete == True:
for conf in self.filter(db_key=key): for conf in self.filter(db_key=key):
conf.delete() conf.delete()
elif value != None: elif value != None:
conf = self.filter(db_key=key) conf = self.filter(db_key=key)
if conf: if conf:
conf = conf[0] conf = conf[0]
else: else:
conf = self.model(db_key=key) conf = self.model(db_key=key)
conf.value = value # this will pickle conf.value = value # this will pickle
else: else:
conf = self.filter(db_key=key) conf = self.filter(db_key=key)
if not conf: if not conf:

View file

@ -12,52 +12,52 @@ effect of MCCP unless you have extremely heavy traffic or sits on a
terribly slow connection. terribly slow connection.
This protocol is implemented by the telnet protocol importing This protocol is implemented by the telnet protocol importing
mccp_compress and calling it from its write methods. mccp_compress and calling it from its write methods.
""" """
import zlib import zlib
# negotiations for v1 and v2 of the protocol # negotiations for v1 and v2 of the protocol
MCCP = chr(86) MCCP = chr(86)
FLUSH = zlib.Z_SYNC_FLUSH FLUSH = zlib.Z_SYNC_FLUSH
def mccp_compress(protocol, data): def mccp_compress(protocol, data):
"Handles zlib compression, if applicable" "Handles zlib compression, if applicable"
if hasattr(protocol, 'zlib'): if hasattr(protocol, 'zlib'):
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH) return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
return data return data
class Mccp(object): class Mccp(object):
""" """
Implements the MCCP protocol. Add this to a Implements the MCCP protocol. Add this to a
variable on the telnet protocol to set it up. variable on the telnet protocol to set it up.
""" """
def __init__(self, protocol): def __init__(self, protocol):
""" """
initialize MCCP by storing protocol on initialize MCCP by storing protocol on
ourselves and calling the client to see if ourselves and calling the client to see if
it supports MCCP. Sets callbacks to it supports MCCP. Sets callbacks to
start zlib compression in that case. start zlib compression in that case.
""" """
self.protocol = protocol self.protocol = protocol
self.protocol.protocol_flags['MCCP'] = False self.protocol.protocol_flags['MCCP'] = False
# ask if client will mccp, connect callbacks to handle answer # ask if client will mccp, connect callbacks to handle answer
self.protocol.will(MCCP).addCallbacks(self.do_mccp, self.no_mccp) self.protocol.will(MCCP).addCallbacks(self.do_mccp, self.no_mccp)
def no_mccp(self, option): def no_mccp(self, option):
""" """
If client doesn't support mccp, don't do anything. If client doesn't support mccp, don't do anything.
""" """
if hasattr(self.protocol, 'zlib'): if hasattr(self.protocol, 'zlib'):
del self.protocol.zlib del self.protocol.zlib
self.protocol.protocol_flags['MCCP'] = False self.protocol.protocol_flags['MCCP'] = False
def do_mccp(self, option): def do_mccp(self, option):
""" """
The client supports MCCP. Set things up by The client supports MCCP. Set things up by
creating a zlib compression stream. creating a zlib compression stream.
""" """
self.protocol.protocol_flags['MCCP'] = True self.protocol.protocol_flags['MCCP'] = True
self.protocol.requestNegotiation(MCCP, '') self.protocol.requestNegotiation(MCCP, '')
self.protocol.zlib = zlib.compressobj(9) self.protocol.zlib = zlib.compressobj(9)

View file

@ -4,8 +4,8 @@ Server Configuration flags
This holds persistent server configuration flags. This holds persistent server configuration flags.
Config values should usually be set through the Config values should usually be set through the
manager's conf() method. manager's conf() method.
""" """
try: try:
@ -26,10 +26,10 @@ from django.utils.translation import ugettext as _
# ServerConfig # ServerConfig
# #
#------------------------------------------------------------ #------------------------------------------------------------
class ServerConfig(SharedMemoryModel): class ServerConfig(SharedMemoryModel):
""" """
On-the fly storage of global settings. On-the fly storage of global settings.
Properties defined on ServerConfig: Properties defined on ServerConfig:
key - main identifier key - main identifier
@ -50,15 +50,15 @@ class ServerConfig(SharedMemoryModel):
db_value = models.TextField(blank=True) db_value = models.TextField(blank=True)
objects = ServerConfigManager() objects = ServerConfigManager()
# Wrapper properties to easily set database fields. These are # Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using # @property decorators that allows to access these fields using
# normal python operations (without having to remember to save() # normal python operations (without having to remember to save()
# etc). So e.g. a property 'attr' has a get/set/del decorator # etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value, # defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self # value = self.attr and del self.attr respectively (where self
# is the object in question). # is the object in question).
# key property (wraps db_key) # key property (wraps db_key)
#@property #@property
def key_get(self): def key_get(self):
@ -86,12 +86,12 @@ class ServerConfig(SharedMemoryModel):
if utils.has_parent('django.db.models.base.Model', value): if utils.has_parent('django.db.models.base.Model', value):
# we have to protect against storing db objects. # we have to protect against storing db objects.
logger.log_errmsg(_("ServerConfig cannot store db objects! (%s)" % value)) logger.log_errmsg(_("ServerConfig cannot store db objects! (%s)" % value))
return return
self.db_value = pickle.dumps(value) self.db_value = pickle.dumps(value)
self.save() self.save()
#@value.deleter #@value.deleter
def value_del(self): def value_del(self):
"Deleter. Allows for del self.value. Deletes entry." "Deleter. Allows for del self.value. Deletes entry."
self.delete() self.delete()
value = property(value_get, value_set, value_del) value = property(value_get, value_set, value_del)
@ -100,8 +100,8 @@ class ServerConfig(SharedMemoryModel):
verbose_name = "Server Config value" verbose_name = "Server Config value"
verbose_name_plural = "Server Config values" verbose_name_plural = "Server Config values"
# #
# ServerConfig other methods # ServerConfig other methods
# #
def __unicode__(self): def __unicode__(self):

View file

@ -28,14 +28,14 @@ regex_varval = re.compile(r"%s(.*?)%s(.*?)[%s]" % (MSDP_VAR, MSDP_VAL, ENDING))
class Msdp(object): class Msdp(object):
""" """
Implements the MSDP protocol. Implements the MSDP protocol.
""" """
def __init__(self, protocol): def __init__(self, protocol):
""" """
Initiates by storing the protocol Initiates by storing the protocol
on itself and trying to determine on itself and trying to determine
if the client supports MSDP. if the client supports MSDP.
""" """
self.protocol = protocol self.protocol = protocol
self.protocol.protocol_FLAGS['MSDP'] = False self.protocol.protocol_FLAGS['MSDP'] = False
@ -45,39 +45,39 @@ class Msdp(object):
def no_msdp(self, option): def no_msdp(self, option):
"No msdp" "No msdp"
pass pass
def do_msdp(self, option): def do_msdp(self, option):
""" """
Called when client confirms that it can do MSDP. Called when client confirms that it can do MSDP.
""" """
self.protocol.protocol_flags['MSDP'] = True self.protocol.protocol_flags['MSDP'] = True
def func_to_msdp(self, cmdname, data): def func_to_msdp(self, cmdname, data):
""" """
handle return data from cmdname by converting it to handle return data from cmdname by converting it to
a proper msdp structure. data can either be a single value (will be a proper msdp structure. data can either be a single value (will be
converted to a string), a list (will be converted to an MSDP_ARRAY), converted to a string), a list (will be converted to an MSDP_ARRAY),
or a dictionary (will be converted to MSDP_TABLE). or a dictionary (will be converted to MSDP_TABLE).
OBS - this supports nested tables and even arrays nested OBS - this supports nested tables and even arrays nested
inside tables, as opposed to the receive method. Arrays inside tables, as opposed to the receive method. Arrays
cannot hold tables by definition (the table must be named cannot hold tables by definition (the table must be named
with MSDP_VAR, and an array can only contain MSDP_VALs). with MSDP_VAR, and an array can only contain MSDP_VALs).
""" """
def make_table(name, datadict, string): def make_table(name, datadict, string):
"build a table that may be nested with other tables or arrays." "build a table that may be nested with other tables or arrays."
string += MSDP_VAR + name + MSDP_VAL + MSDP_TABLE_OPEN string += MSDP_VAR + name + MSDP_VAL + MSDP_TABLE_OPEN
for key, val in datadict.items(): for key, val in datadict.items():
if type(val) == type({}): if type(val) == type({}):
string += make_table(key, val, string) string += make_table(key, val, string)
elif hasattr(val, '__iter__'): elif hasattr(val, '__iter__'):
string += make_array(key, val, string) string += make_array(key, val, string)
else: else:
string += MSDP_VAR + key + MSDP_VAL + val string += MSDP_VAR + key + MSDP_VAL + val
string += MSDP_TABLE_CLOSE string += MSDP_TABLE_CLOSE
return string return string
def make_array(name, string, datalist): def make_array(name, string, datalist):
"build a simple array. Arrays may not nest tables by definition." "build a simple array. Arrays may not nest tables by definition."
@ -85,23 +85,23 @@ class Msdp(object):
for val in datalist: for val in datalist:
string += MSDP_VAL + val string += MSDP_VAL + val
string += MSDP_ARRAY_CLOSE string += MSDP_ARRAY_CLOSE
return string return string
if type(data) == type({}): if type(data) == type({}):
msdp_string = make_table(cmdname, data, "") msdp_string = make_table(cmdname, data, "")
elif hasattr(data, '__iter__'): elif hasattr(data, '__iter__'):
msdp_string = make_array(cmdname, data, "") msdp_string = make_array(cmdname, data, "")
else: else:
msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data
return msdp_string return msdp_string
def msdp_to_func(self, data): def msdp_to_func(self, data):
""" """
Handle a client's requested negotiation, converting Handle a client's requested negotiation, converting
it into a function mapping it into a function mapping
OBS-this does not support receiving nested tables OBS-this does not support receiving nested tables
from the client at this point! from the client at this point!
""" """
tables = {} tables = {}
arrays = {} arrays = {}
@ -112,23 +112,23 @@ class Msdp(object):
for array in regex_array.findall(data): for array in regex_array.findall(data):
arrays[array[0]] = dict(regex_varval(array[1])) arrays[array[0]] = dict(regex_varval(array[1]))
variables = dict(regex._varval(regex_array.sub("", regex_table.sub("", data)))) variables = dict(regex._varval(regex_array.sub("", regex_table.sub("", data))))
# MSDP Commands
# Some given MSDP (varname, value) pairs can also be treated as command + argument. # MSDP Commands
# Some given MSDP (varname, value) pairs can also be treated as command + argument.
# Generic msdp command map. The argument will be sent to the given command. # Generic msdp command map. The argument will be sent to the given command.
# See http://tintin.sourceforge.net/msdp/ for definitions of each command. # See http://tintin.sourceforge.net/msdp/ for definitions of each command.
# These are client->server commands. # These are client->server commands.
def msdp_cmd_list(self, arg): def msdp_cmd_list(self, arg):
""" """
The List command allows for retrieving various info about the server/client The List command allows for retrieving various info about the server/client
""" """
if arg == 'COMMANDS': if arg == 'COMMANDS':
return self.func_to_msdp(arg, MSDP_COMMANDS.keys()) return self.func_to_msdp(arg, MSDP_COMMANDS.keys())
elif arg == 'LISTS': elif arg == 'LISTS':
return self.func_to_msdp(arg, ("COMMANDS", "LISTS", return self.func_to_msdp(arg, ("COMMANDS", "LISTS",
"CONFIGURABLE_VARIABLES", "CONFIGURABLE_VARIABLES",
"REPORTED_VARIABLES", "SENDABLE_VARIABLES")) "REPORTED_VARIABLES", "SENDABLE_VARIABLES"))
elif arg == 'CONFIGURABLE_VARIABLES': elif arg == 'CONFIGURABLE_VARIABLES':
return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID")) return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
@ -149,7 +149,7 @@ class Msdp(object):
try: try:
MSDP_REPORTABLE[arg](report=True) MSDP_REPORTABLE[arg](report=True)
except Exception: except Exception:
logger.log_trace() logger.log_trace()
def msdp_cmd_unreport(self, arg): def msdp_cmd_unreport(self, arg):
""" """
@ -159,7 +159,7 @@ class Msdp(object):
MSDP_REPORTABLE[arg](eport=False) MSDP_REPORTABLE[arg](eport=False)
except Exception: except Exception:
logger.log_trace() logger.log_trace()
def msdp_cmd_reset(self, arg): def msdp_cmd_reset(self, arg):
""" """
The reset command resets a variable to its initial state. The reset command resets a variable to its initial state.
@ -167,12 +167,12 @@ class Msdp(object):
try: try:
MSDP_REPORTABLE[arg](reset=True) MSDP_REPORTABLE[arg](reset=True)
except Exception: except Exception:
logger.log_trace() logger.log_trace()
def msdp_cmd_send(self, arg): def msdp_cmd_send(self, arg):
""" """
Request the server to send a particular variable Request the server to send a particular variable
to the client. to the client.
arg - this is a list of variables the client wants. arg - this is a list of variables the client wants.
""" """
@ -181,8 +181,8 @@ class Msdp(object):
try: try:
ret.append(MSDP_REPORTABLE[arg](send=True)) ret.append(MSDP_REPORTABLE[arg](send=True))
except Exception: except Exception:
logger.log_trace() logger.log_trace()
return ret return ret
MSDP_COMMANDS = { MSDP_COMMANDS = {
"LIST": self.msdp_list, "LIST": self.msdp_list,
@ -192,57 +192,57 @@ class Msdp(object):
"UNREPORT":"mspd_unreport" "UNREPORT":"mspd_unreport"
} }
# MSDP_MAP is a standard suggestions for making it easy to create generic guis. # MSDP_MAP is a standard suggestions for making it easy to create generic guis.
# this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It # this maps MSDP command names to Evennia commands found in OOB_FUNC_MODULE. It
# is up to these commands to return data on proper form. # is up to these commands to return data on proper form.
MSDP_REPORTABLE = { MSDP_REPORTABLE = {
# General # General
"CHARACTER_NAME": "get_character_name", "CHARACTER_NAME": "get_character_name",
"SERVER_ID": "get_server_id", "SERVER_ID": "get_server_id",
"SERVER_TIME": "get_server_time", "SERVER_TIME": "get_server_time",
# Character # Character
"AFFECTS": "char_affects", "AFFECTS": "char_affects",
"ALIGNMENT": "char_alignment", "ALIGNMENT": "char_alignment",
"EXPERIENCE": "char_experience", "EXPERIENCE": "char_experience",
"EXPERIENCE_MAX": "char_experience_max", "EXPERIENCE_MAX": "char_experience_max",
"EXPERIENCE_TNL": "char_experience_tnl", "EXPERIENCE_TNL": "char_experience_tnl",
"HEALTH": "char_health", "HEALTH": "char_health",
"HEALTH_MAX": "char_health_max", "HEALTH_MAX": "char_health_max",
"LEVEL": "char_level", "LEVEL": "char_level",
"RACE": "char_race", "RACE": "char_race",
"CLASS": "char_class", "CLASS": "char_class",
"MANA": "char_mana", "MANA": "char_mana",
"MANA_MAX": "char_mana_max", "MANA_MAX": "char_mana_max",
"WIMPY": "char_wimpy", "WIMPY": "char_wimpy",
"PRACTICE": "char_practice", "PRACTICE": "char_practice",
"MONEY": "char_money", "MONEY": "char_money",
"MOVEMENT": "char_movement", "MOVEMENT": "char_movement",
"MOVEMENT_MAX": "char_movement_max", "MOVEMENT_MAX": "char_movement_max",
"HITROLL": "char_hitroll", "HITROLL": "char_hitroll",
"DAMROLL": "char_damroll", "DAMROLL": "char_damroll",
"AC": "char_ac", "AC": "char_ac",
"STR": "char_str", "STR": "char_str",
"INT": "char_int", "INT": "char_int",
"WIS": "char_wis", "WIS": "char_wis",
"DEX": "char_dex", "DEX": "char_dex",
"CON": "char_con", "CON": "char_con",
# Combat # Combat
"OPPONENT_HEALTH": "opponent_health", "OPPONENT_HEALTH": "opponent_health",
"OPPONENT_HEALTH_MAX":"opponent_health_max", "OPPONENT_HEALTH_MAX":"opponent_health_max",
"OPPONENT_LEVEL": "opponent_level", "OPPONENT_LEVEL": "opponent_level",
"OPPONENT_NAME": "opponent_name", "OPPONENT_NAME": "opponent_name",
# World # World
"AREA_NAME": "area_name", "AREA_NAME": "area_name",
"ROOM_EXITS": "area_room_exits", "ROOM_EXITS": "area_room_exits",
"ROOM_NAME": "room_name", "ROOM_NAME": "room_name",
"ROOM_VNUM": "room_dbref", "ROOM_VNUM": "room_dbref",
"WORLD_TIME": "world_time", "WORLD_TIME": "world_time",
# Configurable variables # Configurable variables
"CLIENT_ID": "client_id", "CLIENT_ID": "client_id",
"CLIENT_VERSION": "client_version", "CLIENT_VERSION": "client_version",
"PLUGIN_ID": "plugin_id", "PLUGIN_ID": "plugin_id",
"ANSI_COLORS": "ansi_colours", "ANSI_COLORS": "ansi_colours",
@ -250,13 +250,13 @@ class Msdp(object):
"UTF_8": "utf_8", "UTF_8": "utf_8",
"SOUND": "sound", "SOUND": "sound",
"MXP": "mxp", "MXP": "mxp",
# GUI variables # GUI variables
"BUTTON_1": "button1", "BUTTON_1": "button1",
"BUTTON_2": "button2", "BUTTON_2": "button2",
"BUTTON_3": "button3", "BUTTON_3": "button3",
"BUTTON_4": "button4", "BUTTON_4": "button4",
"BUTTON_5": "button5", "BUTTON_5": "button5",
"GAUGE_1": "gauge1", "GAUGE_1": "gauge1",
"GAUGE_2": "gauge2", "GAUGE_2": "gauge2",
"GAUGE_3": "gauge3", "GAUGE_3": "gauge3",

View file

@ -4,9 +4,9 @@ MSSP - Mud Server Status Protocol
This implements the MSSP telnet protocol as per This implements the MSSP telnet protocol as per
http://tintin.sourceforge.net/mssp/. MSSP allows web portals and http://tintin.sourceforge.net/mssp/. MSSP allows web portals and
listings to have their crawlers find the mud and automatically listings to have their crawlers find the mud and automatically
extract relevant information about it, such as genre, how many extract relevant information about it, such as genre, how many
active players and so on. active players and so on.
Most of these settings are de Most of these settings are de
@ -19,23 +19,23 @@ MSSP_VAR = chr(1)
MSSP_VAL = chr(2) MSSP_VAL = chr(2)
# try to get the customized mssp info, if it exists. # try to get the customized mssp info, if it exists.
MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={}) MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
class Mssp(object): class Mssp(object):
""" """
Implements the MSSP protocol. Add this to a Implements the MSSP protocol. Add this to a
variable on the telnet protocol to set it up. variable on the telnet protocol to set it up.
""" """
def __init__(self, protocol): def __init__(self, protocol):
""" """
initialize MSSP by storing protocol on ourselves initialize MSSP by storing protocol on ourselves
and calling the client to see if it supports and calling the client to see if it supports
MSSP. MSSP.
""" """
self.protocol = protocol self.protocol = protocol
self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp) self.protocol.will(MSSP).addCallbacks(self.do_mssp, self.no_mssp)
def get_player_count(self): def get_player_count(self):
"Get number of logged-in players" "Get number of logged-in players"
return str(self.protocol.sessionhandler.count_loggedin()) return str(self.protocol.sessionhandler.count_loggedin())
@ -52,31 +52,31 @@ class Mssp(object):
def do_mssp(self, option): def do_mssp(self, option):
""" """
Negotiate all the information. Negotiate all the information.
""" """
self.mssp_table = { self.mssp_table = {
# Required fields # Required fields
"NAME": "Evennia", "NAME": "Evennia",
"PLAYERS": self.get_player_count, "PLAYERS": self.get_player_count,
"UPTIME" : self.get_uptime, "UPTIME" : self.get_uptime,
# Generic # Generic
"CRAWL DELAY": "-1", "CRAWL DELAY": "-1",
"HOSTNAME": "", # current or new hostname "HOSTNAME": "", # current or new hostname
"PORT": ["4000"], # most important port should be last in list "PORT": ["4000"], # most important port should be last in list
"CODEBASE": "Evennia", "CODEBASE": "Evennia",
"CONTACT": "", # email for contacting the mud "CONTACT": "", # email for contacting the mud
"CREATED": "", # year MUD was created "CREATED": "", # year MUD was created
"ICON": "", # url to icon 32x32 or larger; <32kb. "ICON": "", # url to icon 32x32 or larger; <32kb.
"IP": "", # current or new IP address "IP": "", # current or new IP address
"LANGUAGE": "", # name of language used, e.g. English "LANGUAGE": "", # name of language used, e.g. English
"LOCATION": "", # full English name of server country "LOCATION": "", # full English name of server country
"MINIMUM AGE": "0", # set to 0 if not applicable "MINIMUM AGE": "0", # set to 0 if not applicable
"WEBSITE": "www.evennia.com", "WEBSITE": "www.evennia.com",
# Categorisation # Categorisation
@ -88,14 +88,14 @@ class Mssp(object):
# Roleplaying, Simulation, Social or Strategy # Roleplaying, Simulation, Social or Strategy
"STATUS": "Open Beta", # Alpha, Closed Beta, Open Beta, Live "STATUS": "Open Beta", # Alpha, Closed Beta, Open Beta, Live
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew "GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
"INTERMUD": "IMC2", # evennia supports IMC2. "INTERMUD": "IMC2", # evennia supports IMC2.
"SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein, "SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein,
# Cyberpunk, Dragonlance, etc. Or None if not available. # Cyberpunk, Dragonlance, etc. Or None if not available.
# World # World
"AREAS": "0", "AREAS": "0",
"HELPFILES": "0", "HELPFILES": "0",
"MOBILES": "0", "MOBILES": "0",
"OBJECTS": "0", "OBJECTS": "0",
"ROOMS": "0", # use 0 if room-less "ROOMS": "0", # use 0 if room-less
@ -128,7 +128,7 @@ class Mssp(object):
"HIRING BUILDERS": "0", "HIRING BUILDERS": "0",
"HIRING CODERS": "0", "HIRING CODERS": "0",
# Extended variables # Extended variables
# World # World

View file

@ -1,6 +1,6 @@
""" """
This module implements the main Evennia server process, the core of This module implements the main Evennia server process, the core of
the game engine. the game engine.
This module should be started with the 'twistd' executable since it This module should be started with the 'twistd' executable since it
sets up all the networking features. (this is done automatically sets up all the networking features. (this is done automatically
@ -30,7 +30,7 @@ if os.name == 'nt':
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
#------------------------------------------------------------ #------------------------------------------------------------
# Evennia Portal settings # Evennia Portal settings
#------------------------------------------------------------ #------------------------------------------------------------
VERSION = get_evennia_version() VERSION = get_evennia_version()
@ -53,15 +53,15 @@ TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS and TELNET_INTERFACES
SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
AMP_HOST = settings.AMP_HOST AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT AMP_PORT = settings.AMP_PORT
AMP_ENABLED = AMP_HOST and AMP_PORT AMP_ENABLED = AMP_HOST and AMP_PORT
#------------------------------------------------------------ #------------------------------------------------------------
# Portal Service object # Portal Service object
#------------------------------------------------------------ #------------------------------------------------------------
class Portal(object): class Portal(object):
@ -69,17 +69,17 @@ class Portal(object):
The main Portal server handler. This object sets up the database and The main Portal server handler. This object sets up the database and
tracks and interlinks all the twisted network services that make up tracks and interlinks all the twisted network services that make up
Portal. Portal.
""" """
def __init__(self, application): def __init__(self, application):
""" """
Setup the server. Setup the server.
application - an instantiated Twisted application application - an instantiated Twisted application
""" """
sys.path.append('.') sys.path.append('.')
# create a store of services # create a store of services
self.services = service.IServiceCollection(application) self.services = service.IServiceCollection(application)
self.amp_protocol = None # set by amp factory self.amp_protocol = None # set by amp factory
@ -88,25 +88,25 @@ class Portal(object):
print '\n' + '-'*50 print '\n' + '-'*50
# Make info output to the terminal. # Make info output to the terminal.
self.terminal_output() self.terminal_output()
print '-'*50 print '-'*50
# set a callback if the server is killed abruptly, # set a callback if the server is killed abruptly,
# by Ctrl-C, reboot etc. # by Ctrl-C, reboot etc.
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True) reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True)
self.game_running = False self.game_running = False
def terminal_output(self): def terminal_output(self):
""" """
Outputs server startup info to the terminal. Outputs server startup info to the terminal.
""" """
print _(' %(servername)s Portal (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION} print _(' %(servername)s Portal (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION}
if AMP_ENABLED: if AMP_ENABLED:
print " amp (Server): %s" % AMP_PORT print " amp (Server): %s" % AMP_PORT
if TELNET_ENABLED: if TELNET_ENABLED:
ports = ", ".join([str(port) for port in TELNET_PORTS]) ports = ", ".join([str(port) for port in TELNET_PORTS])
ifaces = ",".join([" %s" % iface for iface in TELNET_INTERFACES if iface != '0.0.0.0']) ifaces = ",".join([" %s" % iface for iface in TELNET_INTERFACES if iface != '0.0.0.0'])
print " telnet%s: %s" % (ifaces, ports) print " telnet%s: %s" % (ifaces, ports)
@ -129,11 +129,11 @@ class Portal(object):
def set_restart_mode(self, mode=None): def set_restart_mode(self, mode=None):
""" """
This manages the flag file that tells the runner if the server should This manages the flag file that tells the runner if the server should
be restarted or is shutting down. Valid modes are True/False and None. be restarted or is shutting down. Valid modes are True/False and None.
If mode is None, no change will be done to the flag file. If mode is None, no change will be done to the flag file.
""" """
if mode == None: if mode == None:
return return
f = open(PORTAL_RESTART, 'w') f = open(PORTAL_RESTART, 'w')
print _("writing mode=%(mode)s to %(portal_restart)s") % {'mode': mode, 'portal_restart': PORTAL_RESTART} print _("writing mode=%(mode)s to %(portal_restart)s") % {'mode': mode, 'portal_restart': PORTAL_RESTART}
f.write(str(mode)) f.write(str(mode))
@ -141,13 +141,13 @@ class Portal(object):
def shutdown(self, restart=None, _abrupt=False): def shutdown(self, restart=None, _abrupt=False):
""" """
Shuts down the server from inside it. Shuts down the server from inside it.
restart - True/False sets the flags so the server will be restart - True/False sets the flags so the server will be
restarted or not. If None, the current flag setting restarted or not. If None, the current flag setting
(set at initialization or previous runs) is used. (set at initialization or previous runs) is used.
_abrupt - this is set if server is stopped by a kill command, _abrupt - this is set if server is stopped by a kill command,
in which case the reactor is dead anyway. in which case the reactor is dead anyway.
Note that restarting (regardless of the setting) will not work Note that restarting (regardless of the setting) will not work
if the Portal is currently running in daemon mode. In that if the Portal is currently running in daemon mode. In that
@ -157,7 +157,7 @@ class Portal(object):
if not _abrupt: if not _abrupt:
reactor.callLater(0, reactor.stop) reactor.callLater(0, reactor.stop)
if os.name == 'nt' and os.path.exists(PORTAL_PIDFILE): if os.name == 'nt' and os.path.exists(PORTAL_PIDFILE):
# for Windows we need to remove pid files manually # for Windows we need to remove pid files manually
os.remove(PORTAL_PIDFILE) os.remove(PORTAL_PIDFILE)
#------------------------------------------------------------ #------------------------------------------------------------
@ -170,15 +170,15 @@ class Portal(object):
# what to execute from. # what to execute from.
application = service.Application('Portal') application = service.Application('Portal')
# The main Portal server program. This sets up the database # The main Portal server program. This sets up the database
# and is where we store all the other services. # and is where we store all the other services.
PORTAL = Portal(application) PORTAL = Portal(application)
if AMP_ENABLED: if AMP_ENABLED:
# The AMP protocol handles the communication between # The AMP protocol handles the communication between
# the portal and the mud server. Only reason to ever deactivate # the portal and the mud server. Only reason to ever deactivate
# it would be during testing and debugging. # it would be during testing and debugging.
from src.server import amp from src.server import amp
@ -188,7 +188,7 @@ if AMP_ENABLED:
PORTAL.services.addService(amp_client) PORTAL.services.addService(amp_client)
# We group all the various services under the same twisted app. # We group all the various services under the same twisted app.
# These will gradually be started as they are initialized below. # These will gradually be started as they are initialized below.
if TELNET_ENABLED: if TELNET_ENABLED:
@ -200,7 +200,7 @@ if TELNET_ENABLED:
ifacestr = "" ifacestr = ""
if interface != '0.0.0.0' or len(TELNET_INTERFACES) > 1: if interface != '0.0.0.0' or len(TELNET_INTERFACES) > 1:
ifacestr = "-%s" % interface ifacestr = "-%s" % interface
for port in TELNET_PORTS: for port in TELNET_PORTS:
pstring = "%s:%s" % (ifacestr, port) pstring = "%s:%s" % (ifacestr, port)
factory = protocol.ServerFactory() factory = protocol.ServerFactory()
factory.protocol = telnet.TelnetProtocol factory.protocol = telnet.TelnetProtocol
@ -219,7 +219,7 @@ if SSL_ENABLED:
ifacestr = "" ifacestr = ""
if interface != '0.0.0.0' or len(SSL_INTERFACES) > 1: if interface != '0.0.0.0' or len(SSL_INTERFACES) > 1:
ifacestr = "-%s" % interface ifacestr = "-%s" % interface
for port in SSL_PORTS: for port in SSL_PORTS:
pstring = "%s:%s" % (ifacestr, port) pstring = "%s:%s" % (ifacestr, port)
factory = protocol.ServerFactory() factory = protocol.ServerFactory()
factory.sessionhandler = PORTAL_SESSIONS factory.sessionhandler = PORTAL_SESSIONS
@ -231,7 +231,7 @@ if SSL_ENABLED:
if SSH_ENABLED: if SSH_ENABLED:
# Start SSH game connections. Will create a keypair in evennia/game if necessary. # Start SSH game connections. Will create a keypair in evennia/game if necessary.
from src.server import ssh from src.server import ssh
for interface in SSH_INTERFACES: for interface in SSH_INTERFACES:
@ -242,7 +242,7 @@ if SSH_ENABLED:
pstring = "%s:%s" % (ifacestr, port) pstring = "%s:%s" % (ifacestr, port)
factory = ssh.makeFactory({'protocolFactory':ssh.SshProtocol, factory = ssh.makeFactory({'protocolFactory':ssh.SshProtocol,
'protocolArgs':(), 'protocolArgs':(),
'sessions':PORTAL_SESSIONS}) 'sessions':PORTAL_SESSIONS})
ssh_service = internet.TCPServer(port, factory, interface=interface) ssh_service = internet.TCPServer(port, factory, interface=interface)
ssh_service.setName('EvenniaSSH%s' % pstring) ssh_service.setName('EvenniaSSH%s' % pstring)
PORTAL.services.addService(ssh_service) PORTAL.services.addService(ssh_service)
@ -254,14 +254,14 @@ if WEBSERVER_ENABLED:
from twisted.python import threadpool from twisted.python import threadpool
from src.server.webserver import DjangoWebRoot, WSGIWebServer from src.server.webserver import DjangoWebRoot, WSGIWebServer
# start a thread pool and define the root url (/) as a wsgi resource # start a thread pool and define the root url (/) as a wsgi resource
# recognized by Django # recognized by Django
threads = threadpool.ThreadPool() threads = threadpool.ThreadPool()
web_root = DjangoWebRoot(threads) web_root = DjangoWebRoot(threads)
# point our media resources to url /media # point our media resources to url /media
web_root.putChild("media", static.File(settings.MEDIA_ROOT)) web_root.putChild("media", static.File(settings.MEDIA_ROOT))
if WEBCLIENT_ENABLED: if WEBCLIENT_ENABLED:
# create ajax client processes at /webclientdata # create ajax client processes at /webclientdata
from src.server.webclient import WebClient from src.server.webclient import WebClient
webclient = WebClient() webclient = WebClient()

View file

@ -1,6 +1,6 @@
""" """
This module implements the main Evennia server process, the core of This module implements the main Evennia server process, the core of
the game engine. the game engine.
This module should be started with the 'twistd' executable since it This module should be started with the 'twistd' executable since it
sets up all the networking features. (this is done automatically sets up all the networking features. (this is done automatically
@ -19,7 +19,7 @@ if os.name == 'nt':
from twisted.application import internet, service from twisted.application import internet, service
from twisted.internet import protocol, reactor, defer from twisted.internet import protocol, reactor, defer
from twisted.web import server, static from twisted.web import server, static
import django import django
from django.db import connection from django.db import connection
from django.conf import settings from django.conf import settings
@ -38,20 +38,20 @@ if os.name == 'nt':
# a file with a flag telling the server to restart after shutdown or not. # a file with a flag telling the server to restart after shutdown or not.
SERVER_RESTART = os.path.join(settings.GAME_DIR, 'server.restart') SERVER_RESTART = os.path.join(settings.GAME_DIR, 'server.restart')
# module containing hook methods # module containing hook methods
SERVER_HOOK_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE) SERVER_HOOK_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE)
# i18n # i18n
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
#------------------------------------------------------------ #------------------------------------------------------------
# Evennia Server settings # Evennia Server settings
#------------------------------------------------------------ #------------------------------------------------------------
SERVERNAME = settings.SERVERNAME SERVERNAME = settings.SERVERNAME
VERSION = get_evennia_version() VERSION = get_evennia_version()
AMP_ENABLED = True AMP_ENABLED = True
AMP_HOST = settings.AMP_HOST AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT AMP_PORT = settings.AMP_PORT
@ -61,7 +61,7 @@ IRC_ENABLED = settings.IRC_ENABLED
RSS_ENABLED = settings.RSS_ENABLED RSS_ENABLED = settings.RSS_ENABLED
#------------------------------------------------------------ #------------------------------------------------------------
# Evennia Main Server object # Evennia Main Server object
#------------------------------------------------------------ #------------------------------------------------------------
class Evennia(object): class Evennia(object):
@ -69,15 +69,15 @@ class Evennia(object):
The main Evennia server handler. This object sets up the database and The main Evennia server handler. This object sets up the database and
tracks and interlinks all the twisted network services that make up tracks and interlinks all the twisted network services that make up
evennia. evennia.
""" """
def __init__(self, application): def __init__(self, application):
""" """
Setup the server. Setup the server.
application - an instantiated Twisted application application - an instantiated Twisted application
""" """
sys.path.append('.') sys.path.append('.')
# create a store of services # create a store of services
@ -85,44 +85,44 @@ class Evennia(object):
self.amp_protocol = None # set by amp factory self.amp_protocol = None # set by amp factory
self.sessions = SESSIONS self.sessions = SESSIONS
self.sessions.server = self self.sessions.server = self
print '\n' + '-'*50 print '\n' + '-'*50
# Database-specific startup optimizations. # Database-specific startup optimizations.
self.sqlite3_prep() self.sqlite3_prep()
# Run the initial setup if needed # Run the initial setup if needed
self.run_initial_setup() self.run_initial_setup()
self.start_time = time.time() self.start_time = time.time()
# initialize channelhandler # initialize channelhandler
channelhandler.CHANNELHANDLER.update() channelhandler.CHANNELHANDLER.update()
# Make info output to the terminal. # Make info output to the terminal.
self.terminal_output() self.terminal_output()
print '-'*50 print '-'*50
# set a callback if the server is killed abruptly, # set a callback if the server is killed abruptly,
# by Ctrl-C, reboot etc. # by Ctrl-C, reboot etc.
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True) reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True)
self.game_running = True self.game_running = True
self.run_init_hooks() self.run_init_hooks()
# Server startup methods # Server startup methods
def sqlite3_prep(self): def sqlite3_prep(self):
""" """
Optimize some SQLite stuff at startup since we Optimize some SQLite stuff at startup since we
can't save it to the database. can't save it to the database.
""" """
if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASE_ENGINE == "sqlite3") if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASE_ENGINE == "sqlite3")
or (hasattr(settings, 'DATABASES') or (hasattr(settings, 'DATABASES')
and settings.DATABASES.get("default", {}).get('ENGINE', None) and settings.DATABASES.get("default", {}).get('ENGINE', None)
== 'django.db.backends.sqlite3')): == 'django.db.backends.sqlite3')):
cursor = connection.cursor() cursor = connection.cursor()
cursor.execute("PRAGMA cache_size=10000") cursor.execute("PRAGMA cache_size=10000")
cursor.execute("PRAGMA synchronous=OFF") cursor.execute("PRAGMA synchronous=OFF")
@ -147,7 +147,7 @@ class Evennia(object):
# the last failed module. When all are finished, the step # the last failed module. When all are finished, the step
# is set to -1 to show it does not need to be run again. # is set to -1 to show it does not need to be run again.
print _(' Resuming initial setup from step %(last)s.' % \ print _(' Resuming initial setup from step %(last)s.' % \
{'last': last_initial_setup_step}) {'last': last_initial_setup_step})
initial_setup.handle_setup(int(last_initial_setup_step)) initial_setup.handle_setup(int(last_initial_setup_step))
print '-'*50 print '-'*50
@ -164,25 +164,25 @@ class Evennia(object):
# call server hook. # call server hook.
if SERVER_HOOK_MODULE: if SERVER_HOOK_MODULE:
SERVER_HOOK_MODULE.at_server_start() SERVER_HOOK_MODULE.at_server_start()
def terminal_output(self): def terminal_output(self):
""" """
Outputs server startup info to the terminal. Outputs server startup info to the terminal.
""" """
print _(' %(servername)s Server (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION} print _(' %(servername)s Server (%(version)s) started.') % {'servername': SERVERNAME, 'version': VERSION}
print ' amp (Portal): %s' % AMP_PORT print ' amp (Portal): %s' % AMP_PORT
def set_restart_mode(self, mode=None): def set_restart_mode(self, mode=None):
""" """
This manages the flag file that tells the runner if the server is This manages the flag file that tells the runner if the server is
reloading, resetting or shutting down. Valid modes are reloading, resetting or shutting down. Valid modes are
'reload', 'reset', 'shutdown' and None. 'reload', 'reset', 'shutdown' and None.
If mode is None, no change will be done to the flag file. If mode is None, no change will be done to the flag file.
Either way, the active restart setting (Restart=True/False) is Either way, the active restart setting (Restart=True/False) is
returned so the server knows which more it's in. returned so the server knows which more it's in.
""" """
if mode == None: if mode == None:
if os.path.exists(SERVER_RESTART) and 'True' == open(SERVER_RESTART, 'r').read(): if os.path.exists(SERVER_RESTART) and 'True' == open(SERVER_RESTART, 'r').read():
mode = 'reload' mode = 'reload'
@ -197,17 +197,17 @@ class Evennia(object):
def shutdown(self, mode=None, _abrupt=False): def shutdown(self, mode=None, _abrupt=False):
""" """
Shuts down the server from inside it. Shuts down the server from inside it.
mode - sets the server restart mode. mode - sets the server restart mode.
'reload' - server restarts, no "persistent" scripts are stopped, at_reload hooks called. 'reload' - server restarts, no "persistent" scripts are stopped, at_reload hooks called.
'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called. 'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called.
'shutdown' - like reset, but server will not auto-restart. 'shutdown' - like reset, but server will not auto-restart.
None - keep currently set flag from flag file. None - keep currently set flag from flag file.
_abrupt - this is set if server is stopped by a kill command, _abrupt - this is set if server is stopped by a kill command,
in which case the reactor is dead anyway. in which case the reactor is dead anyway.
""" """
mode = self.set_restart_mode(mode) mode = self.set_restart_mode(mode)
# call shutdown hooks on all cached objects # call shutdown hooks on all cached objects
@ -217,7 +217,7 @@ class Evennia(object):
if mode == 'reload': if mode == 'reload':
# call restart hooks # call restart hooks
[(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()] [(o.typeclass, o.at_server_reload()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()] [(p.typeclass, p.at_server_reload()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()] [(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
@ -226,23 +226,23 @@ class Evennia(object):
else: else:
if mode == 'reset': if mode == 'reset':
# don't call disconnect hooks on reset # don't call disconnect hooks on reset
[(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()] [(o.typeclass, o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
else: # shutdown else: # shutdown
[(o.typeclass, o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()] [(o.typeclass, o.at_disconnect(), o.at_server_shutdown()) for o in ObjectDB.get_all_cached_instances()]
[(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()] [(p.typeclass, p.at_server_shutdown()) for p in PlayerDB.get_all_cached_instances()]
[(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()] [(s.typeclass, s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
ServerConfig.objects.conf("server_restart_mode", "reset") ServerConfig.objects.conf("server_restart_mode", "reset")
if not _abrupt: if not _abrupt:
if SERVER_HOOK_MODULE: if SERVER_HOOK_MODULE:
SERVER_HOOK_MODULE.at_server_stop() SERVER_HOOK_MODULE.at_server_stop()
reactor.callLater(0, reactor.stop) reactor.callLater(0, reactor.stop)
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE): if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
# for Windows we need to remove pid files manually # for Windows we need to remove pid files manually
os.remove(SERVER_PIDFILE) os.remove(SERVER_PIDFILE)
#------------------------------------------------------------ #------------------------------------------------------------
# #
# Start the Evennia game server and add all active services # Start the Evennia game server and add all active services
@ -250,21 +250,21 @@ class Evennia(object):
#------------------------------------------------------------ #------------------------------------------------------------
# Tell the system the server is starting up; some things are not available yet # Tell the system the server is starting up; some things are not available yet
ServerConfig.objects.conf("server_starting_mode", True) ServerConfig.objects.conf("server_starting_mode", True)
# twistd requires us to define the variable 'application' so it knows # twistd requires us to define the variable 'application' so it knows
# what to execute from. # what to execute from.
application = service.Application('Evennia') application = service.Application('Evennia')
# The main evennia server program. This sets up the database # The main evennia server program. This sets up the database
# and is where we store all the other services. # and is where we store all the other services.
EVENNIA = Evennia(application) EVENNIA = Evennia(application)
# The AMP protocol handles the communication between # The AMP protocol handles the communication between
# the portal and the mud server. Only reason to ever deactivate # the portal and the mud server. Only reason to ever deactivate
# it would be during testing and debugging. # it would be during testing and debugging.
if AMP_ENABLED: if AMP_ENABLED:
from src.server import amp from src.server import amp
@ -278,7 +278,7 @@ if IRC_ENABLED:
# IRC channel connections # IRC channel connections
from src.comms import irc from src.comms import irc
irc.connect_all() irc.connect_all()
if IMC2_ENABLED: if IMC2_ENABLED:
@ -289,10 +289,10 @@ if IMC2_ENABLED:
imc2.connect_all() imc2.connect_all()
if RSS_ENABLED: if RSS_ENABLED:
# RSS feed channel connections # RSS feed channel connections
from src.comms import rss from src.comms import rss
rss.connect_all() rss.connect_all()
# clear server startup mode # clear server startup mode
ServerConfig.objects.conf("server_starting_mode", delete=True) ServerConfig.objects.conf("server_starting_mode", delete=True)

View file

@ -1,13 +1,13 @@
""" """
This defines a the Server's generic session object. This object represents This defines a the Server's generic session object. This object represents
a connection to the outside world but don't know any details about how the a connection to the outside world but don't know any details about how the
connection actually happens (so it's the same for telnet, web, ssh etc). connection actually happens (so it's the same for telnet, web, ssh etc).
It is stored on the Server side (as opposed to protocol-specific sessions which It is stored on the Server side (as opposed to protocol-specific sessions which
are stored on the Portal side) are stored on the Portal side)
""" """
import time import time
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from src.scripts.models import ScriptDB from src.scripts.models import ScriptDB
@ -16,7 +16,7 @@ from src.utils import logger, utils
from src.commands import cmdhandler, cmdsethandler from src.commands import cmdhandler, cmdsethandler
from src.server.session import Session from src.server.session import Session
IDLE_COMMAND = settings.IDLE_COMMAND IDLE_COMMAND = settings.IDLE_COMMAND
# load optional out-of-band function module # load optional out-of-band function module
OOB_FUNC_MODULE = settings.OOB_FUNC_MODULE OOB_FUNC_MODULE = settings.OOB_FUNC_MODULE
@ -33,14 +33,14 @@ from django.utils.translation import ugettext as _
class ServerSession(Session): class ServerSession(Session):
""" """
This class represents a player's session and is a template for This class represents a player's session and is a template for
individual protocols to communicate with Evennia. individual protocols to communicate with Evennia.
Each player gets a session assigned to them whenever they connect Each player gets a session assigned to them whenever they connect
to the game server. All communication between game and player goes to the game server. All communication between game and player goes
through their session. through their session.
""" """
def at_sync(self): def at_sync(self):
""" """
This is called whenever a session has been resynced with the portal. This is called whenever a session has been resynced with the portal.
@ -48,13 +48,13 @@ class ServerSession(Session):
been assigned (if applicable). been assigned (if applicable).
Since this is often called after a server restart we need to set up Since this is often called after a server restart we need to set up
the session as it was. the session as it was.
""" """
if not self.logged_in: if not self.logged_in:
# assign the unloggedin-command set. # assign the unloggedin-command set.
self.cmdset = cmdsethandler.CmdSetHandler(self) self.cmdset = cmdsethandler.CmdSetHandler(self)
self.cmdset_storage = [settings.CMDSET_UNLOGGEDIN] self.cmdset_storage = [settings.CMDSET_UNLOGGEDIN]
self.cmdset.update(init_mode=True) self.cmdset.update(init_mode=True)
self.cmdset.update(init_mode=True) self.cmdset.update(init_mode=True)
return return
@ -80,29 +80,29 @@ class ServerSession(Session):
self.uname = self.user.username self.uname = self.user.username
self.logged_in = True self.logged_in = True
self.conn_time = time.time() self.conn_time = time.time()
# Update account's last login time. # Update account's last login time.
self.user.last_login = datetime.now() self.user.last_login = datetime.now()
self.user.save() self.user.save()
# player init # player init
#print "at_init() - player" #print "at_init() - player"
player.at_init() player.at_init()
# Check if this is the first time the *player* logs in # Check if this is the first time the *player* logs in
if player.db.FIRST_LOGIN: if player.db.FIRST_LOGIN:
player.at_first_login() player.at_first_login()
del player.db.FIRST_LOGIN del player.db.FIRST_LOGIN
player.at_pre_login() player.at_pre_login()
character = player.character character = player.character
if character: if character:
# this player has a character. Check if it's the # this player has a character. Check if it's the
# first time *this character* logs in # first time *this character* logs in
character.at_init() character.at_init()
if character.db.FIRST_LOGIN: if character.db.FIRST_LOGIN:
character.at_first_login() character.at_first_login()
del character.db.FIRST_LOGIN del character.db.FIRST_LOGIN
# run character login hook # run character login hook
character.at_pre_login() character.at_pre_login()
@ -110,12 +110,12 @@ class ServerSession(Session):
# start (persistent) scripts on this object # start (persistent) scripts on this object
ScriptDB.objects.validate(obj=self.player.character) ScriptDB.objects.validate(obj=self.player.character)
#add session to connected list #add session to connected list
self.sessionhandler.login(self) self.sessionhandler.login(self)
# post-login hooks # post-login hooks
player.at_post_login() player.at_post_login()
if character: if character:
character.at_post_login() character.at_post_login()
@ -125,15 +125,15 @@ class ServerSession(Session):
accounting. This method is used also for non-loggedin accounting. This method is used also for non-loggedin
accounts. accounts.
""" """
if self.logged_in: if self.logged_in:
player = self.get_player() player = self.get_player()
character = self.get_character() character = self.get_character()
if character: if character:
character.at_disconnect() character.at_disconnect()
uaccount = player.user uaccount = player.user
uaccount.last_login = datetime.now() uaccount.last_login = datetime.now()
uaccount.save() uaccount.save()
self.logged_in = False self.logged_in = False
self.sessionhandler.disconnect(self) self.sessionhandler.disconnect(self)
def get_player(self): def get_player(self):
@ -143,8 +143,8 @@ class ServerSession(Session):
if self.logged_in: if self.logged_in:
return self.player return self.player
else: else:
return None return None
def get_character(self): def get_character(self):
""" """
Returns the in-game character associated with this session. Returns the in-game character associated with this session.
@ -153,12 +153,12 @@ class ServerSession(Session):
player = self.get_player() player = self.get_player()
if player: if player:
return player.character return player.character
return None return None
def log(self, message, channel=True): def log(self, message, channel=True):
""" """
Emits session info to the appropriate outputs and info channels. Emits session info to the appropriate outputs and info channels.
""" """
if channel: if channel:
try: try:
cchan = settings.CHANNEL_CONNECTINFO cchan = settings.CHANNEL_CONNECTINFO
@ -171,7 +171,7 @@ class ServerSession(Session):
def update_session_counters(self, idle=False): def update_session_counters(self, idle=False):
""" """
Hit this when the user enters a command in order to update idle timers Hit this when the user enters a command in order to update idle timers
and command counters. and command counters.
""" """
# Store the timestamp of the user's last command. # Store the timestamp of the user's last command.
self.cmd_last = time.time() self.cmd_last = time.time()
@ -187,24 +187,24 @@ class ServerSession(Session):
""" """
# handle the 'idle' command # handle the 'idle' command
if str(command_string).strip() == IDLE_COMMAND: if str(command_string).strip() == IDLE_COMMAND:
self.update_session_counters(idle=True) self.update_session_counters(idle=True)
return return
# all other inputs, including empty inputs # all other inputs, including empty inputs
character = self.get_character() character = self.get_character()
if character: if character:
character.execute_cmd(command_string) character.execute_cmd(command_string)
else: else:
if self.logged_in: if self.logged_in:
# there is no character, but we are logged in. Use player instead. # there is no character, but we are logged in. Use player instead.
self.get_player().execute_cmd(command_string) self.get_player().execute_cmd(command_string)
else: else:
# we are not logged in. Use the session directly # we are not logged in. Use the session directly
# (it uses the settings.UNLOGGEDIN cmdset) # (it uses the settings.UNLOGGEDIN cmdset)
cmdhandler.cmdhandler(self, command_string) cmdhandler.cmdhandler(self, command_string)
self.update_session_counters() self.update_session_counters()
def data_out(self, msg, data=None): def data_out(self, msg, data=None):
""" """
@ -218,25 +218,25 @@ class ServerSession(Session):
This receives out-of-band data from the Portal. This receives out-of-band data from the Portal.
This method parses the data input (a dict) and uses This method parses the data input (a dict) and uses
it to launch correct methods from those plugged into it to launch correct methods from those plugged into
the system. the system.
data = {funcname: ( [args], {kwargs]), data = {funcname: ( [args], {kwargs]),
funcname: ( [args], {kwargs}), ...} funcname: ( [args], {kwargs}), ...}
example: example:
data = {"get_hp": ([], {}), data = {"get_hp": ([], {}),
"update_counter", (["counter1"], {"now":True}) } "update_counter", (["counter1"], {"now":True}) }
""" """
print "server: " print "server: "
outdata = {} outdata = {}
entity = self.get_character() entity = self.get_character()
if not entity: if not entity:
entity = self.get_player() entity = self.get_player()
if not entity: if not entity:
entity = self entity = self
for funcname, argtuple in data.items(): for funcname, argtuple in data.items():
# loop through the data, calling available functions. # loop through the data, calling available functions.
@ -262,19 +262,19 @@ class ServerSession(Session):
def __eq__(self, other): def __eq__(self, other):
return self.address == other.address return self.address == other.address
def __str__(self): def __str__(self):
""" """
String representation of the user session class. We use String representation of the user session class. We use
this a lot in the server logs. this a lot in the server logs.
""" """
symbol = "" symbol = ""
if self.logged_in and hasattr(self, "player") and self.player: if self.logged_in and hasattr(self, "player") and self.player:
symbol = "(#%s)" % self.player.id symbol = "(#%s)" % self.player.id
try: try:
address = ":".join([str(part) for part in self.address]) address = ":".join([str(part) for part in self.address])
except Exception: except Exception:
address = self.address address = self.address
return "%s%s@%s" % (self.uname, symbol, address) return "%s%s@%s" % (self.uname, symbol, address)
def __unicode__(self): def __unicode__(self):
@ -298,7 +298,7 @@ class ServerSession(Session):
# Dummy API hooks for use a non-loggedin operation # Dummy API hooks for use a non-loggedin operation
def at_cmdset_get(self): def at_cmdset_get(self):
"dummy hook all objects with cmdsets need to have" "dummy hook all objects with cmdsets need to have"
pass pass
@ -306,11 +306,11 @@ class ServerSession(Session):
# Mock db/ndb properties for allowing easy storage on the session # Mock db/ndb properties for allowing easy storage on the session
# (note that no databse is involved at all here. session.db.attr = # (note that no databse is involved at all here. session.db.attr =
# value just saves a normal property in memory, just like ndb). # value just saves a normal property in memory, just like ndb).
#@property #@property
def ndb_get(self): def ndb_get(self):
""" """
A non-persistent store (ndb: NonDataBase). Everything stored A non-persistent store (ndb: NonDataBase). Everything stored
to this is guaranteed to be cleared when a server is shutdown. to this is guaranteed to be cleared when a server is shutdown.
Syntax is same as for the _get_db_holder() method and Syntax is same as for the _get_db_holder() method and
property, e.g. obj.ndb.attr = value etc. property, e.g. obj.ndb.attr = value etc.
@ -321,14 +321,14 @@ class ServerSession(Session):
class NdbHolder(object): class NdbHolder(object):
"Holder for storing non-persistent attributes." "Holder for storing non-persistent attributes."
def all(self): def all(self):
return [val for val in self.__dict__.keys() return [val for val in self.__dict__.keys()
if not val.startswith['_']] if not val.startswith['_']]
def __getattribute__(self, key): def __getattribute__(self, key):
# return None if no matching attribute was found. # return None if no matching attribute was found.
try: try:
return object.__getattribute__(self, key) return object.__getattribute__(self, key)
except AttributeError: except AttributeError:
return None return None
self._ndb_holder = NdbHolder() self._ndb_holder = NdbHolder()
return self._ndb_holder return self._ndb_holder
#@ndb.setter #@ndb.setter
@ -348,4 +348,4 @@ class ServerSession(Session):
# at this stage, so we just present a uniform API) # at this stage, so we just present a uniform API)
def access(self, *args, **kwargs): def access(self, *args, **kwargs):
"Dummy method." "Dummy method."
return True return True

View file

@ -1,10 +1,10 @@
""" """
This defines a generic session class. All connection instances (both This defines a generic session class. All connection instances (both
on Portal and Server side) should inherit from this class. on Portal and Server side) should inherit from this class.
""" """
import time import time
#------------------------------------------------------------ #------------------------------------------------------------
# Server Session # Server Session
@ -21,27 +21,27 @@ class Session(object):
protocols that Evennia supports, like Telnet, SSH etc. The Portal session protocols that Evennia supports, like Telnet, SSH etc. The Portal session
must call init_session() as part of its initialization. The respective must call init_session() as part of its initialization. The respective
hook methods should be connected to the methods unique for the respective hook methods should be connected to the methods unique for the respective
protocol so that there is a unified interface to Evennia. protocol so that there is a unified interface to Evennia.
2) A Server session. This is the same for all connected players, regardless 2) A Server session. This is the same for all connected players, regardless
of how they connect. of how they connect.
The Portal and Server have their own respective sessionhandlers. These are synced The Portal and Server have their own respective sessionhandlers. These are synced
whenever new connections happen or the Server restarts etc, which means much of the whenever new connections happen or the Server restarts etc, which means much of the
same information must be stored in both places e.g. the portal can re-sync with the same information must be stored in both places e.g. the portal can re-sync with the
server when the server reboots. server when the server reboots.
""" """
# names of attributes that should be affected by syncing. # names of attributes that should be affected by syncing.
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname', _attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding', 'logged_in', 'cid', 'encoding',
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total', 'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
'server_data'] 'server_data']
def init_session(self, protocol_key, address, sessionhandler): def init_session(self, protocol_key, address, sessionhandler):
""" """
Initialize the Session. This should be called by the protocol when Initialize the Session. This should be called by the protocol when
a new session is established. a new session is established.
protocol_key - telnet, ssh, ssl or web protocol_key - telnet, ssh, ssl or web
address - client address address - client address
sessionhandler - reference to the sessionhandler instance sessionhandler - reference to the sessionhandler instance
@ -50,24 +50,24 @@ class Session(object):
self.protocol_key = protocol_key self.protocol_key = protocol_key
# Protocol address tied to this session # Protocol address tied to this session
self.address = address self.address = address
# suid is used by some protocols, it's a hex key. # suid is used by some protocols, it's a hex key.
self.suid = None self.suid = None
# unique id for this session # unique id for this session
self.sessid = 0 # no sessid yet self.sessid = 0 # no sessid yet
# database id for the user connected to this session # database id for the user connected to this session
self.uid = None self.uid = None
# user name, for easier tracking of sessions # user name, for easier tracking of sessions
self.uname = None self.uname = None
# if user has authenticated already or not # if user has authenticated already or not
self.logged_in = False self.logged_in = False
# database id of character/object connected to this player session (if any) # database id of character/object connected to this player session (if any)
self.cid = None self.cid = None
self.encoding = "utf-8" self.encoding = "utf-8"
# session time statistics # session time statistics
self.conn_time = time.time() self.conn_time = time.time()
self.cmd_last_visible = self.conn_time self.cmd_last_visible = self.conn_time
self.cmd_last = self.conn_time self.cmd_last = self.conn_time
@ -76,10 +76,10 @@ class Session(object):
self.protocol_flags = {} self.protocol_flags = {}
self.server_data = {} self.server_data = {}
# a back-reference to the relevant sessionhandler this # a back-reference to the relevant sessionhandler this
# session is stored in. # session is stored in.
self.sessionhandler = sessionhandler self.sessionhandler = sessionhandler
def get_sync_data(self): def get_sync_data(self):
""" """
Return all data relevant to sync the session Return all data relevant to sync the session
@ -93,15 +93,15 @@ class Session(object):
""" """
Takes a session dictionary, as created by get_sync_data, Takes a session dictionary, as created by get_sync_data,
and loads it into the correct attributes of the session. and loads it into the correct attributes of the session.
""" """
for attrname, value in sessdata.items(): for attrname, value in sessdata.items():
self.__dict__[attrname] = value self.__dict__[attrname] = value
def at_sync(self): def at_sync(self):
""" """
Called after a session has been fully synced (including Called after a session has been fully synced (including
secondary operations such as setting self.player based secondary operations such as setting self.player based
on uid etc). on uid etc).
""" """
pass pass
@ -112,13 +112,13 @@ class Session(object):
generic hook called from the outside to disconnect this session generic hook called from the outside to disconnect this session
should be connected to the protocols actual disconnect mechanism. should be connected to the protocols actual disconnect mechanism.
""" """
pass pass
def data_out(self, msg, data=None): def data_out(self, msg, data=None):
""" """
generic hook for sending data out through the protocol. Server generic hook for sending data out through the protocol. Server
protocols can use this right away. Portal sessions protocols can use this right away. Portal sessions
should overload this to format/handle the outgoing data as needed. should overload this to format/handle the outgoing data as needed.
""" """
pass pass
@ -126,8 +126,8 @@ class Session(object):
""" """
hook for protocols to send incoming data to the engine. hook for protocols to send incoming data to the engine.
""" """
pass pass
def oob_data_out(self, data): def oob_data_out(self, data):
""" """
for Portal, this receives out-of-band data from Server across the AMP. for Portal, this receives out-of-band data from Server across the AMP.
@ -141,7 +141,7 @@ class Session(object):
""" """
for Portal, this sends out-of-band requests to Server over the AMP. for Portal, this sends out-of-band requests to Server over the AMP.
for Server, this receives data from Portal. for Server, this receives data from Portal.
data is a dictionary data is a dictionary
""" """
pass pass

View file

@ -1,33 +1,33 @@
""" """
This module defines handlers for storing sessions when handles This module defines handlers for storing sessions when handles
sessions of users connecting to the server. sessions of users connecting to the server.
There are two similar but separate stores of sessions: There are two similar but separate stores of sessions:
ServerSessionHandler - this stores generic game sessions ServerSessionHandler - this stores generic game sessions
for the game. These sessions has no knowledge about for the game. These sessions has no knowledge about
how they are connected to the world. how they are connected to the world.
PortalSessionHandler - this stores sessions created by PortalSessionHandler - this stores sessions created by
twisted protocols. These are dumb connectors that twisted protocols. These are dumb connectors that
handle network communication but holds no game info. handle network communication but holds no game info.
""" """
import time import time
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from src.server.models import ServerConfig from src.server.models import ServerConfig
from src.utils import utils from src.utils import utils
from src.commands.cmdhandler import CMD_LOGINSTART from src.commands.cmdhandler import CMD_LOGINSTART
# AMP signals # AMP signals
PCONN = chr(1) # portal session connect PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync SSYNC = chr(8) # server session sync
# i18n # i18n
@ -43,7 +43,7 @@ class SessionHandler(object):
""" """
def __init__(self): def __init__(self):
""" """
Init the handler. Init the handler.
""" """
self.sessions = {} self.sessions = {}
@ -64,13 +64,13 @@ class SessionHandler(object):
def get_all_sync_data(self): def get_all_sync_data(self):
""" """
Create a dictionary of sessdata dicts representing all Create a dictionary of sessdata dicts representing all
sessions in store. sessions in store.
""" """
sessdict = {} sessdict = {}
for sess in self.sessions.values(): for sess in self.sessions.values():
# copy all relevant data from all sessions # copy all relevant data from all sessions
sessdict[sess.sessid] = sess.get_sync_data() sessdict[sess.sessid] = sess.get_sync_data()
return sessdict return sessdict
#------------------------------------------------------------ #------------------------------------------------------------
@ -80,13 +80,13 @@ class SessionHandler(object):
class ServerSessionHandler(SessionHandler): class ServerSessionHandler(SessionHandler):
""" """
This object holds the stack of sessions active in the game at This object holds the stack of sessions active in the game at
any time. any time.
A session register with the handler in two steps, first by A session register with the handler in two steps, first by
registering itself with the connect() method. This indicates an registering itself with the connect() method. This indicates an
non-authenticated session. Whenever the session is authenticated non-authenticated session. Whenever the session is authenticated
the session together with the related player is sent to the login() the session together with the related player is sent to the login()
method. method.
""" """
@ -94,15 +94,15 @@ class ServerSessionHandler(SessionHandler):
def __init__(self): def __init__(self):
""" """
Init the handler. Init the handler.
""" """
self.sessions = {} self.sessions = {}
self.server = None self.server = None
self.server_data = {"servername":settings.SERVERNAME} self.server_data = {"servername":settings.SERVERNAME}
def portal_connect(self, sessid, session): def portal_connect(self, sessid, session):
""" """
Called by Portal when a new session has connected. Called by Portal when a new session has connected.
Creates a new, unlogged-in game session. Creates a new, unlogged-in game session.
""" """
self.sessions[sessid] = session self.sessions[sessid] = session
@ -122,10 +122,10 @@ class ServerSessionHandler(SessionHandler):
""" """
Syncing all session ids of the portal with the ones of the server. This is instantiated Syncing all session ids of the portal with the ones of the server. This is instantiated
by the portal when reconnecting. by the portal when reconnecting.
sesslist is a complete list of (sessid, session) pairs, matching the list on the portal. sesslist is a complete list of (sessid, session) pairs, matching the list on the portal.
if session was logged in, the amp handler will have logged them in before this point. if session was logged in, the amp handler will have logged them in before this point.
""" """
for sess in self.sessions.values(): for sess in self.sessions.values():
# we delete the old session to make sure to catch eventual lingering references. # we delete the old session to make sure to catch eventual lingering references.
del sess del sess
@ -136,15 +136,15 @@ class ServerSessionHandler(SessionHandler):
def portal_shutdown(self): def portal_shutdown(self):
""" """
Called by server when shutting down the portal. Called by server when shutting down the portal.
""" """
self.server.amp_protocol.call_remote_PortalAdmin(0, self.server.amp_protocol.call_remote_PortalAdmin(0,
operation=SSHUTD, operation=SSHUTD,
data="") data="")
# server-side access methods # server-side access methods
def disconnect(self, session, reason=""): def disconnect(self, session, reason=""):
""" """
Called from server side to remove session and inform portal Called from server side to remove session and inform portal
of this fact. of this fact.
""" """
session = self.sessions.get(session.sessid, None) session = self.sessions.get(session.sessid, None)
@ -157,7 +157,7 @@ class ServerSessionHandler(SessionHandler):
data=reason) data=reason)
self.session_count(-1) self.session_count(-1)
def login(self, session): def login(self, session):
""" """
Log in the previously unloggedin session and the player we by Log in the previously unloggedin session and the player we by
@ -165,22 +165,22 @@ class ServerSessionHandler(SessionHandler):
assume the session to be logged in one way or another. assume the session to be logged in one way or another.
""" """
# prep the session with player/user info # prep the session with player/user info
if not ALLOW_MULTISESSION: if not ALLOW_MULTISESSION:
# disconnect previous sessions. # disconnect previous sessions.
self.disconnect_duplicate_sessions(session) self.disconnect_duplicate_sessions(session)
session.logged_in = True session.logged_in = True
self.session_count(1) self.session_count(1)
# sync the portal to this session # sync the portal to this session
sessdata = session.get_sync_data() sessdata = session.get_sync_data()
self.server.amp_protocol.call_remote_PortalAdmin(session.sessid, self.server.amp_protocol.call_remote_PortalAdmin(session.sessid,
operation=SLOGIN, operation=SLOGIN,
data=sessdata) data=sessdata)
def session_sync(self): def session_sync(self):
""" """
This is called by the server when it reboots. It syncs all session data This is called by the server when it reboots. It syncs all session data
to the portal. to the portal.
""" """
sessdata = self.get_all_sync_data() sessdata = self.get_all_sync_data()
self.server.amp_protocol.call_remote_PortalAdmin(0, self.server.amp_protocol.call_remote_PortalAdmin(0,
@ -192,7 +192,7 @@ class ServerSessionHandler(SessionHandler):
""" """
Cleanly disconnect all of the connected sessions. Cleanly disconnect all of the connected sessions.
""" """
for session in self.sessions: for session in self.sessions:
del session del session
self.session_count(0) self.session_count(0)
@ -203,42 +203,42 @@ class ServerSessionHandler(SessionHandler):
def disconnect_duplicate_sessions(self, curr_session, reason = _("Logged in from elsewhere. Disconnecting.") ): def disconnect_duplicate_sessions(self, curr_session, reason = _("Logged in from elsewhere. Disconnecting.") ):
""" """
Disconnects any existing sessions with the same game object. Disconnects any existing sessions with the same game object.
""" """
curr_char = curr_session.get_character() curr_char = curr_session.get_character()
doublet_sessions = [sess for sess in self.sessions doublet_sessions = [sess for sess in self.sessions
if sess.logged_in if sess.logged_in
and sess.get_character() == curr_char and sess.get_character() == curr_char
and sess != curr_session] and sess != curr_session]
for sessid in doublet_sessions: for sessid in doublet_sessions:
self.disconnect(session, reason) self.disconnect(session, reason)
self.session_count(-1) self.session_count(-1)
def validate_sessions(self): def validate_sessions(self):
""" """
Check all currently connected sessions (logged in and not) Check all currently connected sessions (logged in and not)
and see if any are dead. and see if any are dead.
""" """
tcurr = time.time() tcurr = time.time()
reason= _("Idle timeout exceeded, disconnecting.") reason= _("Idle timeout exceeded, disconnecting.")
for session in (session for session in self.sessions.values() for session in (session for session in self.sessions.values()
if session.logged_in and IDLE_TIMEOUT > 0 if session.logged_in and IDLE_TIMEOUT > 0
and (tcurr - session.cmd_last) > IDLE_TIMEOUT): and (tcurr - session.cmd_last) > IDLE_TIMEOUT):
self.disconnect(session, reason=reason) self.disconnect(session, reason=reason)
self.session_count(-1) self.session_count(-1)
def session_count(self, num=None): def session_count(self, num=None):
""" """
Count up/down the number of connected, authenticated users. Count up/down the number of connected, authenticated users.
If num is None, the current number of sessions is returned. If num is None, the current number of sessions is returned.
num can be a positive or negative value to be added to the current count. num can be a positive or negative value to be added to the current count.
If 0, the counter will be reset to 0. If 0, the counter will be reset to 0.
""" """
if num == None: if num == None:
# show the current value. This also syncs it. # show the current value. This also syncs it.
return int(ServerConfig.objects.conf('nr_sessions', default=0)) return int(ServerConfig.objects.conf('nr_sessions', default=0))
elif num == 0: elif num == 0:
# reset value to 0 # reset value to 0
ServerConfig.objects.conf('nr_sessions', 0) ServerConfig.objects.conf('nr_sessions', 0)
@ -255,7 +255,7 @@ class ServerSessionHandler(SessionHandler):
Only logged-in players are counted here. Only logged-in players are counted here.
""" """
return len(set(session.uid for session in self.sessions.values() if session.logged_in)) return len(set(session.uid for session in self.sessions.values() if session.logged_in))
def sessions_from_player(self, player): def sessions_from_player(self, player):
""" """
Given a player, return any matching sessions. Given a player, return any matching sessions.
@ -275,7 +275,7 @@ class ServerSessionHandler(SessionHandler):
player = character.player player = character.player
if player: if player:
return self.sessions_from_player(player) return self.sessions_from_player(player)
return None return None
def announce_all(self, message): def announce_all(self, message):
@ -295,15 +295,15 @@ class ServerSessionHandler(SessionHandler):
def data_in(self, sessid, string="", data=""): def data_in(self, sessid, string="", data=""):
""" """
Data Portal -> Server Data Portal -> Server
""" """
session = self.sessions.get(sessid, None) session = self.sessions.get(sessid, None)
if session: if session:
session.execute_cmd(string) session.execute_cmd(string)
# ignore 'data' argument for now; this is otherwise the place # ignore 'data' argument for now; this is otherwise the place
# to put custom effects on the server due to data input, e.g. # to put custom effects on the server due to data input, e.g.
# from a custom client. # from a custom client.
def oob_data_in(self, sessid, data): def oob_data_in(self, sessid, data):
""" """
OOB (Out-of-band) Data Portal -> Server OOB (Out-of-band) Data Portal -> Server
@ -327,11 +327,11 @@ class PortalSessionHandler(SessionHandler):
""" """
This object holds the sessions connected to the portal at any time. This object holds the sessions connected to the portal at any time.
It is synced with the server's equivalent SessionHandler over the AMP It is synced with the server's equivalent SessionHandler over the AMP
connection. connection.
Sessions register with the handler using the connect() method. This Sessions register with the handler using the connect() method. This
will assign a new unique sessionid to the session and send that sessid will assign a new unique sessionid to the session and send that sessid
to the server using the AMP connection. to the server using the AMP connection.
""" """
@ -339,7 +339,7 @@ class PortalSessionHandler(SessionHandler):
""" """
Init the handler Init the handler
""" """
self.portal = None self.portal = None
self.sessions = {} self.sessions = {}
self.latest_sessid = 0 self.latest_sessid = 0
self.uptime = time.time() self.uptime = time.time()
@ -351,19 +351,19 @@ class PortalSessionHandler(SessionHandler):
Server. At this point, the AMP connection is already Server. At this point, the AMP connection is already
established. established.
""" """
self.connection_time = time.time() self.connection_time = time.time()
def connect(self, session): def connect(self, session):
""" """
Called by protocol at first connect. This adds a not-yet authenticated session Called by protocol at first connect. This adds a not-yet authenticated session
using an ever-increasing counter for sessid. using an ever-increasing counter for sessid.
""" """
self.latest_sessid += 1 self.latest_sessid += 1
sessid = self.latest_sessid sessid = self.latest_sessid
session.sessid = sessid session.sessid = sessid
sessdata = session.get_sync_data() sessdata = session.get_sync_data()
self.sessions[sessid] = session self.sessions[sessid] = session
# sync with server-side # sync with server-side
self.portal.amp_protocol.call_remote_ServerAdmin(sessid, self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
operation=PCONN, operation=PCONN,
data=sessdata) data=sessdata)
@ -374,23 +374,23 @@ class PortalSessionHandler(SessionHandler):
sessid = session.sessid sessid = session.sessid
self.portal.amp_protocol.call_remote_ServerAdmin(sessid, self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
operation=PDISCONN) operation=PDISCONN)
def server_disconnect(self, sessid, reason=""): def server_disconnect(self, sessid, reason=""):
""" """
Called by server to force a disconnect by sessid Called by server to force a disconnect by sessid
""" """
session = self.sessions.get(sessid, None) session = self.sessions.get(sessid, None)
if session: if session:
session.disconnect(reason) session.disconnect(reason)
del session del session
def server_disconnect_all(self, reason=""): def server_disconnect_all(self, reason=""):
""" """
Called by server when forcing a clean disconnect for everyone. Called by server when forcing a clean disconnect for everyone.
""" """
for session in self.sessions.values(): for session in self.sessions.values():
session.disconnect(reason) session.disconnect(reason)
del session del session
def count_loggedin(self, include_unloggedin=False): def count_loggedin(self, include_unloggedin=False):
@ -398,22 +398,22 @@ class PortalSessionHandler(SessionHandler):
Count loggedin connections, alternatively count all connections. Count loggedin connections, alternatively count all connections.
""" """
return len(self.get_sessions(include_unloggedin=include_unloggedin)) return len(self.get_sessions(include_unloggedin=include_unloggedin))
def session_from_suid(self, suid): def session_from_suid(self, suid):
""" """
Given a session id, retrieve the session (this is primarily Given a session id, retrieve the session (this is primarily
intended to be called by web clients) intended to be called by web clients)
""" """
return [sess for sess in self.get_sessions(include_unloggedin=True) return [sess for sess in self.get_sessions(include_unloggedin=True)
if hasattr(sess, 'suid') and sess.suid == suid] if hasattr(sess, 'suid') and sess.suid == suid]
def data_in(self, session, string="", data=""): def data_in(self, session, string="", data=""):
""" """
Called by portal sessions for relaying data coming Called by portal sessions for relaying data coming
in from the protocol to the server. data is in from the protocol to the server. data is
serialized before passed on. serialized before passed on.
""" """
self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid, self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid,
msg=string, msg=string,
data=data) data=data)
@ -426,12 +426,12 @@ class PortalSessionHandler(SessionHandler):
def data_out(self, sessid, string="", data=""): def data_out(self, sessid, string="", data=""):
""" """
Called by server for having the portal relay messages and data Called by server for having the portal relay messages and data
to the correct session protocol. to the correct session protocol.
""" """
session = self.sessions.get(sessid, None) session = self.sessions.get(sessid, None)
if session: if session:
session.data_out(string, data=data) session.data_out(string, data=data)
def oob_data_in(self, session, data): def oob_data_in(self, session, data):
""" """

View file

@ -6,7 +6,7 @@ This depends on a generic session module that implements
the actual login procedure of the game, tracks the actual login procedure of the game, tracks
sessions etc. sessions etc.
Using standard ssh client, Using standard ssh client,
""" """
import os import os
@ -65,11 +65,11 @@ class SshProtocol(Manhole, session.Session):
# initialize the session # initialize the session
client_address = self.getClientAddress() client_address = self.getClientAddress()
self.init_session("ssh", client_address, self.cfactory.sessionhandler) self.init_session("ssh", client_address, self.cfactory.sessionhandler)
# since we might have authenticated already, we might set this here. # since we might have authenticated already, we might set this here.
if self.authenticated_player: if self.authenticated_player:
self.logged_in = True self.logged_in = True
self.uid = self.authenticated_player.user.id self.uid = self.authenticated_player.user.id
self.sessionhandler.connect(self) self.sessionhandler.connect(self)
@ -82,8 +82,8 @@ class SshProtocol(Manhole, session.Session):
self.keyHandlers[CTRL_C] = self.handle_INT self.keyHandlers[CTRL_C] = self.handle_INT
self.keyHandlers[CTRL_D] = self.handle_EOF self.keyHandlers[CTRL_D] = self.handle_EOF
self.keyHandlers[CTRL_L] = self.handle_FF self.keyHandlers[CTRL_L] = self.handle_FF
self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
# initalize # initalize
def handle_INT(self): def handle_INT(self):
@ -128,8 +128,8 @@ class SshProtocol(Manhole, session.Session):
def connectionLost(self, reason=None): def connectionLost(self, reason=None):
""" """
This is executed when the connection is lost for This is executed when the connection is lost for
whatever reason. It can also be called directly, whatever reason. It can also be called directly,
from the disconnect method. from the disconnect method.
""" """
insults.TerminalProtocol.connectionLost(self, reason) insults.TerminalProtocol.connectionLost(self, reason)
@ -186,7 +186,7 @@ class SshProtocol(Manhole, session.Session):
self.lineSend(str(e)) self.lineSend(str(e))
return return
nomarkup = False nomarkup = False
raw = False raw = False
if type(data) == dict: if type(data) == dict:
# check if we want escape codes to go through unparsed. # check if we want escape codes to go through unparsed.
raw = data.get("raw", False) raw = data.get("raw", False)
@ -230,7 +230,7 @@ class PlayerDBPasswordChecker(object):
username = up.username username = up.username
password = up.password password = up.password
player = PlayerDB.objects.get_player_from_name(username) player = PlayerDB.objects.get_player_from_name(username)
res = (None, self.factory) res = (None, self.factory)
if player and player.user.check_password(password): if player and player.user.check_password(password):
res = (player, self.factory) res = (player, self.factory)
return defer.succeed(res) return defer.succeed(res)
@ -290,13 +290,13 @@ class TerminalSessionTransport_getPeer:
def getKeyPair(pubkeyfile, privkeyfile): def getKeyPair(pubkeyfile, privkeyfile):
""" """
This function looks for RSA keypair files in the current directory. If they This function looks for RSA keypair files in the current directory. If they
do not exist, the keypair is created. do not exist, the keypair is created.
""" """
if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)): if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)):
# No keypair exists. Generate a new RSA keypair # No keypair exists. Generate a new RSA keypair
print _(" Generating SSH RSA keypair ..."), print _(" Generating SSH RSA keypair ..."),
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
KEY_LENGTH = 1024 KEY_LENGTH = 1024
rsaKey = Key(RSA.generate(KEY_LENGTH)) rsaKey = Key(RSA.generate(KEY_LENGTH))

View file

@ -1,6 +1,6 @@
""" """
This is a simple context factory for auto-creating This is a simple context factory for auto-creating
SSL keys and certificates. SSL keys and certificates.
""" """
import os, sys import os, sys
@ -16,21 +16,21 @@ from src.server.telnet import TelnetProtocol
class SSLProtocol(TelnetProtocol): class SSLProtocol(TelnetProtocol):
""" """
Communication is the same as telnet, except data transfer Communication is the same as telnet, except data transfer
is done with encryption. is done with encryption.
""" """
pass pass
def verify_SSL_key_and_cert(keyfile, certfile): def verify_SSL_key_and_cert(keyfile, certfile):
""" """
This function looks for RSA key and certificate in the current This function looks for RSA key and certificate in the current
directory. If files ssl.key and ssl.cert does not exist, they directory. If files ssl.key and ssl.cert does not exist, they
are created. are created.
""" """
if not (os.path.exists(keyfile) and os.path.exists(certfile)): if not (os.path.exists(keyfile) and os.path.exists(certfile)):
# key/cert does not exist. Create. # key/cert does not exist. Create.
import subprocess import subprocess
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from twisted.conch.ssh.keys import Key from twisted.conch.ssh.keys import Key
print _(" Creating SSL key and certificate ... "), print _(" Creating SSL key and certificate ... "),
@ -39,16 +39,16 @@ def verify_SSL_key_and_cert(keyfile, certfile):
# create the RSA key and store it. # create the RSA key and store it.
KEY_LENGTH = 1024 KEY_LENGTH = 1024
rsaKey = Key(RSA.generate(KEY_LENGTH)) rsaKey = Key(RSA.generate(KEY_LENGTH))
keyString = rsaKey.toString(type="OPENSSH") keyString = rsaKey.toString(type="OPENSSH")
file(keyfile, 'w+b').write(keyString) file(keyfile, 'w+b').write(keyString)
except Exception,e: except Exception,e:
print _("rsaKey error: %(e)s\n WARNING: Evennia could not auto-generate SSL private key.") % {'e': e} print _("rsaKey error: %(e)s\n WARNING: Evennia could not auto-generate SSL private key.") % {'e': e}
print _("If this error persists, create game/%(keyfile)s yourself using third-party tools.") % {'keyfile': keyfile} print _("If this error persists, create game/%(keyfile)s yourself using third-party tools.") % {'keyfile': keyfile}
sys.exit(5) sys.exit(5)
# try to create the certificate # try to create the certificate
CERT_EXPIRE = 365 * 20 # twenty years validity CERT_EXPIRE = 365 * 20 # twenty years validity
# default: # default:
#openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300 #openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE) exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
#print "exestring:", exestring #print "exestring:", exestring
@ -58,9 +58,9 @@ def verify_SSL_key_and_cert(keyfile, certfile):
print " %s\n" % e print " %s\n" % e
print _(" Evennia's SSL context factory could not automatically create an SSL certificate game/%(cert)s.") % {'cert': certfile} print _(" Evennia's SSL context factory could not automatically create an SSL certificate game/%(cert)s.") % {'cert': certfile}
print _(" A private key 'ssl.key' was already created. Please create %(cert)s manually using the commands valid") % {'cert': certfile} print _(" A private key 'ssl.key' was already created. Please create %(cert)s manually using the commands valid") % {'cert': certfile}
print _(" for your operating system.") print _(" for your operating system.")
print _(" Example (linux, using the openssl program): ") print _(" Example (linux, using the openssl program): ")
print " %s" % exestring print " %s" % exestring
sys.exit(5) sys.exit(5)
print "done." print "done."

View file

@ -2,8 +2,8 @@
This module implements the telnet protocol. This module implements the telnet protocol.
This depends on a generic session module that implements This depends on a generic session module that implements
the actual login procedure of the game, tracks the actual login procedure of the game, tracks
sessions etc. sessions etc.
""" """
@ -11,44 +11,44 @@ from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE,
from src.server.session import Session from src.server.session import Session
from src.server import ttype, mssp from src.server import ttype, mssp
from src.server.mccp import Mccp, mccp_compress, MCCP from src.server.mccp import Mccp, mccp_compress, MCCP
from src.utils import utils, ansi from src.utils import utils, ansi
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
""" """
Each player connecting over telnet (ie using most traditional mud Each player connecting over telnet (ie using most traditional mud
clients) gets a telnet protocol instance assigned to them. All clients) gets a telnet protocol instance assigned to them. All
communication between game and player goes through here. communication between game and player goes through here.
""" """
def connectionMade(self): def connectionMade(self):
""" """
This is called when the connection is first This is called when the connection is first
established. established.
""" """
# initialize the session # initialize the session
client_address = self.transport.client client_address = self.transport.client
self.init_session("telnet", client_address, self.factory.sessionhandler) self.init_session("telnet", client_address, self.factory.sessionhandler)
# negotiate mccp (data compression) # negotiate mccp (data compression)
self.mccp = Mccp(self) self.mccp = Mccp(self)
# negotiate ttype (client info) # negotiate ttype (client info)
self.ttype = ttype.Ttype(self) self.ttype = ttype.Ttype(self)
# negotiate mssp (crawler communication) # negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self) self.mssp = mssp.Mssp(self)
# add this new connection to sessionhandler so
# the Server becomes aware of it.
self.sessionhandler.connect(self)
def enableRemote(self, option): # add this new connection to sessionhandler so
# the Server becomes aware of it.
self.sessionhandler.connect(self)
def enableRemote(self, option):
""" """
This sets up the options we allow for this protocol. This sets up the options we allow for this protocol.
""" """
return (option == LINEMODE or return (option == LINEMODE or
option == ttype.TTYPE or option == ttype.TTYPE or
option == MCCP or option == MCCP or
option == mssp.MSSP) option == mssp.MSSP)
def enableLocal(self, option): def enableLocal(self, option):
@ -60,18 +60,18 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def disableLocal(self, option): def disableLocal(self, option):
if option == MCCP: if option == MCCP:
self.mccp.no_mccp(option) self.mccp.no_mccp(option)
return True return True
else: else:
return super(TelnetProtocol, self).disableLocal(option) return super(TelnetProtocol, self).disableLocal(option)
def connectionLost(self, reason): def connectionLost(self, reason):
""" """
This is executed when the connection is lost for This is executed when the connection is lost for
whatever reason. It can also be called directly, from whatever reason. It can also be called directly, from
the disconnect method the disconnect method
""" """
self.sessionhandler.disconnect(self) self.sessionhandler.disconnect(self)
self.transport.loseConnection() self.transport.loseConnection()
def dataReceived(self, data): def dataReceived(self, data):
@ -83,7 +83,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# print "dataRcv:", data, # print "dataRcv:", data,
# try: # try:
# for b in data: # for b in data:
# print ord(b), # print ord(b),
# print "" # print ""
# except Exception, e: # except Exception, e:
# print str(e) + ":", str(data) # print str(e) + ":", str(data)
@ -91,19 +91,19 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if data and data[0] == IAC: if data and data[0] == IAC:
try: try:
super(TelnetProtocol, self).dataReceived(data) super(TelnetProtocol, self).dataReceived(data)
return return
except Exception: except Exception:
pass pass
StatefulTelnetProtocol.dataReceived(self, data) StatefulTelnetProtocol.dataReceived(self, data)
def _write(self, data): def _write(self, data):
"hook overloading the one used in plain telnet" "hook overloading the one used in plain telnet"
#print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in data)) #print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in data))
data = data.replace('\n', '\r\n') data = data.replace('\n', '\r\n')
super(TelnetProtocol, self)._write(mccp_compress(self, data)) super(TelnetProtocol, self)._write(mccp_compress(self, data))
def sendLine(self, line): def sendLine(self, line):
"hook overloading the one used by linereceiver" "hook overloading the one used by linereceiver"
#print "sendLine (%s):\n%s" % (self.state, line) #print "sendLine (%s):\n%s" % (self.state, line)
#escape IAC in line mode, and correctly add \r\n #escape IAC in line mode, and correctly add \r\n
line += self.delimiter line += self.delimiter
@ -112,17 +112,17 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def lineReceived(self, string): def lineReceived(self, string):
""" """
Telnet method called when data is coming in over the telnet Telnet method called when data is coming in over the telnet
connection. We pass it on to the game engine directly. connection. We pass it on to the game engine directly.
""" """
self.sessionhandler.data_in(self, string) self.sessionhandler.data_in(self, string)
# Session hooks
# Session hooks
def disconnect(self, reason=None): def disconnect(self, reason=None):
""" """
generic hook for the engine to call in order to generic hook for the engine to call in order to
disconnect this protocol. disconnect this protocol.
""" """
if reason: if reason:
@ -131,25 +131,25 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def data_out(self, string, data=None): def data_out(self, string, data=None):
""" """
generic hook method for engine to call in order to send data generic hook method for engine to call in order to send data
through the telnet connection. through the telnet connection.
Data Evennia -> Player. Data Evennia -> Player.
data argument may contain a dict with output flags. data argument may contain a dict with output flags.
""" """
try: try:
string = utils.to_str(string, encoding=self.encoding) string = utils.to_str(string, encoding=self.encoding)
except Exception, e: except Exception, e:
self.sendLine(str(e)) self.sendLine(str(e))
return return
ttype = self.protocol_flags.get('TTYPE', {}) ttype = self.protocol_flags.get('TTYPE', {})
nomarkup = not (ttype.get('256 COLORS') or ttype.get('ANSI') or not ttype.get("init_done")) nomarkup = not (ttype.get('256 COLORS') or ttype.get('ANSI') or not ttype.get("init_done"))
raw = False raw = False
if type(data) == dict: if type(data) == dict:
# check if we want escape codes to go through unparsed. # check if we want escape codes to go through unparsed.
raw = data.get("raw", False) raw = data.get("raw", False)
# check if we want to remove all markup (TTYPE override) # check if we want to remove all markup (TTYPE override)
nomarkup = data.get("nomarkup", False) nomarkup = data.get("nomarkup", False)
if raw: if raw:
self.sendLine(string) self.sendLine(string)
else: else:
self.sendLine(ansi.parse_ansi(string, strip_ansi=nomarkup, xterm256=ttype.get('256 COLORS'))) self.sendLine(ansi.parse_ansi(string, strip_ansi=nomarkup, xterm256=ttype.get('256 COLORS')))

View file

@ -17,18 +17,18 @@ IS = chr(0)
SEND = chr(1) SEND = chr(1)
# terminal capabilities and their codes # terminal capabilities and their codes
MTTS = [(128,'PROXY'), MTTS = [(128,'PROXY'),
(64, 'SCREEN READER'), (64, 'SCREEN READER'),
(32, 'OSC COLOR PALETTE'), (32, 'OSC COLOR PALETTE'),
(16, 'MOUSE TRACKING'), (16, 'MOUSE TRACKING'),
(8, '256 COLORS'), (8, '256 COLORS'),
(4, 'UTF-8'), (4, 'UTF-8'),
(2, 'VT100'), (2, 'VT100'),
(1, 'ANSI')] (1, 'ANSI')]
class Ttype(object): class Ttype(object):
""" """
Handles ttype negotiations. Called and initiated by the Handles ttype negotiations. Called and initiated by the
telnet protocol. telnet protocol.
""" """
def __init__(self, protocol): def __init__(self, protocol):
@ -39,26 +39,26 @@ class Ttype(object):
the ttype_step indicates how far in the data retrieval we've the ttype_step indicates how far in the data retrieval we've
gotten. gotten.
""" """
self.ttype_step = 0 self.ttype_step = 0
self.protocol = protocol self.protocol = protocol
self.protocol.protocol_flags['TTYPE'] = {"init_done":False} self.protocol.protocol_flags['TTYPE'] = {"init_done":False}
# setup protocol to handle ttype initialization and negotiation # setup protocol to handle ttype initialization and negotiation
self.protocol.negotiationMap[TTYPE] = self.do_ttype self.protocol.negotiationMap[TTYPE] = self.do_ttype
# ask if client will ttype, connect callback if it does. # ask if client will ttype, connect callback if it does.
self.protocol.will(TTYPE).addCallbacks(self.do_ttype, self.no_ttype) self.protocol.will(TTYPE).addCallbacks(self.do_ttype, self.no_ttype)
def no_ttype(self, option): def no_ttype(self, option):
""" """
Callback if ttype is not supported by client. Callback if ttype is not supported by client.
""" """
self.protocol.protocol_flags['TTYPE'] = False self.protocol.protocol_flags['TTYPE'] = False
def do_ttype(self, option): def do_ttype(self, option):
""" """
Handles negotiation of the ttype protocol once the Handles negotiation of the ttype protocol once the
client has confirmed that it supports the ttype client has confirmed that it supports the ttype
protocol. protocol.
The negotiation proceeds in several steps, each returning a The negotiation proceeds in several steps, each returning a
certain piece of information about the client. All data is certain piece of information about the client. All data is
@ -66,15 +66,15 @@ class Ttype(object):
""" """
if self.protocol.protocol_flags['TTYPE']['init_done']: if self.protocol.protocol_flags['TTYPE']['init_done']:
return return
self.ttype_step += 1 self.ttype_step += 1
if self.ttype_step == 1: if self.ttype_step == 1:
# set up info storage and initialize subnegotiation # set up info storage and initialize subnegotiation
self.protocol.requestNegotiation(TTYPE, SEND) self.protocol.requestNegotiation(TTYPE, SEND)
else: else:
# receive data # receive data
option = "".join(option).lstrip(IS) option = "".join(option).lstrip(IS)
if self.ttype_step == 2: if self.ttype_step == 2:
self.protocol.protocol_flags['TTYPE']['CLIENTNAME'] = option self.protocol.protocol_flags['TTYPE']['CLIENTNAME'] = option
@ -82,17 +82,16 @@ class Ttype(object):
elif self.ttype_step == 3: elif self.ttype_step == 3:
self.protocol.protocol_flags['TTYPE']['TERM'] = option self.protocol.protocol_flags['TTYPE']['TERM'] = option
self.protocol.requestNegotiation(TTYPE, SEND) self.protocol.requestNegotiation(TTYPE, SEND)
elif self.ttype_step == 4: elif self.ttype_step == 4:
option = int(option.strip('MTTS ')) option = int(option.strip('MTTS '))
self.protocol.protocol_flags['TTYPE']['MTTS'] = option self.protocol.protocol_flags['TTYPE']['MTTS'] = option
for codenum, standard in MTTS: for codenum, standard in MTTS:
if option == 0: if option == 0:
break break
status = option % codenum < option status = option % codenum < option
self.protocol.protocol_flags['TTYPE'][standard] = status self.protocol.protocol_flags['TTYPE'][standard] = status
if status: if status:
option = option % codenum option = option % codenum
self.protocol.protocol_flags['TTYPE']['init_done'] = True self.protocol.protocol_flags['TTYPE']['init_done'] = True
#print "ttype results:", self.protocol.protocol_flags['TTYPE'] #print "ttype results:", self.protocol.protocol_flags['TTYPE']

View file

@ -4,17 +4,17 @@ Web client server resource.
The Evennia web client consists of two components running The Evennia web client consists of two components running
on twisted and django. They are both a part of the Evennia on twisted and django. They are both a part of the Evennia
website url tree (so the testing website might be located website url tree (so the testing website might be located
on http://localhost:8000/, whereas the webclient can be on http://localhost:8000/, whereas the webclient can be
found on http://localhost:8000/webclient.) found on http://localhost:8000/webclient.)
/webclient - this url is handled through django's template /webclient - this url is handled through django's template
system and serves the html page for the client system and serves the html page for the client
itself along with its javascript chat program. itself along with its javascript chat program.
/webclientdata - this url is called by the ajax chat using /webclientdata - this url is called by the ajax chat using
POST requests (long-polling when necessary) POST requests (long-polling when necessary)
The WebClient resource in this module will The WebClient resource in this module will
handle these requests and act as a gateway handle these requests and act as a gateway
to sessions connected over the webclient. to sessions connected over the webclient.
""" """
import time import time
from hashlib import md5 from hashlib import md5
@ -25,7 +25,7 @@ from twisted.internet import defer, reactor
from django.utils import simplejson from django.utils import simplejson
from django.utils.functional import Promise from django.utils.functional import Promise
from django.utils.encoding import force_unicode from django.utils.encoding import force_unicode
from django.conf import settings from django.conf import settings
from src.utils import utils, logger, ansi from src.utils import utils, logger, ansi
from src.utils.text2html import parse_html from src.utils.text2html import parse_html
from src.server import session from src.server import session
@ -34,7 +34,7 @@ SERVERNAME = settings.SERVERNAME
ENCODINGS = settings.ENCODINGS ENCODINGS = settings.ENCODINGS
# defining a simple json encoder for returning # defining a simple json encoder for returning
# django data to the client. Might need to # django data to the client. Might need to
# extend this if one wants to send more # extend this if one wants to send more
# complex database objects too. # complex database objects too.
@ -51,36 +51,36 @@ def jsonify(obj):
# WebClient resource - this is called by the ajax client # WebClient resource - this is called by the ajax client
# using POST requests to /webclientdata. # using POST requests to /webclientdata.
# #
class WebClient(resource.Resource): class WebClient(resource.Resource):
""" """
An ajax/comet long-polling transport An ajax/comet long-polling transport
""" """
isLeaf = True isLeaf = True
allowedMethods = ('POST',) allowedMethods = ('POST',)
def __init__(self): def __init__(self):
self.requests = {} self.requests = {}
self.databuffer = {} self.databuffer = {}
def getChild(self, path, request): def getChild(self, path, request):
""" """
This is the place to put dynamic content. This is the place to put dynamic content.
""" """
return self return self
def _responseFailed(self, failure, suid, request): def _responseFailed(self, failure, suid, request):
"callback if a request is lost/timed out" "callback if a request is lost/timed out"
try: try:
self.requests.get(suid, []).remove(request) self.requests.get(suid, []).remove(request)
except ValueError: except ValueError:
pass pass
def lineSend(self, suid, string, data=None): def lineSend(self, suid, string, data=None):
""" """
This adds the data to the buffer and/or sends it to This adds the data to the buffer and/or sends it to
the client as soon as possible. the client as soon as possible.
""" """
requests = self.requests.get(suid, None) requests = self.requests.get(suid, None)
if requests: if requests:
request = requests.pop(0) request = requests.pop(0)
@ -88,22 +88,22 @@ class WebClient(resource.Resource):
request.write(jsonify({'msg':string, 'data':data})) request.write(jsonify({'msg':string, 'data':data}))
request.finish() request.finish()
self.requests[suid] = requests self.requests[suid] = requests
else: else:
# no waiting request. Store data in buffer # no waiting request. Store data in buffer
dataentries = self.databuffer.get(suid, []) dataentries = self.databuffer.get(suid, [])
dataentries.append(jsonify({'msg':string, 'data':data})) dataentries.append(jsonify({'msg':string, 'data':data}))
self.databuffer[suid] = dataentries self.databuffer[suid] = dataentries
def client_disconnect(self, suid): def client_disconnect(self, suid):
""" """
Disconnect session with given suid. Disconnect session with given suid.
""" """
if self.requests.has_key(suid): if self.requests.has_key(suid):
for request in self.requests.get(suid, []): for request in self.requests.get(suid, []):
request.finish() request.finish()
del self.requests[suid] del self.requests[suid]
if self.databuffer.has_key(suid): if self.databuffer.has_key(suid):
del self.databuffer[suid] del self.databuffer[suid]
def mode_init(self, request): def mode_init(self, request):
""" """
@ -120,10 +120,10 @@ class WebClient(resource.Resource):
# creating a unique id hash string # creating a unique id hash string
suid = md5(str(time.time())).hexdigest() suid = md5(str(time.time())).hexdigest()
self.requests[suid] = [] self.requests[suid] = []
self.databuffer[suid] = [] self.databuffer[suid] = []
sess = WebClientSession() sess = WebClientSession()
sess.client = self sess.client = self
sess.init_session("comet", remote_addr, self.sessionhandler) sess.init_session("comet", remote_addr, self.sessionhandler)
sess.suid = suid sess.suid = suid
sess.sessionhandler.connect(sess) sess.sessionhandler.connect(sess)
@ -154,22 +154,22 @@ class WebClient(resource.Resource):
available. available.
""" """
suid = request.args.get('suid', ['0'])[0] suid = request.args.get('suid', ['0'])[0]
if suid == '0': if suid == '0':
return '' return ''
dataentries = self.databuffer.get(suid, []) dataentries = self.databuffer.get(suid, [])
if dataentries: if dataentries:
return dataentries.pop(0) return dataentries.pop(0)
reqlist = self.requests.get(suid, []) reqlist = self.requests.get(suid, [])
request.notifyFinish().addErrback(self._responseFailed, suid, request) request.notifyFinish().addErrback(self._responseFailed, suid, request)
reqlist.append(request) reqlist.append(request)
self.requests[suid] = reqlist self.requests[suid] = reqlist
return server.NOT_DONE_YET return server.NOT_DONE_YET
def mode_close(self, request): def mode_close(self, request):
""" """
This is called by render_POST when the client is signalling This is called by render_POST when the client is signalling
that it is about to be closed. that it is about to be closed.
""" """
suid = request.args.get('suid', ['0'])[0] suid = request.args.get('suid', ['0'])[0]
if suid == '0': if suid == '0':
@ -184,8 +184,8 @@ class WebClient(resource.Resource):
initializing or sending/receving data through the request. It initializing or sending/receving data through the request. It
uses a long-polling mechanism to avoid sending data unless uses a long-polling mechanism to avoid sending data unless
there is actual data available. there is actual data available.
""" """
dmode = request.args.get('mode', [None])[0] dmode = request.args.get('mode', [None])[0]
if dmode == 'init': if dmode == 'init':
# startup. Setup the server. # startup. Setup the server.
return self.mode_init(request) return self.mode_init(request)
@ -201,11 +201,11 @@ class WebClient(resource.Resource):
else: else:
# this should not happen if client sends valid data. # this should not happen if client sends valid data.
return '' return ''
#
# A session type handling communication over the
# web client interface.
# #
# A session type handling communication over the
# web client interface.
#
class WebClientSession(session.Session): class WebClientSession(session.Session):
""" """
@ -215,38 +215,38 @@ class WebClientSession(session.Session):
def disconnect(self, reason=None): def disconnect(self, reason=None):
""" """
Disconnect from server Disconnect from server
""" """
if reason: if reason:
self.client.lineSend(self.suid, reason) self.client.lineSend(self.suid, reason)
self.client.client_disconnect(self.suid) self.client.client_disconnect(self.suid)
def data_out(self, string='', data=None): def data_out(self, string='', data=None):
""" """
Data Evennia -> Player access hook. Data Evennia -> Player access hook.
data argument may be used depending on data argument may be used depending on
the client-server implementation. the client-server implementation.
""" """
if data: if data:
# treat data? # treat data?
pass pass
# string handling is similar to telnet # string handling is similar to telnet
try: try:
string = utils.to_str(string, encoding=self.encoding) string = utils.to_str(string, encoding=self.encoding)
nomarkup = False nomarkup = False
raw = False raw = False
if type(data) == dict: if type(data) == dict:
# check if we want escape codes to go through unparsed. # check if we want escape codes to go through unparsed.
raw = data.get("raw", False) raw = data.get("raw", False)
# check if we want to remove all markup # check if we want to remove all markup
nomarkup = data.get("nomarkup", False) nomarkup = data.get("nomarkup", False)
if raw: if raw:
self.client.lineSend(self.suid, string) self.client.lineSend(self.suid, string)
else: else:
self.client.lineSend(self.suid, parse_html(ansi.parse_ansi(string, strip_ansi=nomarkup))) self.client.lineSend(self.suid, parse_html(ansi.parse_ansi(string, strip_ansi=nomarkup)))
return return
except Exception, e: except Exception, e:
logger.log_trace() logger.log_trace()

View file

@ -14,7 +14,7 @@ a great example/aid on how to do this.)
from twisted.web import resource from twisted.web import resource
from twisted.python import threadpool from twisted.python import threadpool
from twisted.internet import reactor from twisted.internet import reactor
from twisted.application import service, internet from twisted.application import service, internet
from twisted.web.wsgi import WSGIResource from twisted.web.wsgi import WSGIResource
from django.core.handlers.wsgi import WSGIHandler from django.core.handlers.wsgi import WSGIHandler
@ -26,19 +26,19 @@ from django.core.handlers.wsgi import WSGIHandler
class DjangoWebRoot(resource.Resource): class DjangoWebRoot(resource.Resource):
""" """
This creates a web root (/) that Django This creates a web root (/) that Django
understands by tweaking the way the understands by tweaking the way the
child instancee are recognized. child instancee are recognized.
""" """
def __init__(self, pool): def __init__(self, pool):
""" """
Setup the django+twisted resource Setup the django+twisted resource
""" """
resource.Resource.__init__(self) resource.Resource.__init__(self)
self.wsgi_resource = WSGIResource(reactor, pool , WSGIHandler()) self.wsgi_resource = WSGIResource(reactor, pool , WSGIHandler())
def getChild(self, path, request): def getChild(self, path, request):
""" """
To make things work we nudge the To make things work we nudge the
url tree to make this the root. url tree to make this the root.
""" """
path0 = request.prepath.pop(0) path0 = request.prepath.pop(0)
@ -62,9 +62,9 @@ class WSGIWebServer(internet.TCPServer):
internet.TCPServer.__init__(self, *args, **kwargs) internet.TCPServer.__init__(self, *args, **kwargs)
def startService(self): def startService(self):
"Start the pool after the service" "Start the pool after the service"
internet.TCPServer.startService(self) internet.TCPServer.startService(self)
self.pool.start() self.pool.start()
def stopService(self): def stopService(self):
"Safely stop the pool after service stop." "Safely stop the pool after service stop."
internet.TCPServer.stopService(self) internet.TCPServer.stopService(self)
self.pool.stop() self.pool.stop()

View file

@ -16,19 +16,19 @@ always be sure of what you have changed and what is default behaviour.
import os import os
################################################### ###################################################
# Evennia base server config # Evennia base server config
################################################### ###################################################
# This is the name of your game. Make it catchy! # This is the name of your game. Make it catchy!
SERVERNAME = "Evennia" SERVERNAME = "Evennia"
# Activate telnet service # Activate telnet service
TELNET_ENABLED = True TELNET_ENABLED = True
# A list of ports the Evennia telnet server listens on # A list of ports the Evennia telnet server listens on
# Can be one or many. # Can be one or many.
TELNET_PORTS = [4000] TELNET_PORTS = [4000]
# Interface addresses to listen to. If 0.0.0.0, listen to all. # Interface addresses to listen to. If 0.0.0.0, listen to all.
TELNET_INTERFACES = ['0.0.0.0'] TELNET_INTERFACES = ['0.0.0.0']
# Start the evennia django+twisted webserver so you can # Start the evennia django+twisted webserver so you can
# browse the evennia website and the admin interface # browse the evennia website and the admin interface
# (Obs - further web configuration can be found below # (Obs - further web configuration can be found below
# in the section 'Config for Django web features') # in the section 'Config for Django web features')
@ -42,21 +42,21 @@ WEBSERVER_INTERFACES = ['0.0.0.0']
WEBCLIENT_ENABLED = True WEBCLIENT_ENABLED = True
# Activate SSH protocol (SecureShell) # Activate SSH protocol (SecureShell)
SSH_ENABLED = False SSH_ENABLED = False
# Ports to use for SSH # Ports to use for SSH
SSH_PORTS = [8022] SSH_PORTS = [8022]
# Interface addresses to listen to. If 0.0.0.0, listen to all. # Interface addresses to listen to. If 0.0.0.0, listen to all.
SSH_INTERFACES = ['0.0.0.0'] SSH_INTERFACES = ['0.0.0.0']
# Actiave SSL protocol (SecureSocketLibrary) # Actiave SSL protocol (SecureSocketLibrary)
SSL_ENABLED = False SSL_ENABLED = False
# Ports to use for SSL # Ports to use for SSL
SSL_PORTS = [4001] SSL_PORTS = [4001]
# Interface addresses to listen to. If 0.0.0.0, listen to all. # Interface addresses to listen to. If 0.0.0.0, listen to all.
SSL_INTERFACES = ['0.0.0.0'] SSL_INTERFACES = ['0.0.0.0']
# If multisessions are allowed, a user can log into the game # If multisessions are allowed, a user can log into the game
# from several different computers/clients at the same time. # from several different computers/clients at the same time.
# All feedback from the game will be echoed to all sessions. # All feedback from the game will be echoed to all sessions.
# If false, only one session is allowed, all other are logged off # If false, only one session is allowed, all other are logged off
# when a new connects. # when a new connects.
ALLOW_MULTISESSION = True ALLOW_MULTISESSION = True
# Make this unique, and don't share it with anybody. # Make this unique, and don't share it with anybody.
# NOTE: If you change this after creating any accounts, your users won't be # NOTE: If you change this after creating any accounts, your users won't be
@ -72,8 +72,8 @@ GAME_DIR = os.path.join(BASE_PATH, 'game')
LOG_DIR = os.path.join(GAME_DIR, 'logs') LOG_DIR = os.path.join(GAME_DIR, 'logs')
SERVER_LOG_FILE = os.path.join(LOG_DIR, 'server.log') SERVER_LOG_FILE = os.path.join(LOG_DIR, 'server.log')
PORTAL_LOG_FILE = os.path.join(LOG_DIR, 'portal.log') PORTAL_LOG_FILE = os.path.join(LOG_DIR, 'portal.log')
# Where to log server requests to the web server. This is VERY spammy, so this # Where to log server requests to the web server. This is VERY spammy, so this
# file should be removed at regular intervals. # file should be removed at regular intervals.
HTTP_LOG_FILE = os.path.join(LOG_DIR, 'http_requests.log') HTTP_LOG_FILE = os.path.join(LOG_DIR, 'http_requests.log')
# Local time zone for this installation. All choices can be found here: # Local time zone for this installation. All choices can be found here:
# http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE # http://www.postgresql.org/docs/8.0/interactive/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE
@ -81,57 +81,57 @@ TIME_ZONE = 'UTC'
# Language code for this installation. All choices can be found here: # Language code for this installation. All choices can be found here:
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes # http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
# Should the default MUX help files be imported? This might be # Should the default MUX help files be imported? This might be
# interesting to developers for reference, but is frustrating to users # interesting to developers for reference, but is frustrating to users
# since it creates a lot of help entries that has nothing to do # since it creates a lot of help entries that has nothing to do
# with what is actually available in the game. # with what is actually available in the game.
IMPORT_MUX_HELP = False IMPORT_MUX_HELP = False
# How long time (in seconds) a user may idle before being logged # How long time (in seconds) a user may idle before being logged
# out. This can be set as big as desired. A user may avoid being # out. This can be set as big as desired. A user may avoid being
# thrown off by sending the empty system command 'idle' to the server # thrown off by sending the empty system command 'idle' to the server
# at regular intervals. Set <=0 to deactivate idle timout completely. # at regular intervals. Set <=0 to deactivate idle timout completely.
IDLE_TIMEOUT = 3600 IDLE_TIMEOUT = 3600
# The idle command can be sent to keep your session active without actually # The idle command can be sent to keep your session active without actually
# having to spam normal commands regularly. It gives no feedback, only updates # having to spam normal commands regularly. It gives no feedback, only updates
# the idle timer. # the idle timer.
IDLE_COMMAND = "idle" IDLE_COMMAND = "idle"
# The set of encodings tried. A Player object may set an attribute "encoding" on # The set of encodings tried. A Player object may set an attribute "encoding" on
# itself to match the client used. If not set, or wrong encoding is # itself to match the client used. If not set, or wrong encoding is
# given, this list is tried, in order, aborting on the first match. # given, this list is tried, in order, aborting on the first match.
# Add sets for languages/regions your players are likely to use. # Add sets for languages/regions your players are likely to use.
# (see http://en.wikipedia.org/wiki/Character_encoding) # (see http://en.wikipedia.org/wiki/Character_encoding)
ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"] ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"]
# The game server opens an AMP port so that the portal can # The game server opens an AMP port so that the portal can
# communicate with it. This is an internal functionality of Evennia, usually # communicate with it. This is an internal functionality of Evennia, usually
# operating between the two processes on the same machine. Don't change unless # operating between the two processes on the same machine. Don't change unless
# you know what you are doing. # you know what you are doing.
AMP_HOST = 'localhost' AMP_HOST = 'localhost'
AMP_PORT = 5000 AMP_PORT = 5000
################################################### ###################################################
# Evennia Database config # Evennia Database config
################################################### ###################################################
# Database config syntax for Django 1.2+. You can add several # Database config syntax for Django 1.2+. You can add several
# database engines in the dictionary (untested). # database engines in the dictionary (untested).
# ENGINE - path to the the database backend (replace # ENGINE - path to the the database backend (replace
# sqlite3 in the example with the one you want. # sqlite3 in the example with the one you want.
# Supported database engines are # Supported database engines are
# 'postgresql_psycopg2', 'postgresql', 'mysql', # 'postgresql_psycopg2', 'postgresql', 'mysql',
# 'sqlite3' and 'oracle'). # 'sqlite3' and 'oracle').
# NAME - database name, or path the db file for sqlite3 # NAME - database name, or path the db file for sqlite3
# USER - db admin (unused in sqlite3) # USER - db admin (unused in sqlite3)
# PASSWORD - db admin password (unused in sqlite3) # PASSWORD - db admin password (unused in sqlite3)
# HOST - empty string is localhost (unused in sqlite3) # HOST - empty string is localhost (unused in sqlite3)
# PORT - empty string defaults to localhost (unused in sqlite3) # PORT - empty string defaults to localhost (unused in sqlite3)
DATABASES = { DATABASES = {
'default':{ 'default':{
'ENGINE':'django.db.backends.sqlite3', 'ENGINE':'django.db.backends.sqlite3',
'NAME':os.path.join(GAME_DIR, 'evennia.db3'), 'NAME':os.path.join(GAME_DIR, 'evennia.db3'),
'USER':'', 'USER':'',
'PASSWORD':'', 'PASSWORD':'',
'HOST':'', 'HOST':'',
'PORT':'' 'PORT':''
}} }}
# Engine Config style for Django versions < 1.2. See above. # Engine Config style for Django versions < 1.2. See above.
DATABASE_ENGINE = 'sqlite3' DATABASE_ENGINE = 'sqlite3'
@ -142,10 +142,10 @@ DATABASE_HOST = ''
DATABASE_PORT = '' DATABASE_PORT = ''
################################################### ###################################################
# Evennia pluggable modules # Evennia pluggable modules
################################################### ###################################################
# An alternate command parser module to use # An alternate command parser module to use
COMMAND_PARSER = "src.commands.cmdparser.cmdparser" COMMAND_PARSER = "src.commands.cmdparser.cmdparser"
# The handler that outputs errors when searching # The handler that outputs errors when searching
# objects using object.search(). # objects using object.search().
@ -154,25 +154,25 @@ SEARCH_AT_RESULT = "src.commands.cmdparser.at_search_result"
# object matches (so you can separate between same-named # object matches (so you can separate between same-named
# objects without using dbrefs). # objects without using dbrefs).
SEARCH_AT_MULTIMATCH_INPUT = "src.commands.cmdparser.at_multimatch_input" SEARCH_AT_MULTIMATCH_INPUT = "src.commands.cmdparser.at_multimatch_input"
# The module holding text strings for the connection screen. # The module holding text strings for the connection screen.
# This module should contain one or more variables # This module should contain one or more variables
# with strings defining the look of the screen. # with strings defining the look of the screen.
CONNECTION_SCREEN_MODULE = "src.commands.connection_screen" CONNECTION_SCREEN_MODULE = "src.commands.connection_screen"
# An option al module that, if existing, must hold a function # An option al module that, if existing, must hold a function
# named at_initial_setup(). This hook method can be used to customize # named at_initial_setup(). This hook method can be used to customize
# the server's initial setup sequence (the very first startup of the system). # the server's initial setup sequence (the very first startup of the system).
# The check will fail quietly if module doesn't exist or fails to load. # The check will fail quietly if module doesn't exist or fails to load.
AT_INITIAL_SETUP_HOOK_MODULE = "" AT_INITIAL_SETUP_HOOK_MODULE = ""
# Module holding at_server_start(), at_server_reload() and # Module holding at_server_start(), at_server_reload() and
# at_server_stop() methods. These methods will be called every time # at_server_stop() methods. These methods will be called every time
# the server starts, reloads and resets/stops. # the server starts, reloads and resets/stops.
AT_SERVER_STARTSTOP_MODULE = ""# Module holding server-side functions for out-of-band protocols to call. AT_SERVER_STARTSTOP_MODULE = ""# Module holding server-side functions for out-of-band protocols to call.
OOB_FUNC_MODULE = "" OOB_FUNC_MODULE = ""
# Module holding MSSP meta data # Module holding MSSP meta data
MSSP_META_MODULE = "" MSSP_META_MODULE = ""
################################################### ###################################################
# Default command sets # Default command sets
################################################### ###################################################
# Note that with the exception of the unloggedin set (which is not # Note that with the exception of the unloggedin set (which is not
# stored anywhere), changing these paths will only affect NEW created # stored anywhere), changing these paths will only affect NEW created
@ -211,9 +211,9 @@ BASE_ROOM_TYPECLASS = "src.objects.objects.Room"
BASE_EXIT_TYPECLASS = "src.objects.objects.Exit" BASE_EXIT_TYPECLASS = "src.objects.objects.Exit"
# Typeclass for Scripts (fallback) # Typeclass for Scripts (fallback)
BASE_SCRIPT_TYPECLASS = "src.scripts.scripts.DoNothing" BASE_SCRIPT_TYPECLASS = "src.scripts.scripts.DoNothing"
# The home location for new characters. This must be a unique # The home location for new characters. This must be a unique
# dbref (default is Limbo #2). If you want more advanced control over # dbref (default is Limbo #2). If you want more advanced control over
# start locations, copy the "create" command from # start locations, copy the "create" command from
# src/commands/default/unloggedin.py and customize. # src/commands/default/unloggedin.py and customize.
CHARACTER_DEFAULT_HOME = "2" CHARACTER_DEFAULT_HOME = "2"
@ -221,7 +221,7 @@ CHARACTER_DEFAULT_HOME = "2"
# Batch processors # Batch processors
################################################### ###################################################
# Python path to a directory to be searched for batch scripts # Python path to a directory to be searched for batch scripts
# for the batch processors (.ev and/or .py files). # for the batch processors (.ev and/or .py files).
BASE_BATCHPROCESS_PATHS = ['game.gamesrc.world', 'contrib'] BASE_BATCHPROCESS_PATHS = ['game.gamesrc.world', 'contrib']
@ -236,8 +236,8 @@ BASE_BATCHPROCESS_PATHS = ['game.gamesrc.world', 'contrib']
#The time factor dictates if the game world runs faster (timefactor>1) #The time factor dictates if the game world runs faster (timefactor>1)
# or slower (timefactor<1) than the real world. # or slower (timefactor<1) than the real world.
TIME_FACTOR = 2.0 TIME_FACTOR = 2.0
# The tick is the smallest unit of time in the game. Smallest value is 1s. # The tick is the smallest unit of time in the game. Smallest value is 1s.
TIME_TICK = 1.0 TIME_TICK = 1.0
# These measures might or might not make sense to your game world. # These measures might or might not make sense to your game world.
TIME_MIN_PER_HOUR = 60 TIME_MIN_PER_HOUR = 60
@ -248,7 +248,7 @@ TIME_MONTH_PER_YEAR = 12
################################################### ###################################################
# In-Game access # In-Game access
################################################### ###################################################
# The access hiearchy, in climbing order. A higher permission in the # The access hiearchy, in climbing order. A higher permission in the
@ -268,7 +268,7 @@ LOCK_FUNC_MODULES = ("src.locks.lockfuncs",)
# Defines a dict with one key for each from-start # Defines a dict with one key for each from-start
# channel. Each key points to a tuple containing # channel. Each key points to a tuple containing
# (name, aliases, description, locks) # (name, aliases, description, locks)
# where aliases may be a tuple too, and locks is # where aliases may be a tuple too, and locks is
# a valid lockstring definition. # a valid lockstring definition.
# Default user channel for communication # Default user channel for communication
CHANNEL_PUBLIC = ("Public", ('ooc',), 'Public discussion', CHANNEL_PUBLIC = ("Public", ('ooc',), 'Public discussion',
@ -281,17 +281,17 @@ CHANNEL_CONNECTINFO = ("MUDconnections", '', 'Connection log',
"control:perm(Immortals);listen:perm(Wizards);send:false()") "control:perm(Immortals);listen:perm(Wizards);send:false()")
################################################### ###################################################
# External Channel connections # External Channel connections
################################################### ###################################################
# Note: You do *not* have to make your MUD open to # Note: You do *not* have to make your MUD open to
# the public to use the external connections, they # the public to use the external connections, they
# operate as long as you have an internet connection, # operate as long as you have an internet connection,
# just like stand-alone chat clients. IRC and IMC2 # just like stand-alone chat clients. IRC and IMC2
# requires that you have twisted.words installed. # requires that you have twisted.words installed.
# Evennia can connect to external IRC channels and # Evennia can connect to external IRC channels and
# echo what is said on the channel to IRC and vice # echo what is said on the channel to IRC and vice
# versa. Obs - make sure the IRC network allows bots. # versa. Obs - make sure the IRC network allows bots.
# When enabled, command @irc2chan will be available in-game # When enabled, command @irc2chan will be available in-game
IRC_ENABLED = False IRC_ENABLED = False
@ -307,7 +307,7 @@ IRC_ENABLED = False
# command @imc2chan becomes available in-game and allows you to # command @imc2chan becomes available in-game and allows you to
# connect Evennia channels to IMC channels on the network. The Evennia # connect Evennia channels to IMC channels on the network. The Evennia
# discussion channel 'ievennia' is on server01.mudbytes.net:5000. # discussion channel 'ievennia' is on server01.mudbytes.net:5000.
IMC2_ENABLED = False IMC2_ENABLED = False
IMC2_NETWORK = "server01.mudbytes.net" IMC2_NETWORK = "server01.mudbytes.net"
IMC2_PORT = 5000 IMC2_PORT = 5000
IMC2_CLIENT_PWD = "" IMC2_CLIENT_PWD = ""
@ -316,7 +316,7 @@ IMC2_SERVER_PWD = ""
# an in-game channel. The channel will be updated when the rss feed # an in-game channel. The channel will be updated when the rss feed
# updates. Use @rss2chan in game to connect if this setting is # updates. Use @rss2chan in game to connect if this setting is
# active. OBS: RSS support requires the python-feedparser package to # active. OBS: RSS support requires the python-feedparser package to
# be installed (through package manager or from the website # be installed (through package manager or from the website
# http://code.google.com/p/feedparser/) # http://code.google.com/p/feedparser/)
RSS_ENABLED=False RSS_ENABLED=False
RSS_UPDATE_INTERVAL = 60*10 # 10 minutes RSS_UPDATE_INTERVAL = 60*10 # 10 minutes
@ -366,7 +366,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = False
USE_I18N = False USE_I18N = False
# Where to find locales (no need to change this, most likely) # Where to find locales (no need to change this, most likely)
LOCALE_PATHS = ["../locale/"] LOCALE_PATHS = ["../locale/"]
# This should be turned off unless you want to do tests with Django's # This should be turned off unless you want to do tests with Django's
# development webserver (normally Evennia runs its own server) # development webserver (normally Evennia runs its own server)
SERVE_MEDIA = False SERVE_MEDIA = False
# The master urlconf file that contains all of the sub-branches to the # The master urlconf file that contains all of the sub-branches to the
@ -383,7 +383,7 @@ LOGOUT_URL = '/accounts/login'
MEDIA_URL = '/media/' MEDIA_URL = '/media/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure # URL prefix for admin media -- CSS, JavaScript and images. Make sure
# to use a trailing slash. Django1.4+ will look for admin files under # to use a trailing slash. Django1.4+ will look for admin files under
# STATIC_URL/admin. # STATIC_URL/admin.
STATIC_URL = '/media/' STATIC_URL = '/media/'
ADMIN_MEDIA_PREFIX = STATIC_URL + "admin/" # needed for backwards compatibility django < 1.4 ADMIN_MEDIA_PREFIX = STATIC_URL + "admin/" # needed for backwards compatibility django < 1.4
# The name of the currently selected web template. This corresponds to the # The name of the currently selected web template. This corresponds to the
@ -431,26 +431,26 @@ INSTALLED_APPS = (
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.admindocs', 'django.contrib.admindocs',
'django.contrib.flatpages', 'django.contrib.flatpages',
'src.server', 'src.server',
'src.players', 'src.players',
'src.objects', 'src.objects',
'src.comms', 'src.comms',
'src.help', 'src.help',
'src.scripts', 'src.scripts',
'src.web.news', 'src.web.news',
'src.web.website',) 'src.web.website',)
# The user profile extends the User object with more functionality; # The user profile extends the User object with more functionality;
# This should usually not be changed. # This should usually not be changed.
AUTH_PROFILE_MODULE = "players.PlayerDB" AUTH_PROFILE_MODULE = "players.PlayerDB"
# Use a custom test runner that just tests Evennia-specific apps. # Use a custom test runner that just tests Evennia-specific apps.
TEST_RUNNER = 'src.utils.test_utils.EvenniaTestSuiteRunner' TEST_RUNNER = 'src.utils.test_utils.EvenniaTestSuiteRunner'
################################################### ###################################################
# Django extensions # Django extensions
################################################### ###################################################
# Django extesions are useful third-party tools that are not # Django extesions are useful third-party tools that are not
# always included in the default django distro. # always included in the default django distro.
try: try:
import django_extensions import django_extensions
INSTALLED_APPS = INSTALLED_APPS + ('django_extensions',) INSTALLED_APPS = INSTALLED_APPS + ('django_extensions',)