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
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
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."""
sys.exit()
# signal processing
# signal processing
SIG = signal.SIGINT
HELPENTRY = \
"""
(version %s)
(version %s)
This program launches Evennia with various options. You can access all
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.
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 = \
@ -160,7 +160,7 @@ except DatabaseError:
Your database does not seem to be set up correctly.
Please run:
python manage.py syncdb
(make sure to create an admin user when prompted). If you use
@ -231,7 +231,7 @@ if os.name == 'nt':
# Functions
def get_pid(pidfile):
"""
Get the PID (Process ID) by trying to access
@ -319,7 +319,7 @@ def run_menu():
if inp == 5:
if os.name == 'nt':
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")
elif inp == 6:
if os.name == 'nt':
@ -353,8 +353,8 @@ def handle_args(options, mode, service):
errmsg = "The %s does not seem to be running."
if mode == 'start':
# launch the error checker. Best to catch the errors already here.
# launch the error checker. Best to catch the errors already here.
error_check_python_modules()
# starting one or many services
@ -376,13 +376,13 @@ def handle_args(options, mode, service):
# restarting services
if os.name == 'nt':
print "Restarting from command line is not supported under Windows. Log into the game to restart."
return
return
if service == 'server':
kill(SERVER_PIDFILE, SIG, "Server reloaded.", errmsg % 'Server')
elif service == 'portal':
print """
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'
"""
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
the python source files themselves). Best they fail already here
before we get any further.
"""
"""
def imp(path, split=True):
mod, fromlist = path, "None"
if split:
@ -473,12 +473,12 @@ def main():
# handle command-line arguments
cmdstr = handle_args(options, mode, service)
if cmdstr:
# call the runner.
# call the runner.
cmdstr.append('start')
Popen(cmdstr)
if __name__ == '__main__':
# start Evennia
# start Evennia
from src.utils.utils import check_evennia_dependencies
if check_evennia_dependencies():
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
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
something fitting.
- change settings.CMDSET_DEFAULT to point to the new module's
@ -36,12 +36,12 @@ class ExampleCmdSet(CmdSet):
"""
Implements an empty, example cmdset.
"""
key = "ExampleSet"
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.
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):
"""
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.
Here we copy everything by calling the parent, but you can
@ -60,7 +60,7 @@ class DefaultCmdSet(default_cmds.DefaultCmdSet):
to this class.
"""
key = "DefaultMUX"
def at_cmdset_creation(self):
"""
Populates the cmdset
@ -72,8 +72,8 @@ class DefaultCmdSet(default_cmds.DefaultCmdSet):
# any commands you add below will overload the default ones.
#
#self.add(menusystem.CmdMenuTest())
#self.add(lineeditor.CmdEditor())
#self.add(misc_commands.CmdQuell())
#self.add(lineeditor.CmdEditor())
#self.add(misc_commands.CmdQuell())
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
"""
@ -87,25 +87,25 @@ class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
point to this class.
"""
key = "Unloggedin"
def at_cmdset_creation(self):
"""
Populates the cmdset
"""
# calling setup in src.commands.default.cmdset_unloggedin
super(UnloggedinCmdSet, self).at_cmdset_creation()
#
# any commands you add below will overload the default ones.
#
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).
"""
key = "OOC"
def at_cmdset_creation(self):
"""
Populates the cmdset
@ -114,9 +114,4 @@ class OOCCmdSet(default_cmds.OOCCmdSet):
super(OOCCmdSet, self).at_cmdset_creation()
#
# 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
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
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.
"""
import random
import random
from ev import Command, CmdSet
# Some simple commands for the red button
@ -19,14 +19,14 @@ from ev import Command, CmdSet
class CmdNudge(Command):
"""
Try to nudge the button's lid
Usage:
Usage:
nudge lid
This command will have you try to
push the lid of the button away.
This command will have you try to
push the lid of the button away.
"""
key = "nudge lid" # two-word command name!
aliases = ["nudge"]
locks = "cmd:all()"
@ -43,10 +43,10 @@ class CmdNudge(Command):
else:
self.caller.msg("You manage to get a nail under the lid.")
self.caller.execute_cmd("open lid")
class CmdPush(Command):
"""
Push the red button
Push the red button
Usage:
push button
@ -55,7 +55,7 @@ class CmdPush(Command):
key = "push button"
aliases = ["push", "press button", "press"]
locks = "cmd:all()"
def func(self):
"""
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
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 += "\n\nA BOOM! A bright light blinds you!"
string += "\nThe world goes dark ..."
self.caller.msg(string)
self.caller.location.msg_contents("%s presses the button. BOOM! %s is blinded by a flash!" %
self.caller.msg(string)
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)
# the button's method will handle all setup of scripts etc.
self.obj.press_button(self.caller)
self.obj.press_button(self.caller)
else:
string = "You cannot push the button - there is a glass lid covering it."
self.caller.msg(string)
class CmdSmashGlass(Command):
@ -92,15 +92,15 @@ class CmdSmashGlass(Command):
Try to smash the glass of the button.
"""
key = "smash glass"
aliases = ["smash lid", "break lid", "smash"]
locks = "cmd:all()"
def func(self):
"""
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()
@ -109,11 +109,11 @@ class CmdSmashGlass(Command):
string += " with all your might. The lid won't budge"
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."
self.obj.lamp_works = False
self.obj.lamp_works = False
elif rand < 0.6:
string = "You hit the lid hard. It doesn't move an inch."
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 += " you should just try to open the lid instead?"
self.caller.msg(string)
@ -125,7 +125,7 @@ class CmdOpenLid(Command):
open lid
Usage:
open lid
open lid
"""
@ -135,21 +135,21 @@ class CmdOpenLid(Command):
def func(self):
"simply call the right function."
if self.obj.db.lid_locked:
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 += "the lid will soon close again."
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)
# add the relevant cmdsets to button
# add the relevant cmdsets to button
self.obj.cmdset.add(LidClosedCmdSet)
# call object method
self.obj.open_lid()
class CmdCloseLid(Command):
"""
close the lid
@ -163,7 +163,7 @@ class CmdCloseLid(Command):
key = "close lid"
aliases = ["close"]
locks = "cmd:all()"
def func(self):
"Close the lid"
@ -171,9 +171,9 @@ class CmdCloseLid(Command):
# 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.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)
class CmdBlindLook(Command):
"""
Looking around in darkness
@ -185,14 +185,14 @@ class CmdBlindLook(Command):
"""
key = "look"
key = "look"
aliases = ["l", "get", "examine", "ex", "feel", "listen"]
locks = "cmd:all()"
def func(self):
"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
if self.cmdstring == "get":
@ -208,7 +208,7 @@ class CmdBlindLook(Command):
string = "You are temporarily blinded by the flash. "
string += "Until it wears off, all you can do is feel around blindly."
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)
class CmdBlindHelp(Command):
@ -220,7 +220,7 @@ class CmdBlindHelp(Command):
"""
key = "help"
aliases = "h"
aliases = "h"
locks = "cmd:all()"
def func(self):
@ -242,13 +242,13 @@ class DefaultCmdSet(CmdSet):
The default cmdset always sits
on the button object and whereas other
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
using obj.cmdset.add_default().
"""
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):
"Init the cmdset"
self.add(CmdPush())
@ -260,13 +260,13 @@ class LidClosedCmdSet(CmdSet):
It contains the commands that launches the other
command sets, making the red button a self-contained
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"
# default Union is used *except* if we are adding to a
# cmdset named LidOpenCmdSet - this one we replace
# default Union is used *except* if we are adding to a
# cmdset named LidOpenCmdSet - this one we replace
# completely.
key_mergetype = {"LidOpenCmdSet": "Replace"}
key_mergetype = {"LidOpenCmdSet": "Replace"}
def at_cmdset_creation(self):
"Populates the cmdset when it is instantiated."
@ -276,14 +276,14 @@ class LidClosedCmdSet(CmdSet):
class LidOpenCmdSet(CmdSet):
"""
This is the opposite of the Closed cmdset.
This is the opposite of the Closed cmdset.
"""
key = "LidOpenCmdSet"
# default Union is used *except* if we are adding to a
# cmdset named LidClosedCmdSet - this one we replace
# default Union is used *except* if we are adding to a
# cmdset named LidClosedCmdSet - this one we replace
# completely.
key_mergetype = {"LidClosedCmdSet": "Replace"}
def at_cmdset_creation(self):
"setup the cmdset (just one command)"
self.add(CmdCloseLid())
@ -295,13 +295,13 @@ class BlindCmdSet(CmdSet):
"""
key = "BlindCmdSet"
# we want it to completely replace all normal commands
# until the timed script removes it again.
mergetype = "Replace"
# until the timed script removes it again.
mergetype = "Replace"
# we want to stop the player from walking around
# in this blinded state, so we hide all exits too.
# (channel commands will still work).
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):
"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
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):
"""
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)
Note that the class's __doc__ string (this text) is
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"
aliases = ["mycmd", "myc"]
aliases = ["mycmd", "myc"]
locks = "cmd:all()"
help_category = "General"
# 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.
# (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
# (in that case see src.commands.command.Command))
def at_pre_cmd(self):
"""
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
that can be later accessed from self.func() (see below)
The following variables are available to us:
# class variables:
The following variables are available to us:
# class variables:
self.key - the name of this command ('mycommand')
self.aliases - the aliases of this cmd ('mycmd','myc')
self.locks - lock string for this command ("cmd:all()")
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.cmdstring - the actual command name used to call this
@ -67,10 +67,10 @@ class Command(BaseCommand):
for example)
self.args - the raw input; everything following self.cmdstring.
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)
self.obj - the object on which this command was defined. It is often
the same as self.caller.
the same as self.caller.
"""
pass
@ -78,13 +78,13 @@ class Command(BaseCommand):
"""
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
to all the variables defined therein.
to all the variables defined therein.
"""
self.caller.msg("Command called!")
def at_post_cmd(self):
"""
This hook is called after self.func().
This hook is called after self.func().
"""
pass
@ -101,32 +101,32 @@ class MuxCommand(default_cmd.MuxCommand):
name[ with several words][/switch[/switch..]] arg1[,arg2,...] [[=|,] arg[,..]]
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
in self.args.
cmdhandler at this point, and stored in self.cmdname. The rest is stored
in self.args.
The MuxCommand parser breaks self.args into its constituents and stores them in the
following variables:
The MuxCommand parser breaks self.args into its constituents and stores them in the
following variables:
self.switches = optional list of /switches (without the /)
self.raw = This is the raw argument input, including 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.
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.
self.lhslist - self.lhs 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)
All args and list members are stripped of excess whitespace around the
strings, but case is preserved.
All args and list members are stripped of excess whitespace around the
strings, but case is preserved.
"""
def func(self):
"""
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
to all the variables defined therein.
"""
# this can be removed in your child class, it's just
to all the variables defined therein.
"""
# this can be removed in your child class, it's just
# printing the ingoing variables as a demo.
super(MuxCommand, self).func()

View file

@ -2,7 +2,7 @@
At_initial_setup module template
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
module.
@ -21,4 +21,4 @@ does what you expect it to.
"""
def at_initial_setup():
pass
pass

View file

@ -3,7 +3,7 @@
At_server_startstop module template
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
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
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_stop()
at_server_start()
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
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
new module.
@ -10,7 +10,7 @@ new module.
This module holds textual connection screen definitions. All global
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
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
#
# CUSTOM_SCREEN = \
# """{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:
# {wconnect <email> <password>{n
@ -45,5 +45,5 @@ from src.commands.connection_screen import DEFAULT_SCREEN
# MENU_SCREEN = \
# """{b=============================================================={n
# Welcome to {gEvennnia{n, version %s!
# Welcome to {gEvennnia{n, version %s!
# {b=============================================================={n""" % utils.get_evennia_version()

View file

@ -3,9 +3,9 @@
Lockfuncs module template
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.
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
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):
"""
called in lockstring with myfalse().
called in lockstring with myfalse().
A simple logger that always returns false. Prints to stdout
for simplicity, should use utils.logger for real operation.
"""
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
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.
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
information to keep up-to-date with your game stats as you change
them. Also number of currently active players and uptime will
automatically be reported. You don't have to fill in everything
(and most are not used by all crawlers); leave the default
if so needed. You need to @reload the game before updated
information is made available to crawlers (reloading does not
affect uptime).
if so needed. You need to @reload the game before updated
information is made available to crawlers (reloading does not
affect uptime).
"""
MSSPTable = {
@ -29,16 +29,16 @@ MSSPTable = {
"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
"CODEBASE": "Evennia",
"CONTACT": "", # email for contacting the mud
"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
"LANGUAGE": "", # name of language used, e.g. English
"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",
# Categorisation
@ -50,14 +50,14 @@ MSSPTable = {
# Roleplaying, Simulation, Social or Strategy
"STATUS": "Open Beta", # Alpha, Closed Beta, Open Beta, Live
"GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew
"INTERMUD": "IMC2", # evennia supports IMC2.
"SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein,
"INTERMUD": "IMC2", # evennia supports IMC2.
"SUBGENRE": "None", # LASG, Medieval Fantasy, World War II, Frankenstein,
# Cyberpunk, Dragonlance, etc. Or None if not available.
# World
"AREAS": "0",
"HELPFILES": "0",
"AREAS": "0",
"HELPFILES": "0",
"MOBILES": "0",
"OBJECTS": "0",
"ROOMS": "0", # use 0 if room-less
@ -121,7 +121,7 @@ MSSPTable = {
# Protocols (only change if you added/removed something manually)
"ATCP": "0",
"MSDP": "0",
"MSDP": "0",
"MCCP": "1",
"SSL": "1",
"UTF-8": "1",

View file

@ -3,14 +3,14 @@
** OBS This module is not yet used by Evennia **
Example module holding functions for out-of-band protocols to
import and map to given commands from the client. This module
is selected by settings.OOB_FUNC_MODULE.
import and map to given commands from the client. This module
is selected by settings.OOB_FUNC_MODULE.
All functions defined global in this module will be available
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
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):

View file

@ -3,16 +3,16 @@
Template for Characters
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
your new class, e.g.
settings.BASE_CHARACTER_TYPECLASS = "game.gamesrc.objects.mychar.MyChar"
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.
"""
@ -20,22 +20,21 @@ from ev import Character
class ExampleCharacter(Character):
"""
The Character is like any normal Object (see example/object.py for
a list of properties and methods), except it actually implements
some of its hook methods to do some work:
at_basetype_setup - always assigns the default_cmdset to this object type
(important!)sets locks so character cannot be picked up
The Character is like any normal Object (see example/object.py for
a list of properties and methods), except it actually implements
some of its hook methods to do some work:
at_basetype_setup - always assigns the default_cmdset to this object type
(important!)sets locks so character cannot be picked up
and its commands only be called by itself, not anyone else.
(to change things, use at_object_creation() instead)
at_after_move - launches the "look" command
at_disconnect - stores the current location, so the "unconnected" character
object does not need to stay on grid but can be given a
None-location while offline.
object does not need to stay on grid but can be given a
None-location while offline.
at_post_login - retrieves the character's old location and puts it back
on the grid with a "charname has connected" message echoed
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"
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.
"""
@ -21,8 +21,8 @@ from ev import Exit
class ExampleExit(Exit):
"""
Exits are connectors between rooms. Exits are normal Objects except
they defines the 'destination' property. It also does work in the
following methods:
they defines the 'destination' property. It also does work in the
following methods:
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
@ -33,11 +33,11 @@ class ExampleExit(Exit):
go there") if exit traversal fails and an
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_after_traverse(traveller, source_loc) - called just after traversing
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.
"""
pass

View file

@ -3,7 +3,7 @@
Template for Objects
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
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"
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.
"""
@ -34,19 +34,19 @@ class ExampleObject(Object):
methods, such as __init__ and especially never __getattribute__ and
__setattr__ since these are used heavily by the typeclass system
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
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.
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.
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)
location (Object) - current location. Is None if this is a room
home (Object) - safety start-location
@ -54,19 +54,19 @@ class ExampleObject(Object):
has_player (bool, read-only)- will only return *connected* players
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
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
* Handlers available
* Handlers available
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
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()
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
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)
execute_cmd(raw_string)
@ -79,21 +79,21 @@ class ExampleObject(Object):
swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
access(accessing_obj, access_type='read', default=False)
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_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
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_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_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_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_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_server_reload() - called before server is reloaded
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_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_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.
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
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_drop(dropper) - called when this object has been dropped.
at_say(speaker, message) - by default, called if an object inside this object speaks

View file

@ -3,16 +3,16 @@
Template module for Players
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
your new class, e.g.
settings.BASE_PLAYER_TYPECLASS = "game.gamesrc.objects.myplayer.MyPlayer"
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.
"""
@ -20,19 +20,19 @@ from ev import 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
is handled by the character which is connected to this). Comm channels
are attended/joined using this object.
It can be useful e.g. for storing configuration options for your game, but
are attended/joined using this object.
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
on the character level).
Can be set using BASE_PLAYER_TYPECLASS.
* available properties
* available properties
key (string) - name of player
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
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
permissions (list of strings) - list of permission strings
permissions (list of strings) - list of permission strings
user (User, read-only) - django User authorization object
obj (Object) - game object controlled by player. 'character' can also be used.
sessions (list of Sessions) - sessions connected to this player
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
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()
cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
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)
basetype_setup()
at_player_creation()
at_player_creation()
- note that the following hooks are also found on Objects and are
usually handled on the character level:
at_init()
at_init()
at_cmdset_get()
at_first_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
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 game.gamesrc.scripts.examples import red_button_scripts as scriptexamples
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
#
class RedButton(Object):
"""
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
instead of e.g. __init__.
"""
"""
# store desc (default, you can change this at creation time)
desc = "This is a large red button, inviting yet evil-looking. "
desc += "A closed glass lid protects it."
self.db.desc = desc
desc += "A closed glass lid protects it."
self.db.desc = desc
# We have to define all the variables the scripts
# are checking/using *before* adding the scripts or
# they might be deactivated before even starting!
self.db.lid_open = False
self.db.lamp_works = True
# We have to define all the variables the scripts
# are checking/using *before* adding the scripts or
# they might be deactivated before even starting!
self.db.lid_open = False
self.db.lamp_works = True
self.db.lid_locked = False
self.cmdset.add_default(cmdsetexamples.DefaultCmdSet, permanent=True)
# 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
# check so they are valid (i.e. the lid is actually still closed)).
# The AddClosedCmdSet script makes sure to add the Closed-cmdset.
# check so they are valid (i.e. the lid is actually still closed)).
# The AddClosedCmdSet script makes sure to add the Closed-cmdset.
self.scripts.add(scriptexamples.ClosedLidState)
# the script EventBlinkButton makes the button blink regularly.
self.scripts.add(scriptexamples.BlinkButtonEvent)
# state-changing methods
# state-changing methods
def open_lid(self):
"""
@ -68,12 +68,12 @@ class RedButton(Object):
"""
if self.db.lid_open:
return
return
desc = self.db.desc_lid_open
if not desc:
desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is open and the button exposed."
self.db.desc = desc
self.db.desc = desc
self.db.lid_open = True
# with the lid open, we validate scripts; this will clean out
@ -93,13 +93,13 @@ class RedButton(Object):
"""
if not self.db.lid_open:
return
return
desc = self.db.desc_lid_closed
if not desc:
desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is closed, protecting it."
self.db.desc = desc
self.db.lid_open = False
desc += "Its glass cover is closed, protecting it."
self.db.desc = desc
self.db.lid_open = False
# clean out scripts depending on lid to be open
self.scripts.validate()
@ -109,17 +109,17 @@ class RedButton(Object):
def break_lamp(self, feedback=True):
"""
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
if not desc:
if not desc:
self.db.desc += "\nThe big red button has stopped blinking for the time being."
else:
self.db.desc = desc
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()
def press_button(self, pobject):
@ -129,7 +129,7 @@ class RedButton(Object):
"""
# deactivate the button so it won't flash/close lid etc.
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!
pobject.scripts.add(scriptexamples.BlindedState)
@ -140,19 +140,18 @@ class RedButton(Object):
The script system will regularly call this
function to make the button blink. Now and then
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
if loc:
loc = self.location
if loc:
rand = random.random()
if rand < 0.2:
if rand < 0.2:
string = "The red button flashes briefly."
elif rand < 0.4:
string = "The red button blinks invitingly."
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:
# no blink
return
return
loc.msg_contents(string)

View file

@ -3,7 +3,7 @@
Template module for Rooms
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
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"
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.
"""
@ -22,11 +22,11 @@ from ev import Room
class ExampleRoom(Room):
"""
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.
(to change that, use at_object_creation instead)
See examples/object.py for a list of
properties and methods available on all Objects.
properties and methods available on all Objects.
"""
pass

View file

@ -1,9 +1,9 @@
"""
Example script for testing. This adds a simple timer that
has your character make observations and noices at irregular
intervals.
intervals.
To test, use
To test, use
@script me = examples.bodyfunctions.BodyFunctions
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!
"""
import random
import random
from ev import Script
class BodyFunctions(Script):
@ -19,26 +19,26 @@ class BodyFunctions(Script):
This class defines the script itself
"""
def at_script_creation(self):
def at_script_creation(self):
self.key = "bodyfunction"
self.desc = "Adds various timed events to a character."
self.interval = 20 # seconds
#self.repeats = 5 # repeat only a certain number of times
self.start_delay = True # wait self.interval until first call
#self.persistent = True
def at_repeat(self):
"""
This gets called every self.interval seconds. We make
a random check here so as to only return 33% of the time.
This gets called every self.interval seconds. We make
a random check here so as to only return 33% of the time.
"""
if random.random() < 0.66:
# no message this time
return
return
rand = random.random()
# return a random message
if rand < 0.1:
if rand < 0.1:
string = "You tap your foot, looking around."
elif rand < 0.2:
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."
else:
string = "You suddenly realize how much you love Evennia!"
# echo the message to the object
self.obj.msg(string)

View file

@ -1,7 +1,7 @@
"""
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
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 have many uses, one of which is to statically
# make changes when a particular state of an object changes.
# There is no "timer" involved in this case (although there could be),
# 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,
# 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
# 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
# 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):
"""
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
cmdset to it (with commands like open, nudge lid etc)
"""
def at_script_creation(self):
"Called when script first created."
self.desc = "Script that manages the closed-state cmdsets for red button."
self.persistent = True
self.persistent = True
def at_start(self):
"""
This is called once every server restart, so we want to add the
(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
open lid.
open lid.
"""
#All we do is add the cmdset for the closed state.
self.obj.cmdset.add(cmdsetexamples.LidClosedCmdSet)
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.
"""
return not self.obj.db.lid_open
@ -57,7 +57,7 @@ class ClosedLidState(Script):
def at_stop(self):
"""
When the script stops we must make sure to clean up after us.
"""
self.obj.cmdset.delete(cmdsetexamples.LidClosedCmdSet)
@ -68,23 +68,23 @@ class OpenLidState(Script):
the RedButtonOpen
"""
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.persistent = True
self.persistent = True
def at_start(self):
"""
This is called once every server restart, so we want to add the
(memory-resident) cmdset to the object here. is_valid is
automatically checked, so we don't need to worry about
(memory-resident) cmdset to the object here. is_valid is
automatically checked, so we don't need to worry about
adding the cmdset to a closed lid-button.
"""
#print "In Open at_start (should add cmdset)"
self.obj.cmdset.add(cmdsetexamples.LidOpenCmdSet)
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.
"""
return self.obj.db.lid_open
@ -99,12 +99,12 @@ class OpenLidState(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,
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
set it on the right player object.
set it on the right player object.
"""
def at_script_creation(self):
"""
@ -114,13 +114,13 @@ class BlindedState(Script):
self.desc = "Temporarily blinds the player for a little while."
self.interval = 20 # seconds
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
def at_start(self):
"""
We want to add the cmdset to the linked object.
Note that the RedButtonBlind cmdset is defined to completly
replace the other cmdsets on the stack while it is active
(this means that while blinded, only operations in this cmdset
@ -133,39 +133,39 @@ class BlindedState(Script):
def at_stop(self):
"""
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.location.msg_contents("%s seems to be recovering their eyesight."
% self.obj.name,
self.obj.location.msg_contents("%s seems to be recovering their eyesight."
% self.obj.name,
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).
#
# 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
# more "alive" - one that makes the button blink menacingly, another
# that makes the lid covering the button slide back after a while.
# more "alive" - one that makes the button blink menacingly, another
# that makes the lid covering the button slide back after a while.
#
class CloseLidEvent(Script):
"""
This event closes the glass lid over the button
some time after it was opened. It's a one-off
script that should be started/created when the
lid is opened.
script that should be started/created when the
lid is opened.
"""
def at_script_creation(self):
"""
Called when script object is first created. Sets things up.
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
from pressing (and deleting this command).
from pressing (and deleting this command).
"""
self.key = "lid_closer"
self.desc = "Closes lid on a red buttons"
@ -177,61 +177,61 @@ class CloseLidEvent(Script):
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.
Note that we are here relying on an self.obj being
defined (and being a RedButton object) - this we should be able to
expect since this type of script is always tied to one individual
defined (and being a RedButton object) - this we should be able to
expect since this type of script is always tied to one individual
red button object and not having it would be an error.
"""
return self.obj.db.lid_open
def at_repeat(self):
"""
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 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
check this manually.
"""
self.obj.close_lid()
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):
"""
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
if you try to smash the glass, say).
if you try to smash the glass, say).
"""
self.key = "blink_button"
self.desc = "Blinks red buttons"
self.interval = 35 #seconds
self.start_delay = False #blink right away
self.persistent = True #keep blinking also after server reboot
def is_valid(self):
"""
Button will keep blinking unless it is broken.
"""
#print "self.obj.db.lamp_works:", self.obj.db.lamp_works
return self.obj.db.lamp_works
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.
"""
self.obj.blink()
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
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*
whereas this one must be defined on the *button*.
whereas this one must be defined on the *button*.
"""
def at_script_creation(self):
"""
@ -241,9 +241,9 @@ class DeactivateButtonEvent(Script):
self.desc = "Deactivate red button temporarily"
self.interval = 21 #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
def at_start(self):
"""
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
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.
self.obj.db.lid_locked = True
self.obj.db.lid_locked = True
# breaking the lamp also sets a correct desc
self.obj.break_lamp(feedback=False)
@ -263,11 +263,11 @@ class DeactivateButtonEvent(Script):
When this is called, reset the functionality of the button.
"""
# restore button's desc.
self.obj.db.lamp_works = True
desc = "This is a large red button, inviting yet evil-looking. "
desc += "Its glass cover is closed, protecting it."
self.db.desc = desc
desc += "Its glass cover is closed, protecting it."
self.db.desc = desc
# re-activate the blink button event.
self.obj.scripts.add(BlinkButtonEvent)
# 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
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 player being involved in the change). Scripts can work like "events",
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
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.
* available properties
* available properties
key (string) - name of object
key (string) - name of object
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.
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
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
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
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
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
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
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
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)
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
at the current time. If is_valid() returns False, the running
script is stopped and removed from the game. You can use this
to check state changes (i.e. an script tracking some combat
stats at regular intervals is only valid to run while there is
actual combat going on).
to check state changes (i.e. an script tracking some combat
stats at regular intervals is only valid to run while there is
actual combat going on).
at_start() - Called every time the script is started, which for persistent
scripts is at least once every server start. Note that this is
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
upon launch unless self.delay_start is True, which will delay
the first call of this method by self.interval seconds. If
self.interval==0, this method will never be called.
the first call of this method by self.interval seconds. If
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
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.
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.
# Test it by loading it with the @batchprocess command
@ -10,9 +10,9 @@
# marks the end of a previous command definition (important!).
#
# 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
# line in a command definition translates into a newline.
# line in a command definition translates into a newline.
#
# This creates a red button
@ -20,32 +20,32 @@
@create button:examples.red_button.RedButton
# This comment ends input for @create
# Next command:
# Next command:
@set button/desc =
This is a large red button. Now and then
it flashes in an evil, yet strangely tantalizing way.
@set button/desc =
This is a large red button. Now and then
it flashes in an evil, yet strangely tantalizing way.
A big sign sits next to it. It says:
-----------
Press me!
Press me!
-----------
... It really begs to be pressed, doesn't it? You
know you want to!
# This ends the @set command. Note that line breaks and extra spaces
# in the argument are not considered. A completely empty line
... It really begs to be pressed, doesn't it? You
know you want to!
# This ends the @set command. Note that line breaks and extra spaces
# in the argument are not considered. A completely empty line
# 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
# 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)
@teleport #2

View file

@ -1,6 +1,6 @@
#
# Batchcode script
#
#
#
# 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
@ -8,29 +8,29 @@
# of the file and allows for features like stepping from block to block
# (without executing those coming before), as well as automatic deletion
# 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
# import statements and global variables. They will
# automatically be made available for each block. Observe
# that changes to these variables made in one block is not
# 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
# defined.
# infotext is a describing text about what goes in in this block. It will be
# defined.
# infotext is a describing text about what goes in in this block. It will be
# shown by the batchprocessing command.
# <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
# debugging the script). E.g., if the code contains the command
# <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
# debugging the script). E.g., if the code contains the command
# myobj = create.create_object(...), you could put 'myobj' in the #CODE header
# 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
# 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
# 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.
# 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:
@ -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.
from ev import create, search
@ -53,13 +53,13 @@ limbo = search.objects('Limbo', global_search=True)[0]
#CODE (create red button)
# 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
# offers no information about red_button variable, so it
# won't be able to be deleted in debug mode.
# offers no information about red_button variable, so it
# won't be able to be deleted in debug mode.
# 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"])
# we take a look at what we created
@ -74,10 +74,10 @@ caller.msg("A %s was created." % red_button.key)
# times).
# 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
# debugging run.
# header for the system to be able to delete them afterwards during a
# debugging run.
table = create.create_object(baseobjects.Object, key="Table", 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))

View file

@ -2,7 +2,7 @@
"""
Set up the evennia system. A first startup consists of giving
the command './manage syncdb' to setup the system and create
the database.
the database.
"""
import sys
@ -21,10 +21,10 @@ except IOError:
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 settings.py doesn't already exist, create it and populate it with some
# basic stuff.
@ -33,7 +33,7 @@ if not os.path.exists('settings.py'):
_CREATED_SETTINGS = True
string = \
"""#
"""#
# Evennia MU* server configuration file
#
# 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).
#
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.
print """
Welcome to Evennia (version %(version)s)!
We created a fresh settings.py file for you.""" % {'version': VERSION}
Welcome to Evennia (version %(version)s)!
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.
string += """\n
Error: Couldn't import the file 'settings.py' in the directory
containing %(file)r. There are usually two reasons for this:
1) You moved your settings.py elsewhere. In that case move it back or
create a link to it from this folder.
Error: Couldn't import the file 'settings.py' in the directory
containing %(file)r. There are usually two reasons for this:
1) You moved your settings.py elsewhere. In that case move it back or
create a link to it from this folder.
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.
3) If you get errors on finding DJANGO_SETTINGS_MODULE you might have
Review the traceback above to resolve the problem, then try again.
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
to restart it to see if this resolves the issue. Evennia should not require you
to define any environment variables manually.
""" % {'file': __file__}
print string
to restart it to see if this resolves the issue. Evennia should not require you
to define any environment variables manually.
""" % {'file': __file__}
print string
sys.exit(1)
os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
@ -139,8 +139,8 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
#------------------------------------------------------------
if __name__ == "__main__":
# checks if the settings file was created this run
if _CREATED_SETTINGS:
# checks if the settings file was created this run
if _CREATED_SETTINGS:
print _("""
Edit your new settings.py file as needed, then run
'python manage syncdb' and follow the prompts to
@ -149,7 +149,7 @@ if __name__ == "__main__":
sys.exit()
# 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():
from django.core.management import execute_manager
from django.core.management import execute_manager
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.
"""
import os
import os
import sys
from optparse import OptionParser
from subprocess import Popen, call
@ -22,7 +22,7 @@ import Queue, thread, subprocess
#
# System Configuration
#
#
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."
sys.exit()
# Get the settings
from django.conf import settings
@ -58,26 +58,26 @@ if 'PYTHONPATH' in os.environ:
else:
os.environ['PYTHONPATH'] = currpath
TWISTED_BINARY = 'twistd'
TWISTED_BINARY = 'twistd'
if os.name == 'nt':
TWISTED_BINARY = 'twistd.bat'
err = False
err = False
try:
import win32api # Test for for win32api
except ImportError:
err = True
err = True
if not os.path.exists(TWISTED_BINARY):
err = True
err = True
if err:
print "Twisted binary for Windows is not ready to use. Please run evennia.py."
sys.exit()
# Functions
# Functions
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.write(str(flag))
f.close()
@ -89,24 +89,24 @@ def get_restart_mode(restart_file):
if os.path.exists(restart_file):
flag = open(restart_file, 'r').read()
return flag == "True"
return False
return False
def get_pid(pidfile):
"""
Get the PID (Process ID) by trying to access
an PID file.
an PID file.
"""
pid = None
pid = None
if os.path.exists(pidfile):
f = open(pidfile, 'r')
pid = f.read()
return pid
return pid
def cycle_logfile(logfile):
"""
Move the old log files to <filename>.old
"""
"""
logfile_old = logfile + '.old'
if os.path.exists(logfile):
# Cycle the old logfiles to *.old
@ -122,70 +122,70 @@ def cycle_logfile(logfile):
if os.path.exists(logfile_old):
# E.g. Windows don't support rename-replace
os.remove(logfile_old)
os.rename(logfile, logfile_old)
os.rename(logfile, logfile_old)
# Start program management
# Start program management
SERVER = None
PORTAL = None
PORTAL = None
def start_services(server_argv, portal_argv):
"""
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()
def server_waiter(queue):
try:
def server_waiter(queue):
try:
rc = Popen(server_argv).wait()
except Exception, e:
print "Server process error: %(e)s" % {'e': e}
queue.put(("server_stopped", rc)) # this signals the controller that the program finished
def portal_waiter(queue):
try:
def portal_waiter(queue):
try:
rc = Popen(portal_argv).wait()
except Exception, e:
print "Portal process error: %(e)s" % {'e': e}
queue.put(("portal_stopped", rc)) # this signals the controller that the program finished
if server_argv:
# start server as a reloadable thread
# start server as a reloadable thread
SERVER = thread.start_new_thread(server_waiter, (processes, ))
if portal_argv:
if portal_argv:
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, ))
else:
# normal operation: start portal as a daemon; we don't care to monitor it for restart
PORTAL = Popen(portal_argv)
if not SERVER:
# 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:
# this blocks until something is actually returned.
message, rc = processes.get()
message, rc = processes.get()
# restart only if process stopped cleanly
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, ))
continue
continue
# 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):
print "Evennia Portal stopped in interactive mode. Restarting ..."
PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
continue
break
PORTAL = thread.start_new_thread(portal_waiter, (processes, ))
continue
break
# 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)
"""
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).")
parser.add_option('-s', '--noserver', action='store_true',
parser.add_option('-s', '--noserver', action='store_true',
dest='noserver', default=False,
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,
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,
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,
help='output portal log to stdout. Does not make portal a daemon.')
parser.add_option('-S', '--profile-server', action='store_true',
@ -222,17 +222,17 @@ def main():
parser.print_help()
sys.exit()
# set up default project calls
server_argv = [TWISTED_BINARY,
# set up default project calls
server_argv = [TWISTED_BINARY,
'--nodaemon',
'--logfile=%s' % SERVER_LOGFILE,
'--pidfile=%s' % SERVER_PIDFILE,
'--pidfile=%s' % SERVER_PIDFILE,
'--python=%s' % SERVER_PY_FILE]
portal_argv = [TWISTED_BINARY,
'--logfile=%s' % PORTAL_LOGFILE,
'--pidfile=%s' % PORTAL_PIDFILE,
'--python=%s' % PORTAL_PY_FILE]
'--pidfile=%s' % PORTAL_PIDFILE,
'--python=%s' % PORTAL_PY_FILE]
# Profiling settings (read file from python shell e.g with
# p = pstats.Stats('server.prof')
sprof_argv = ['--savestats',
@ -242,14 +242,14 @@ def main():
'--profiler=cprofile',
'--profile=portal.prof']
# Server
# Server
pid = get_pid(SERVER_PIDFILE)
if pid and not options.noserver:
print "\nEvennia Server is already running as process %(pid)s. Not restarted." % {'pid': pid}
options.noserver = True
if options.noserver:
server_argv = None
server_argv = None
else:
set_restart_mode(SERVER_RESTART, True)
if options.iserver:
@ -264,19 +264,19 @@ def main():
cycle_logfile(SERVER_LOGFILE)
# Portal
# Portal
pid = get_pid(PORTAL_PIDFILE)
if pid and not options.noportal:
print "\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid}
options.noportal = True
print "\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid}
options.noportal = True
if options.noportal:
portal_argv = None
portal_argv = None
else:
if options.iportal:
# make portal interactive
portal_argv[1] = '--nodaemon'
PORTAL_INTERACTIVE = True
PORTAL_INTERACTIVE = True
set_restart_mode(PORTAL_RESTART, True)
print "\nStarting Evennia Portal in non-Daemon mode (output to stdout)."
else:
@ -297,7 +297,7 @@ def main():
# Start processes
start_services(server_argv, portal_argv)
if __name__ == '__main__':
from src.utils.utils import check_evennia_dependencies
if check_evennia_dependencies():

View file

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

View file

@ -4,11 +4,11 @@ to service the MUD portal proxy.
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
it will automatically try to reconnect.
Server - (AMP server) Handles all mud operations. The server holds its own list
it will automatically try to reconnect.
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
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")
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server.restart")
# communication bits
# communication bits
PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync
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
SSHUTD = chr(7) # server shutdown
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
# i18n
@ -54,7 +54,7 @@ def get_restart_mode(restart_file):
if os.path.exists(restart_file):
flag = open(restart_file, 'r').read()
return flag == "True"
return False
return False
class AmpServerFactory(protocol.ServerFactory):
"""
@ -66,8 +66,8 @@ class AmpServerFactory(protocol.ServerFactory):
server: The Evennia server service instance
protocol: The protocol the factory creates instances of.
"""
self.server = server
self.protocol = AMPProtocol
self.server = server
self.protocol = AMPProtocol
def buildProtocol(self, addr):
"""
@ -91,7 +91,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
maxDelay = 1
def __init__(self, portal):
self.portal = portal
self.portal = portal
self.protocol = AMPProtocol
def startedConnecting(self, connector):
@ -100,10 +100,10 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
"""
pass
#print 'AMP started to connect:', connector
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
self.resetDelay()
@ -114,7 +114,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
def clientConnectionLost(self, connector, reason):
"""
Called when the AMP connection to the MUD server is lost.
"""
"""
if not get_restart_mode(SERVER_RESTART):
self.portal.sessions.announce_all(_(" Portal lost connection to Server."))
protocol.ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
@ -128,7 +128,7 @@ class AmpClientFactory(protocol.ReconnectingClientFactory):
class MsgPortal2Server(amp.Command):
"""
"""
Message portal -> server
"""
arguments = [('sessid', amp.Integer()),
@ -138,7 +138,7 @@ class MsgPortal2Server(amp.Command):
response = []
class MsgServer2Portal(amp.Command):
"""
"""
Message server -> portal
"""
arguments = [('sessid', amp.Integer()),
@ -155,7 +155,7 @@ class OOBPortal2Server(amp.Command):
('data', amp.String())]
errors = [(Exception, "EXCEPTION")]
response = []
class OOBServer2Portal(amp.Command):
"""
OOB data server -> portal
@ -164,13 +164,13 @@ class OOBServer2Portal(amp.Command):
('data', amp.String())]
errors = [(Exception, "EXCEPTION")]
response = []
class ServerAdmin(amp.Command):
"""
Portal -> Server
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
"""
arguments = [('sessid', amp.Integer()),
@ -178,13 +178,13 @@ class ServerAdmin(amp.Command):
('data', amp.String())]
errors = [(Exception, 'EXCEPTION')]
response = []
class PortalAdmin(amp.Command):
"""
Server -> Portal
Sent when the server needs to perform admin
operations on the portal.
operations on the portal.
"""
arguments = [('sessid', amp.Integer()),
('operation', amp.String()),
@ -209,27 +209,27 @@ class AMPProtocol(amp.AMP):
subclasses that specify the datatypes of the input/output of these methods.
"""
# helper methods
# helper methods
def connectionMade(self):
"""
This is called when a connection is established
between server and portal. It is called on both sides,
so we need to make sure to only trigger resync from the
server side.
server side.
"""
if hasattr(self.factory, "portal"):
sessdata = self.factory.portal.sessions.get_all_sync_data()
#print sessdata
self.call_remote_ServerAdmin(0,
PSYNC,
self.call_remote_ServerAdmin(0,
PSYNC,
data=sessdata)
if get_restart_mode(SERVER_RESTART):
msg = _(" ... Server restarted.")
self.factory.portal.sessions.announce_all(msg)
self.factory.portal.sessions.at_server_connection()
# Error handling
# Error handling
def errback(self, e, info):
"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
# 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.
"""
@ -253,14 +253,14 @@ class AMPProtocol(amp.AMP):
def call_remote_MsgPortal2Server(self, sessid, msg, data=""):
"""
Access method called by the Portal and executed on the Portal.
"""
"""
#print "msg portal->server (portal side):", sessid, msg
self.callRemote(MsgPortal2Server,
sessid=sessid,
msg=msg,
data=dumps(data)).addErrback(self.errback, "MsgPortal2Server")
# Server -> Portal message
# Server -> Portal message
def amp_msg_server2portal(self, sessid, msg, data):
"""
@ -281,11 +281,11 @@ class AMPProtocol(amp.AMP):
msg=to_str(msg),
data=dumps(data)).addErrback(self.errback, "OOBServer2Portal")
# OOB Portal -> Server
# OOB Portal -> Server
# 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.
"""
@ -297,13 +297,13 @@ class AMPProtocol(amp.AMP):
def call_remote_OOBPortal2Server(self, sessid, data=""):
"""
Access method called by the Portal and executed on the Portal.
"""
"""
#print "oob portal->server (portal side):", sessid, data
self.callRemote(OOBPortal2Server,
sessid=sessid,
sessid=sessid,
data=dumps(data)).addErrback(self.errback, "OOBPortal2Server")
# Server -> Portal message
# Server -> Portal message
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.
"""
#print "oob server->portal (server side):", sessid, data
#print "oob server->portal (server side):", sessid, data
self.callRemote(OOBServer2Portal,
sessid=sessid,
sessid=sessid,
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):
"""
This allows the portal to perform admin
@ -334,12 +334,12 @@ class AMPProtocol(amp.AMP):
data = loads(data)
#print "serveradmin (server side):", sessid, operation, data
if operation == PCONN: #portal_session_connect
# create a new session and sync it
sess = ServerSession()
sess.sessionhandler = self.factory.server.sessions
sess.load_sync_data(data)
sess.load_sync_data(data)
if sess.logged_in and sess.uid:
# this can happen in the case of auto-authenticating protocols like SSH
sess.player = PlayerDB.objects.get_player_from_uid(sess.uid)
@ -348,24 +348,24 @@ class AMPProtocol(amp.AMP):
self.factory.server.sessions.portal_connect(sessid, sess)
elif operation == PDISCONN: #'portal_session_disconnect'
# session closed from portal side
# session closed from portal side
self.factory.server.sessions.portal_disconnect(sessid)
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
# to sync for each session.
sesslist = []
server_sessionhandler = self.factory.server.sessions
for sessid, sessdict in data.items():
for sessid, sessdict in data.items():
sess = ServerSession()
sess.sessionhandler = server_sessionhandler
sess.load_sync_data(sessdict)
if sess.uid:
sess.player = PlayerDB.objects.get_player_from_uid(sess.uid)
sesslist.append(sess)
sesslist.append(sess)
# 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)
init_mode = ServerConfig.objects.conf("server_restart_mode", default=None)
ScriptDB.objects.validate(init_mode=init_mode)
@ -373,7 +373,7 @@ class AMPProtocol(amp.AMP):
else:
raise Exception(_("operation %(op)s not recognized.") % {'op': operation})
return {}
ServerAdmin.responder(amp_server_admin)
@ -393,7 +393,7 @@ class AMPProtocol(amp.AMP):
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.
"""
data = loads(data)
@ -401,7 +401,7 @@ class AMPProtocol(amp.AMP):
#print "portaladmin (portal side):", sessid, operation, data
if operation == SLOGIN: # 'server_session_login'
# 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)
elif operation == SDISCONN: #'server_session_disconnect'
@ -415,14 +415,14 @@ class AMPProtocol(amp.AMP):
elif operation == SSHUTD: #server_shutdown'
# the server orders the portal to shut down
self.factory.portal.shutdown(restart=False)
elif operation == SSYNC: #'server_session_sync'
# server wants to save session data to the portal, maybe because
# it's about to shut down. We don't overwrite any sessions,
# just update data on them and remove eventual ones that are
# out of sync (shouldn't happen normally).
# it's about to shut down. We don't overwrite any sessions,
# just update data on them and remove eventual ones that are
# 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_delete = [sessid for sessid in data if sessid not in to_save]
@ -449,10 +449,3 @@ class AMPProtocol(amp.AMP):
sessid=sessid,
operation=operation,
data=data).addErrback(self.errback, "PortalAdmin")

View file

@ -11,7 +11,7 @@ from django.core import management
from django.conf import settings
from src.server.models import ServerConfig
from src.help.models import HelpEntry
from src.utils import create
from src.utils import create
# i18n
from django.utils.translation import ugettext as _
@ -19,7 +19,7 @@ from django.utils.translation import ugettext as _
def create_config_values():
"""
Creates the initial config values.
"""
"""
ServerConfig.objects.conf("site_name", settings.SERVERNAME)
ServerConfig.objects.conf("idle_timeout", settings.IDLE_TIMEOUT)
@ -33,28 +33,28 @@ def create_objects():
"""
Creates the #1 player and Limbo room.
"""
print _(" Creating objects (Player #1 and Limbo room) ...")
# 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()
# Create a Player 'user profile' object to hold eventual
# mud-specific settings for the bog standard User object. This is
# accessed by user.get_profile() and can also store attributes.
# It also holds mud permissions, but for a superuser these
# have no effect anyhow.
# have no effect anyhow.
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
# 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
# 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,
user=god_user,
god_character = create.create_player(god_user.username, None, None,
user=god_user,
create_character=True,
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.save()
# Limbo is the default "nowhere" starting room
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
# the creation hooks already fixed this).
if not god_character.location:
if not god_character.location:
god_character.location = limbo_obj
if not god_character.home:
god_character.home = limbo_obj
def create_channels():
"""
Creates some sensible default channels.
@ -97,7 +97,7 @@ def create_channels():
# public channel
key, aliases, desc, locks = settings.CHANNEL_PUBLIC
pchan = create.create_channel(key, aliases, desc, locks=locks)
# mudinfo channel
# mudinfo channel
key, aliases, desc, locks = settings.CHANNEL_MUDINFO
ichan = create.create_channel(key, aliases, desc, locks=locks)
# connectinfo channel
@ -110,29 +110,29 @@ def create_channels():
PlayerChannelConnection.objects.create_connection(goduser, pchan)
PlayerChannelConnection.objects.create_connection(goduser, ichan)
PlayerChannelConnection.objects.create_connection(goduser, cchan)
def import_MUX_help_files():
"""
Imports the MUX help files.
"""
"""
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.
default_category = "MUX"
print _(" Moving imported help db to help category '%(default)s'." \
% {'default': default_category})
HelpEntry.objects.all_to_category(default_category)
def create_system_scripts():
"""
Setup the system repeat scripts. They are automatically started
by the create_script function.
by the create_script function.
"""
from src.scripts import 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)
# validate all scripts in script table.
script2 = create.create_script(scripts.ValidateScripts)
@ -140,7 +140,7 @@ def create_system_scripts():
script3 = create.create_script(scripts.ValidateChannelHandler)
if not script1 or not script2 or not script3:
print _(" Error creating system scripts.")
def start_game_time():
"""
This starts a persistent script that keeps track of the
@ -155,7 +155,7 @@ def start_game_time():
def create_admin_media_links():
"""
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
want to mess with (use templates to customize the admin
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)
if os.path.isdir(apath):
print _(" ADMIN_MEDIA_ROOT already exists. Ignored.")
return
if os.name == 'nt':
return
if os.name == 'nt':
print _(" Admin-media files copied to ADMIN_MEDIA_ROOT (Windows mode).")
os.mkdir(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
if not modname:
return
try:
return
try:
mod = __import__(modname, fromlist=[None])
except ImportError, ValueError:
return
return
print _(" Running at_initial_setup() hook.")
if mod.__dict__.get("at_initial_setup", None):
mod.at_initial_setup()
mod.at_initial_setup()
def handle_setup(last_step):
"""
Main logic for the module. It allows for restarting
the initialization at any point if one of the modules
should crash.
the initialization at any point if one of the modules
should crash.
"""
if last_step < 0:
# this means we don't need to handle setup since
# it already ran sucessfully once.
# it already ran sucessfully once.
return
elif last_step == None:
# config doesn't exist yet. First start of server
last_step = 0
# setting up the list of functions to run
# setting up the list of functions to run
setup_queue = [
create_config_values,
create_objects,
create_config_values,
create_objects,
create_channels,
create_system_scripts,
start_game_time,
@ -224,17 +224,17 @@ def handle_setup(last_step):
at_initial_setup]
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.
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
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
# traceback we let it stop the system so the config
# step is not saved.
# step is not saved.
#print "%s..." % num
try:
@ -255,10 +255,10 @@ def handle_setup(last_step):
chan.delete()
for conn in PlayerChannelConnection.objects.all():
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
# 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):
"""
This ServerConfigManager implements methods for searching
This ServerConfigManager implements methods for searching
and manipulating ServerConfigs directly from the database.
These methods will all return database objects
These methods will all return database objects
(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.
Evennia-specific:
conf
conf
"""
def conf(self, key=None, value=None, delete=False, default=None):
@ -27,13 +27,13 @@ class ServerConfigManager(models.Manager):
elif delete == True:
for conf in self.filter(db_key=key):
conf.delete()
elif value != None:
elif value != None:
conf = self.filter(db_key=key)
if conf:
conf = conf[0]
else:
conf = self.model(db_key=key)
conf.value = value # this will pickle
conf = self.model(db_key=key)
conf.value = value # this will pickle
else:
conf = self.filter(db_key=key)
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.
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
MCCP = chr(86)
FLUSH = zlib.Z_SYNC_FLUSH
def mccp_compress(protocol, data):
"Handles zlib compression, if applicable"
"Handles zlib compression, if applicable"
if hasattr(protocol, 'zlib'):
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
return data
return data
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.
"""
def __init__(self, protocol):
"""
initialize MCCP by storing protocol on
ourselves and calling the client to see if
it supports MCCP. Sets callbacks to
start zlib compression in that case.
initialize MCCP by storing protocol on
ourselves and calling the client to see if
it supports MCCP. Sets callbacks to
start zlib compression in that case.
"""
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
self.protocol.will(MCCP).addCallbacks(self.do_mccp, self.no_mccp)
def no_mccp(self, option):
"""
If client doesn't support mccp, don't do anything.
"""
"""
if hasattr(self.protocol, 'zlib'):
del self.protocol.zlib
self.protocol.protocol_flags['MCCP'] = False
self.protocol.protocol_flags['MCCP'] = False
def do_mccp(self, option):
"""
The client supports MCCP. Set things up by
creating a zlib compression stream.
"""
self.protocol.protocol_flags['MCCP'] = True
The client supports MCCP. Set things up by
creating a zlib compression stream.
"""
self.protocol.protocol_flags['MCCP'] = True
self.protocol.requestNegotiation(MCCP, '')
self.protocol.zlib = zlib.compressobj(9)

View file

@ -4,8 +4,8 @@ Server Configuration flags
This holds persistent server configuration flags.
Config values should usually be set through the
manager's conf() method.
Config values should usually be set through the
manager's conf() method.
"""
try:
@ -26,10 +26,10 @@ from django.utils.translation import ugettext as _
# ServerConfig
#
#------------------------------------------------------------
class ServerConfig(SharedMemoryModel):
"""
On-the fly storage of global settings.
On-the fly storage of global settings.
Properties defined on ServerConfig:
key - main identifier
@ -50,15 +50,15 @@ class ServerConfig(SharedMemoryModel):
db_value = models.TextField(blank=True)
objects = ServerConfigManager()
# Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using
# normal python operations (without having to remember to save()
# etc). So e.g. a property 'attr' has a get/set/del decorator
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# defined that allows the user to do self.attr = value,
# value = self.attr and del self.attr respectively (where self
# is the object in question).
# key property (wraps db_key)
#@property
def key_get(self):
@ -86,12 +86,12 @@ class ServerConfig(SharedMemoryModel):
if utils.has_parent('django.db.models.base.Model', value):
# we have to protect against storing db objects.
logger.log_errmsg(_("ServerConfig cannot store db objects! (%s)" % value))
return
return
self.db_value = pickle.dumps(value)
self.save()
#@value.deleter
def value_del(self):
"Deleter. Allows for del self.value. Deletes entry."
"Deleter. Allows for del self.value. Deletes entry."
self.delete()
value = property(value_get, value_set, value_del)
@ -100,8 +100,8 @@ class ServerConfig(SharedMemoryModel):
verbose_name = "Server Config value"
verbose_name_plural = "Server Config values"
#
# ServerConfig other methods
#
# ServerConfig other methods
#
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):
"""
Implements the MSDP protocol.
Implements the MSDP protocol.
"""
def __init__(self, protocol):
"""
Initiates by storing the protocol
on itself and trying to determine
if the client supports MSDP.
if the client supports MSDP.
"""
self.protocol = protocol
self.protocol.protocol_FLAGS['MSDP'] = False
@ -45,39 +45,39 @@ class Msdp(object):
def no_msdp(self, option):
"No msdp"
pass
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):
"""
handle return data from cmdname by converting it to
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),
or a dictionary (will be converted to MSDP_TABLE).
handle return data from cmdname by converting it to
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),
or a dictionary (will be converted to MSDP_TABLE).
OBS - this supports nested tables and even arrays nested
inside tables, as opposed to the receive method. Arrays
OBS - this supports nested tables and even arrays nested
inside tables, as opposed to the receive method. Arrays
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."
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({}):
string += make_table(key, val, string)
elif hasattr(val, '__iter__'):
string += make_array(key, val, string)
elif hasattr(val, '__iter__'):
string += make_array(key, val, string)
else:
string += MSDP_VAR + key + MSDP_VAL + val
string += MSDP_TABLE_CLOSE
return string
string += MSDP_TABLE_CLOSE
return string
def make_array(name, string, datalist):
"build a simple array. Arrays may not nest tables by definition."
@ -85,23 +85,23 @@ class Msdp(object):
for val in datalist:
string += MSDP_VAL + val
string += MSDP_ARRAY_CLOSE
return string
return string
if type(data) == type({}):
if type(data) == type({}):
msdp_string = make_table(cmdname, data, "")
elif hasattr(data, '__iter__'):
msdp_string = make_array(cmdname, data, "")
else:
msdp_string = MSDP_VAR + cmdname + MSDP_VAL + data
return msdp_string
return msdp_string
def msdp_to_func(self, data):
"""
Handle a client's requested negotiation, converting
it into a function mapping
OBS-this does not support receiving nested tables
from the client at this point!
OBS-this does not support receiving nested tables
from the client at this point!
"""
tables = {}
arrays = {}
@ -112,23 +112,23 @@ class Msdp(object):
for array in regex_array.findall(data):
arrays[array[0]] = dict(regex_varval(array[1]))
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.
# See http://tintin.sourceforge.net/msdp/ for definitions of each command.
# These are client->server commands.
# See http://tintin.sourceforge.net/msdp/ for definitions of each command.
# These are client->server commands.
def msdp_cmd_list(self, arg):
"""
The List command allows for retrieving various info about the server/client
"""
"""
if arg == 'COMMANDS':
return self.func_to_msdp(arg, MSDP_COMMANDS.keys())
elif arg == 'LISTS':
return self.func_to_msdp(arg, ("COMMANDS", "LISTS",
"CONFIGURABLE_VARIABLES",
return self.func_to_msdp(arg, ("COMMANDS", "LISTS",
"CONFIGURABLE_VARIABLES",
"REPORTED_VARIABLES", "SENDABLE_VARIABLES"))
elif arg == 'CONFIGURABLE_VARIABLES':
return self.func_to_msdp(arg, ("CLIENT_NAME", "CLIENT_VERSION", "PLUGIN_ID"))
@ -149,7 +149,7 @@ class Msdp(object):
try:
MSDP_REPORTABLE[arg](report=True)
except Exception:
logger.log_trace()
logger.log_trace()
def msdp_cmd_unreport(self, arg):
"""
@ -159,7 +159,7 @@ class Msdp(object):
MSDP_REPORTABLE[arg](eport=False)
except Exception:
logger.log_trace()
def msdp_cmd_reset(self, arg):
"""
The reset command resets a variable to its initial state.
@ -167,12 +167,12 @@ class Msdp(object):
try:
MSDP_REPORTABLE[arg](reset=True)
except Exception:
logger.log_trace()
logger.log_trace()
def msdp_cmd_send(self, arg):
"""
Request the server to send a particular variable
to the client.
to the client.
arg - this is a list of variables the client wants.
"""
@ -181,8 +181,8 @@ class Msdp(object):
try:
ret.append(MSDP_REPORTABLE[arg](send=True))
except Exception:
logger.log_trace()
return ret
logger.log_trace()
return ret
MSDP_COMMANDS = {
"LIST": self.msdp_list,
@ -192,57 +192,57 @@ class Msdp(object):
"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
# is up to these commands to return data on proper form.
# is up to these commands to return data on proper form.
MSDP_REPORTABLE = {
# General
"CHARACTER_NAME": "get_character_name",
"SERVER_ID": "get_server_id",
"SERVER_TIME": "get_server_time",
# Character
# Character
"AFFECTS": "char_affects",
"ALIGNMENT": "char_alignment",
"EXPERIENCE": "char_experience",
"EXPERIENCE_MAX": "char_experience_max",
"EXPERIENCE_TNL": "char_experience_tnl",
"HEALTH": "char_health",
"HEALTH_MAX": "char_health_max",
"LEVEL": "char_level",
"HEALTH": "char_health",
"HEALTH_MAX": "char_health_max",
"LEVEL": "char_level",
"RACE": "char_race",
"CLASS": "char_class",
"MANA": "char_mana",
"CLASS": "char_class",
"MANA": "char_mana",
"MANA_MAX": "char_mana_max",
"WIMPY": "char_wimpy",
"PRACTICE": "char_practice",
"MONEY": "char_money",
"WIMPY": "char_wimpy",
"PRACTICE": "char_practice",
"MONEY": "char_money",
"MOVEMENT": "char_movement",
"MOVEMENT_MAX": "char_movement_max",
"HITROLL": "char_hitroll",
"DAMROLL": "char_damroll",
"HITROLL": "char_hitroll",
"DAMROLL": "char_damroll",
"AC": "char_ac",
"STR": "char_str",
"INT": "char_int",
"STR": "char_str",
"INT": "char_int",
"WIS": "char_wis",
"DEX": "char_dex",
"CON": "char_con",
# Combat
"DEX": "char_dex",
"CON": "char_con",
# Combat
"OPPONENT_HEALTH": "opponent_health",
"OPPONENT_HEALTH_MAX":"opponent_health_max",
"OPPONENT_LEVEL": "opponent_level",
"OPPONENT_NAME": "opponent_name",
# World
# World
"AREA_NAME": "area_name",
"ROOM_EXITS": "area_room_exits",
"ROOM_NAME": "room_name",
"ROOM_VNUM": "room_dbref",
"ROOM_NAME": "room_name",
"ROOM_VNUM": "room_dbref",
"WORLD_TIME": "world_time",
# Configurable variables
"CLIENT_ID": "client_id",
# Configurable variables
"CLIENT_ID": "client_id",
"CLIENT_VERSION": "client_version",
"PLUGIN_ID": "plugin_id",
"ANSI_COLORS": "ansi_colours",
@ -250,13 +250,13 @@ class Msdp(object):
"UTF_8": "utf_8",
"SOUND": "sound",
"MXP": "mxp",
# GUI variables
# GUI variables
"BUTTON_1": "button1",
"BUTTON_2": "button2",
"BUTTON_3": "button3",
"BUTTON_4": "button4",
"BUTTON_5": "button5",
"BUTTON_5": "button5",
"GAUGE_1": "gauge1",
"GAUGE_2": "gauge2",
"GAUGE_3": "gauge3",

View file

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

View file

@ -1,6 +1,6 @@
"""
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
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 _
#------------------------------------------------------------
# Evennia Portal settings
# Evennia Portal settings
#------------------------------------------------------------
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
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_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_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):
@ -69,17 +69,17 @@ class Portal(object):
The main Portal server handler. This object sets up the database and
tracks and interlinks all the twisted network services that make up
Portal.
"""
"""
def __init__(self, application):
"""
Setup the server.
Setup the server.
application - an instantiated Twisted application
"""
"""
sys.path.append('.')
# create a store of services
self.services = service.IServiceCollection(application)
self.amp_protocol = None # set by amp factory
@ -88,25 +88,25 @@ class Portal(object):
print '\n' + '-'*50
# Make info output to the terminal.
# Make info output to the terminal.
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.
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True)
self.game_running = False
def terminal_output(self):
"""
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:
print " amp (Server): %s" % AMP_PORT
if TELNET_ENABLED:
if TELNET_ENABLED:
ports = ", ".join([str(port) for port in TELNET_PORTS])
ifaces = ",".join([" %s" % iface for iface in TELNET_INTERFACES if iface != '0.0.0.0'])
print " telnet%s: %s" % (ifaces, ports)
@ -129,11 +129,11 @@ class Portal(object):
def set_restart_mode(self, mode=None):
"""
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 == None:
return
return
f = open(PORTAL_RESTART, 'w')
print _("writing mode=%(mode)s to %(portal_restart)s") % {'mode': mode, 'portal_restart': PORTAL_RESTART}
f.write(str(mode))
@ -141,13 +141,13 @@ class Portal(object):
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
restarted or not. If None, the current flag setting
(set at initialization or previous runs) is used.
_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
if the Portal is currently running in daemon mode. In that
@ -157,7 +157,7 @@ class Portal(object):
if not _abrupt:
reactor.callLater(0, reactor.stop)
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)
#------------------------------------------------------------
@ -170,15 +170,15 @@ class Portal(object):
# what to execute from.
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.
PORTAL = Portal(application)
if AMP_ENABLED:
if AMP_ENABLED:
# The AMP protocol handles the communication between
# 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
@ -188,7 +188,7 @@ if AMP_ENABLED:
PORTAL.services.addService(amp_client)
# 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:
@ -200,7 +200,7 @@ if TELNET_ENABLED:
ifacestr = ""
if interface != '0.0.0.0' or len(TELNET_INTERFACES) > 1:
ifacestr = "-%s" % interface
for port in TELNET_PORTS:
for port in TELNET_PORTS:
pstring = "%s:%s" % (ifacestr, port)
factory = protocol.ServerFactory()
factory.protocol = telnet.TelnetProtocol
@ -219,7 +219,7 @@ if SSL_ENABLED:
ifacestr = ""
if interface != '0.0.0.0' or len(SSL_INTERFACES) > 1:
ifacestr = "-%s" % interface
for port in SSL_PORTS:
for port in SSL_PORTS:
pstring = "%s:%s" % (ifacestr, port)
factory = protocol.ServerFactory()
factory.sessionhandler = PORTAL_SESSIONS
@ -231,7 +231,7 @@ if SSL_ENABLED:
if SSH_ENABLED:
# Start SSH game connections. Will create a keypair in evennia/game if necessary.
from src.server import ssh
for interface in SSH_INTERFACES:
@ -242,7 +242,7 @@ if SSH_ENABLED:
pstring = "%s:%s" % (ifacestr, port)
factory = ssh.makeFactory({'protocolFactory':ssh.SshProtocol,
'protocolArgs':(),
'sessions':PORTAL_SESSIONS})
'sessions':PORTAL_SESSIONS})
ssh_service = internet.TCPServer(port, factory, interface=interface)
ssh_service.setName('EvenniaSSH%s' % pstring)
PORTAL.services.addService(ssh_service)
@ -254,14 +254,14 @@ if WEBSERVER_ENABLED:
from twisted.python import threadpool
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
threads = threadpool.ThreadPool()
web_root = DjangoWebRoot(threads)
# point our media resources to url /media
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
# point our media resources to url /media
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
if WEBCLIENT_ENABLED:
if WEBCLIENT_ENABLED:
# create ajax client processes at /webclientdata
from src.server.webclient import WebClient
webclient = WebClient()

View file

@ -1,6 +1,6 @@
"""
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
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.internet import protocol, reactor, defer
from twisted.web import server, static
import django
import django
from django.db import connection
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.
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)
# i18n
from django.utils.translation import ugettext as _
#------------------------------------------------------------
# Evennia Server settings
# Evennia Server settings
#------------------------------------------------------------
SERVERNAME = settings.SERVERNAME
VERSION = get_evennia_version()
AMP_ENABLED = True
AMP_ENABLED = True
AMP_HOST = settings.AMP_HOST
AMP_PORT = settings.AMP_PORT
@ -61,7 +61,7 @@ IRC_ENABLED = settings.IRC_ENABLED
RSS_ENABLED = settings.RSS_ENABLED
#------------------------------------------------------------
# Evennia Main Server object
# Evennia Main Server object
#------------------------------------------------------------
class Evennia(object):
@ -69,15 +69,15 @@ class Evennia(object):
The main Evennia server handler. This object sets up the database and
tracks and interlinks all the twisted network services that make up
evennia.
"""
"""
def __init__(self, application):
"""
Setup the server.
Setup the server.
application - an instantiated Twisted application
"""
"""
sys.path.append('.')
# create a store of services
@ -85,44 +85,44 @@ class Evennia(object):
self.amp_protocol = None # set by amp factory
self.sessions = SESSIONS
self.sessions.server = self
print '\n' + '-'*50
# Database-specific startup optimizations.
self.sqlite3_prep()
# Run the initial setup if needed
# Run the initial setup if needed
self.run_initial_setup()
self.start_time = time.time()
# initialize channelhandler
channelhandler.CHANNELHANDLER.update()
# Make info output to the terminal.
# Make info output to the terminal.
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.
reactor.addSystemEventTrigger('before', 'shutdown', self.shutdown, _abrupt=True)
self.game_running = True
self.run_init_hooks()
# Server startup methods
def sqlite3_prep(self):
"""
Optimize some SQLite stuff at startup since we
can't save it to the database.
"""
"""
if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASE_ENGINE == "sqlite3")
or (hasattr(settings, 'DATABASES')
and settings.DATABASES.get("default", {}).get('ENGINE', None)
== 'django.db.backends.sqlite3')):
or (hasattr(settings, 'DATABASES')
and settings.DATABASES.get("default", {}).get('ENGINE', None)
== 'django.db.backends.sqlite3')):
cursor = connection.cursor()
cursor.execute("PRAGMA cache_size=10000")
cursor.execute("PRAGMA synchronous=OFF")
@ -147,7 +147,7 @@ class Evennia(object):
# the last failed module. When all are finished, the step
# is set to -1 to show it does not need to be run again.
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))
print '-'*50
@ -164,25 +164,25 @@ class Evennia(object):
# call server hook.
if SERVER_HOOK_MODULE:
SERVER_HOOK_MODULE.at_server_start()
SERVER_HOOK_MODULE.at_server_start()
def terminal_output(self):
"""
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
def set_restart_mode(self, mode=None):
"""
This manages the flag file that tells the runner if the server is
reloading, resetting or shutting down. Valid modes are
'reload', 'reset', 'shutdown' and None.
reloading, resetting or shutting down. Valid modes are
'reload', 'reset', 'shutdown' and None.
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.
"""
"""
if mode == None:
if os.path.exists(SERVER_RESTART) and 'True' == open(SERVER_RESTART, 'r').read():
mode = 'reload'
@ -197,17 +197,17 @@ class Evennia(object):
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.
'reset' - server restarts, non-persistent scripts stopped, at_shutdown hooks called.
'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,
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
@ -217,7 +217,7 @@ class Evennia(object):
if mode == 'reload':
# 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()]
[(s.typeclass, s.pause(), s.at_server_reload()) for s in ScriptDB.get_all_cached_instances()]
@ -226,23 +226,23 @@ class Evennia(object):
else:
if mode == '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
[(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()]
[(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")
if not _abrupt:
if SERVER_HOOK_MODULE:
SERVER_HOOK_MODULE.at_server_stop()
reactor.callLater(0, reactor.stop)
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)
#------------------------------------------------------------
#
# 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
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
# what to execute from.
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.
EVENNIA = Evennia(application)
# The AMP protocol handles the communication between
# 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
@ -278,7 +278,7 @@ if IRC_ENABLED:
# IRC channel connections
from src.comms import irc
from src.comms import irc
irc.connect_all()
if IMC2_ENABLED:
@ -289,10 +289,10 @@ if IMC2_ENABLED:
imc2.connect_all()
if RSS_ENABLED:
# RSS feed channel connections
# RSS feed channel connections
from src.comms import rss
rss.connect_all()
rss.connect_all()
# clear server startup mode
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
a connection to the outside world but don't know any details about how the
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
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
are stored on the Portal side)
"""
import time
import time
from datetime import datetime
from django.conf import settings
from src.scripts.models import ScriptDB
@ -16,7 +16,7 @@ from src.utils import logger, utils
from src.commands import cmdhandler, cmdsethandler
from src.server.session import Session
IDLE_COMMAND = settings.IDLE_COMMAND
IDLE_COMMAND = settings.IDLE_COMMAND
# load optional out-of-band function module
OOB_FUNC_MODULE = settings.OOB_FUNC_MODULE
@ -33,14 +33,14 @@ from django.utils.translation import ugettext as _
class ServerSession(Session):
"""
This class represents a player's session and is a template for
individual protocols to communicate with Evennia.
This class represents a player's session and is a template for
individual protocols to communicate with Evennia.
Each player gets a session assigned to them whenever they connect
to the game server. All communication between game and player goes
through their session.
"""
"""
def at_sync(self):
"""
This is called whenever a session has been resynced with the portal.
@ -48,13 +48,13 @@ class ServerSession(Session):
been assigned (if applicable).
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:
# assign the unloggedin-command set.
self.cmdset = cmdsethandler.CmdSetHandler(self)
self.cmdset_storage = [settings.CMDSET_UNLOGGEDIN]
self.cmdset.update(init_mode=True)
self.cmdset.update(init_mode=True)
self.cmdset.update(init_mode=True)
return
@ -80,29 +80,29 @@ class ServerSession(Session):
self.uname = self.user.username
self.logged_in = True
self.conn_time = time.time()
# Update account's last login time.
self.user.last_login = datetime.now()
self.user.save()
self.user.last_login = datetime.now()
self.user.save()
# player init
#print "at_init() - player"
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:
player.at_first_login()
del player.db.FIRST_LOGIN
player.at_pre_login()
player.at_pre_login()
character = player.character
if character:
if character:
# this player has a character. Check if it's the
# first time *this character* logs in
character.at_init()
if character.db.FIRST_LOGIN:
character.at_first_login()
del character.db.FIRST_LOGIN
del character.db.FIRST_LOGIN
# run character login hook
character.at_pre_login()
@ -110,12 +110,12 @@ class ServerSession(Session):
# start (persistent) scripts on this object
ScriptDB.objects.validate(obj=self.player.character)
#add session to connected list
self.sessionhandler.login(self)
# post-login hooks
player.at_post_login()
# post-login hooks
player.at_post_login()
if character:
character.at_post_login()
@ -125,15 +125,15 @@ class ServerSession(Session):
accounting. This method is used also for non-loggedin
accounts.
"""
if self.logged_in:
if self.logged_in:
player = self.get_player()
character = self.get_character()
if character:
character.at_disconnect()
uaccount = player.user
uaccount.last_login = datetime.now()
uaccount.save()
self.logged_in = False
uaccount.save()
self.logged_in = False
self.sessionhandler.disconnect(self)
def get_player(self):
@ -143,8 +143,8 @@ class ServerSession(Session):
if self.logged_in:
return self.player
else:
return None
return None
def get_character(self):
"""
Returns the in-game character associated with this session.
@ -153,12 +153,12 @@ class ServerSession(Session):
player = self.get_player()
if player:
return player.character
return None
return None
def log(self, message, channel=True):
"""
Emits session info to the appropriate outputs and info channels.
"""
"""
if channel:
try:
cchan = settings.CHANNEL_CONNECTINFO
@ -171,7 +171,7 @@ class ServerSession(Session):
def update_session_counters(self, idle=False):
"""
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.
self.cmd_last = time.time()
@ -187,24 +187,24 @@ class ServerSession(Session):
"""
# handle the 'idle' command
if str(command_string).strip() == IDLE_COMMAND:
self.update_session_counters(idle=True)
return
self.update_session_counters(idle=True)
return
# all other inputs, including empty inputs
character = self.get_character()
character = self.get_character()
if character:
character.execute_cmd(command_string)
else:
if self.logged_in:
# there is no character, but we are logged in. Use player instead.
self.get_player().execute_cmd(command_string)
else:
# we are not logged in. Use the session directly
self.get_player().execute_cmd(command_string)
else:
# we are not logged in. Use the session directly
# (it uses the settings.UNLOGGEDIN cmdset)
cmdhandler.cmdhandler(self, command_string)
self.update_session_counters()
self.update_session_counters()
def data_out(self, msg, data=None):
"""
@ -218,25 +218,25 @@ class ServerSession(Session):
This receives out-of-band data from the Portal.
This method parses the data input (a dict) and uses
it to launch correct methods from those plugged into
the system.
it to launch correct methods from those plugged into
the system.
data = {funcname: ( [args], {kwargs]),
funcname: ( [args], {kwargs}), ...}
example:
example:
data = {"get_hp": ([], {}),
"update_counter", (["counter1"], {"now":True}) }
"""
print "server: "
outdata = {}
entity = self.get_character()
if not entity:
entity = self.get_player()
if not entity:
entity = self
entity = self
for funcname, argtuple in data.items():
# loop through the data, calling available functions.
@ -262,19 +262,19 @@ class ServerSession(Session):
def __eq__(self, other):
return self.address == other.address
def __str__(self):
"""
String representation of the user session class. We use
this a lot in the server logs.
"""
symbol = ""
if self.logged_in and hasattr(self, "player") and self.player:
symbol = "(#%s)" % self.player.id
if self.logged_in and hasattr(self, "player") and self.player:
symbol = "(#%s)" % self.player.id
try:
address = ":".join([str(part) for part in self.address])
address = ":".join([str(part) for part in self.address])
except Exception:
address = self.address
address = self.address
return "%s%s@%s" % (self.uname, symbol, address)
def __unicode__(self):
@ -298,7 +298,7 @@ class ServerSession(Session):
# Dummy API hooks for use a non-loggedin operation
def at_cmdset_get(self):
"dummy hook all objects with cmdsets need to have"
pass
@ -306,11 +306,11 @@ class ServerSession(Session):
# Mock db/ndb properties for allowing easy storage on the session
# (note that no databse is involved at all here. session.db.attr =
# value just saves a normal property in memory, just like ndb).
#@property
def ndb_get(self):
"""
A non-persistent store (ndb: NonDataBase). Everything stored
A non-persistent store (ndb: NonDataBase). Everything stored
to this is guaranteed to be cleared when a server is shutdown.
Syntax is same as for the _get_db_holder() method and
property, e.g. obj.ndb.attr = value etc.
@ -321,14 +321,14 @@ class ServerSession(Session):
class NdbHolder(object):
"Holder for storing non-persistent attributes."
def all(self):
return [val for val in self.__dict__.keys()
if not val.startswith['_']]
return [val for val in self.__dict__.keys()
if not val.startswith['_']]
def __getattribute__(self, key):
# return None if no matching attribute was found.
# return None if no matching attribute was found.
try:
return object.__getattribute__(self, key)
except AttributeError:
return None
return None
self._ndb_holder = NdbHolder()
return self._ndb_holder
#@ndb.setter
@ -348,4 +348,4 @@ class ServerSession(Session):
# at this stage, so we just present a uniform API)
def access(self, *args, **kwargs):
"Dummy method."
return True
return True

View file

@ -1,10 +1,10 @@
"""
This defines a generic session class. All connection instances (both
on Portal and Server side) should inherit from this class.
This defines a generic session class. All connection instances (both
on Portal and Server side) should inherit from this class.
"""
import time
import time
#------------------------------------------------------------
# Server Session
@ -21,27 +21,27 @@ class Session(object):
protocols that Evennia supports, like Telnet, SSH etc. The Portal session
must call init_session() as part of its initialization. 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
of how they connect.
of how they connect.
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
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.
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding',
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
'server_data']
_attrs_to_sync = ['protocol_key', 'address', 'suid', 'sessid', 'uid', 'uname',
'logged_in', 'cid', 'encoding',
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
'server_data']
def init_session(self, protocol_key, address, sessionhandler):
"""
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
address - client address
sessionhandler - reference to the sessionhandler instance
@ -50,24 +50,24 @@ class Session(object):
self.protocol_key = protocol_key
# Protocol address tied to this session
self.address = address
# suid is used by some protocols, it's a hex key.
self.suid = None
# unique id for this session
self.suid = None
# unique id for this session
self.sessid = 0 # no sessid yet
# database id for the user connected to this session
self.uid = None
# user name, for easier tracking of sessions
self.uname = None
self.uname = None
# if user has authenticated already or not
self.logged_in = False
# database id of character/object connected to this player session (if any)
self.cid = None
self.cid = None
self.encoding = "utf-8"
# session time statistics
# session time statistics
self.conn_time = time.time()
self.cmd_last_visible = self.conn_time
self.cmd_last = self.conn_time
@ -76,10 +76,10 @@ class Session(object):
self.protocol_flags = {}
self.server_data = {}
# a back-reference to the relevant sessionhandler this
# session is stored in.
# a back-reference to the relevant sessionhandler this
# session is stored in.
self.sessionhandler = sessionhandler
def get_sync_data(self):
"""
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,
and loads it into the correct attributes of the session.
"""
"""
for attrname, value in sessdata.items():
self.__dict__[attrname] = value
self.__dict__[attrname] = value
def at_sync(self):
"""
Called after a session has been fully synced (including
secondary operations such as setting self.player based
on uid etc).
secondary operations such as setting self.player based
on uid etc).
"""
pass
@ -112,13 +112,13 @@ class Session(object):
generic hook called from the outside to disconnect this session
should be connected to the protocols actual disconnect mechanism.
"""
pass
pass
def data_out(self, msg, data=None):
"""
generic hook for sending data out through the protocol. Server
protocols can use this right away. Portal sessions
should overload this to format/handle the outgoing data as needed.
generic hook for sending data out through the protocol. Server
protocols can use this right away. Portal sessions
should overload this to format/handle the outgoing data as needed.
"""
pass
@ -126,8 +126,8 @@ class Session(object):
"""
hook for protocols to send incoming data to the engine.
"""
pass
pass
def oob_data_out(self, data):
"""
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 Server, this receives data from Portal.
data is a dictionary
"""
pass

View file

@ -1,33 +1,33 @@
"""
This module defines handlers for storing sessions when handles
sessions of users connecting to the server.
This module defines handlers for storing sessions when handles
sessions of users connecting to the server.
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
how they are connected to the world.
how they are connected to the world.
PortalSessionHandler - this stores sessions created by
twisted protocols. These are dumb connectors that
handle network communication but holds no game info.
"""
import time
from django.conf import settings
from django.contrib.auth.models import User
from src.server.models import ServerConfig
from src.utils import utils
from src.utils import utils
from src.commands.cmdhandler import CMD_LOGINSTART
# AMP signals
# AMP signals
PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync
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
SSHUTD = chr(7) # server shutdown
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
# i18n
@ -43,7 +43,7 @@ class SessionHandler(object):
"""
def __init__(self):
"""
Init the handler.
Init the handler.
"""
self.sessions = {}
@ -64,13 +64,13 @@ class SessionHandler(object):
def get_all_sync_data(self):
"""
Create a dictionary of sessdata dicts representing all
sessions in store.
Create a dictionary of sessdata dicts representing all
sessions in store.
"""
sessdict = {}
for sess in self.sessions.values():
# copy all relevant data from all sessions
sessdict[sess.sessid] = sess.get_sync_data()
sessdict[sess.sessid] = sess.get_sync_data()
return sessdict
#------------------------------------------------------------
@ -80,13 +80,13 @@ class SessionHandler(object):
class ServerSessionHandler(SessionHandler):
"""
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
registering itself with the connect() method. This indicates an
non-authenticated session. Whenever the session is authenticated
the session together with the related player is sent to the login()
method.
method.
"""
@ -94,15 +94,15 @@ class ServerSessionHandler(SessionHandler):
def __init__(self):
"""
Init the handler.
Init the handler.
"""
self.sessions = {}
self.server = None
self.server = None
self.server_data = {"servername":settings.SERVERNAME}
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.
"""
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
by the portal when reconnecting.
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.
"""
"""
for sess in self.sessions.values():
# we delete the old session to make sure to catch eventual lingering references.
del sess
@ -136,15 +136,15 @@ class ServerSessionHandler(SessionHandler):
def portal_shutdown(self):
"""
Called by server when shutting down the portal.
"""
"""
self.server.amp_protocol.call_remote_PortalAdmin(0,
operation=SSHUTD,
data="")
# server-side access methods
data="")
# server-side access methods
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.
"""
session = self.sessions.get(session.sessid, None)
@ -157,7 +157,7 @@ class ServerSessionHandler(SessionHandler):
data=reason)
self.session_count(-1)
def login(self, session):
"""
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.
"""
# prep the session with player/user info
if not ALLOW_MULTISESSION:
# disconnect previous sessions.
self.disconnect_duplicate_sessions(session)
session.logged_in = True
session.logged_in = True
self.session_count(1)
# sync the portal to this session
sessdata = session.get_sync_data()
self.server.amp_protocol.call_remote_PortalAdmin(session.sessid,
operation=SLOGIN,
data=sessdata)
def session_sync(self):
"""
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()
self.server.amp_protocol.call_remote_PortalAdmin(0,
@ -192,7 +192,7 @@ class ServerSessionHandler(SessionHandler):
"""
Cleanly disconnect all of the connected sessions.
"""
for session in self.sessions:
del session
self.session_count(0)
@ -203,42 +203,42 @@ class ServerSessionHandler(SessionHandler):
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()
doublet_sessions = [sess for sess in self.sessions
if sess.logged_in
if sess.logged_in
and sess.get_character() == curr_char
and sess != curr_session]
for sessid in doublet_sessions:
self.disconnect(session, reason)
self.disconnect(session, reason)
self.session_count(-1)
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.
"""
tcurr = time.time()
reason= _("Idle timeout exceeded, disconnecting.")
for session in (session for session in self.sessions.values()
if session.logged_in and IDLE_TIMEOUT > 0
for session in (session for session in self.sessions.values()
if session.logged_in and IDLE_TIMEOUT > 0
and (tcurr - session.cmd_last) > IDLE_TIMEOUT):
self.disconnect(session, reason=reason)
self.session_count(-1)
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.
num can be a positive or negative value to be added to the current count.
If 0, the counter will be reset to 0.
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 num == None:
# show the current value. This also syncs it.
return int(ServerConfig.objects.conf('nr_sessions', default=0))
# show the current value. This also syncs it.
return int(ServerConfig.objects.conf('nr_sessions', default=0))
elif num == 0:
# reset value to 0
ServerConfig.objects.conf('nr_sessions', 0)
@ -255,7 +255,7 @@ class ServerSessionHandler(SessionHandler):
Only logged-in players are counted here.
"""
return len(set(session.uid for session in self.sessions.values() if session.logged_in))
def sessions_from_player(self, player):
"""
Given a player, return any matching sessions.
@ -275,7 +275,7 @@ class ServerSessionHandler(SessionHandler):
player = character.player
if player:
return self.sessions_from_player(player)
return None
return None
def announce_all(self, message):
@ -295,15 +295,15 @@ class ServerSessionHandler(SessionHandler):
def data_in(self, sessid, string="", data=""):
"""
Data Portal -> Server
"""
session = self.sessions.get(sessid, None)
if session:
"""
session = self.sessions.get(sessid, None)
if session:
session.execute_cmd(string)
# ignore 'data' argument for now; this is otherwise the place
# 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):
"""
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.
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
to the server using the AMP connection.
to the server using the AMP connection.
"""
@ -339,7 +339,7 @@ class PortalSessionHandler(SessionHandler):
"""
Init the handler
"""
self.portal = None
self.portal = None
self.sessions = {}
self.latest_sessid = 0
self.uptime = time.time()
@ -351,19 +351,19 @@ class PortalSessionHandler(SessionHandler):
Server. At this point, the AMP connection is already
established.
"""
self.connection_time = time.time()
self.connection_time = time.time()
def connect(self, 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
sessid = self.latest_sessid
session.sessid = sessid
sessdata = session.get_sync_data()
self.sessions[sessid] = session
# sync with server-side
# sync with server-side
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
operation=PCONN,
data=sessdata)
@ -374,23 +374,23 @@ class PortalSessionHandler(SessionHandler):
sessid = session.sessid
self.portal.amp_protocol.call_remote_ServerAdmin(sessid,
operation=PDISCONN)
def server_disconnect(self, sessid, reason=""):
"""
Called by server to force a disconnect by sessid
"""
session = self.sessions.get(sessid, None)
if session:
session.disconnect(reason)
del session
session.disconnect(reason)
del session
def server_disconnect_all(self, reason=""):
"""
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)
del session
del session
def count_loggedin(self, include_unloggedin=False):
@ -398,22 +398,22 @@ class PortalSessionHandler(SessionHandler):
Count loggedin connections, alternatively count all connections.
"""
return len(self.get_sessions(include_unloggedin=include_unloggedin))
def session_from_suid(self, suid):
"""
Given a session id, retrieve the session (this is primarily
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]
def data_in(self, session, string="", data=""):
"""
Called by portal sessions for relaying data coming
in from the protocol to the server. data is
serialized before passed on.
"""
Called by portal sessions for relaying data coming
in from the protocol to the server. data is
serialized before passed on.
"""
self.portal.amp_protocol.call_remote_MsgPortal2Server(session.sessid,
msg=string,
data=data)
@ -426,12 +426,12 @@ class PortalSessionHandler(SessionHandler):
def data_out(self, sessid, string="", data=""):
"""
Called by server for having the portal relay messages and data
to the correct session protocol.
Called by server for having the portal relay messages and data
to the correct session protocol.
"""
session = self.sessions.get(sessid, None)
if session:
session.data_out(string, data=data)
session.data_out(string, data=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
sessions etc.
Using standard ssh client,
Using standard ssh client,
"""
import os
@ -65,11 +65,11 @@ class SshProtocol(Manhole, session.Session):
# initialize the session
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:
self.logged_in = True
self.logged_in = True
self.uid = self.authenticated_player.user.id
self.sessionhandler.connect(self)
@ -82,8 +82,8 @@ class SshProtocol(Manhole, session.Session):
self.keyHandlers[CTRL_C] = self.handle_INT
self.keyHandlers[CTRL_D] = self.handle_EOF
self.keyHandlers[CTRL_L] = self.handle_FF
self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
# initalize
def handle_INT(self):
@ -128,8 +128,8 @@ class SshProtocol(Manhole, session.Session):
def connectionLost(self, reason=None):
"""
This is executed when the connection is lost for
whatever reason. It can also be called directly,
from the disconnect method.
whatever reason. It can also be called directly,
from the disconnect method.
"""
insults.TerminalProtocol.connectionLost(self, reason)
@ -186,7 +186,7 @@ class SshProtocol(Manhole, session.Session):
self.lineSend(str(e))
return
nomarkup = False
raw = False
raw = False
if type(data) == dict:
# check if we want escape codes to go through unparsed.
raw = data.get("raw", False)
@ -230,7 +230,7 @@ class PlayerDBPasswordChecker(object):
username = up.username
password = up.password
player = PlayerDB.objects.get_player_from_name(username)
res = (None, self.factory)
res = (None, self.factory)
if player and player.user.check_password(password):
res = (player, self.factory)
return defer.succeed(res)
@ -290,13 +290,13 @@ class TerminalSessionTransport_getPeer:
def getKeyPair(pubkeyfile, privkeyfile):
"""
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)):
# No keypair exists. Generate a new RSA keypair
print _(" Generating SSH RSA keypair ..."),
from Crypto.PublicKey import RSA
from Crypto.PublicKey import RSA
KEY_LENGTH = 1024
rsaKey = Key(RSA.generate(KEY_LENGTH))

View file

@ -1,6 +1,6 @@
"""
This is a simple context factory for auto-creating
SSL keys and certificates.
This is a simple context factory for auto-creating
SSL keys and certificates.
"""
import os, sys
@ -16,21 +16,21 @@ from src.server.telnet import TelnetProtocol
class SSLProtocol(TelnetProtocol):
"""
Communication is the same as telnet, except data transfer
is done with encryption.
is done with encryption.
"""
pass
def verify_SSL_key_and_cert(keyfile, certfile):
"""
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.
"""
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
from Crypto.PublicKey import RSA
from Crypto.PublicKey import RSA
from twisted.conch.ssh.keys import Key
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.
KEY_LENGTH = 1024
rsaKey = Key(RSA.generate(KEY_LENGTH))
keyString = rsaKey.toString(type="OPENSSH")
keyString = rsaKey.toString(type="OPENSSH")
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 _("If this error persists, create game/%(keyfile)s yourself using third-party tools.") % {'keyfile': keyfile}
sys.exit(5)
# try to create the certificate
CERT_EXPIRE = 365 * 20 # twenty years validity
# default:
CERT_EXPIRE = 365 * 20 # twenty years validity
# default:
#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)
#print "exestring:", exestring
@ -58,9 +58,9 @@ def verify_SSL_key_and_cert(keyfile, certfile):
print " %s\n" % e
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 _(" for your operating system.")
print _(" for your operating system.")
print _(" Example (linux, using the openssl program): ")
print " %s" % exestring
print " %s" % exestring
sys.exit(5)
print "done."

View file

@ -2,8 +2,8 @@
This module implements the telnet protocol.
This depends on a generic session module that implements
the actual login procedure of the game, tracks
sessions etc.
the actual login procedure of the game, tracks
sessions etc.
"""
@ -11,44 +11,44 @@ from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE,
from src.server.session import Session
from src.server import ttype, mssp
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):
"""
Each player connecting over telnet (ie using most traditional mud
clients) gets a telnet protocol instance assigned to them. All
communication between game and player goes through here.
"""
"""
def connectionMade(self):
"""
This is called when the connection is first
established.
"""
This is called when the connection is first
established.
"""
# initialize the session
client_address = self.transport.client
client_address = self.transport.client
self.init_session("telnet", client_address, self.factory.sessionhandler)
# negotiate mccp (data compression)
self.mccp = Mccp(self)
self.mccp = Mccp(self)
# negotiate ttype (client info)
self.ttype = ttype.Ttype(self)
# negotiate mssp (crawler communication)
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.
"""
return (option == LINEMODE or
option == ttype.TTYPE or
option == MCCP or
option == MCCP or
option == mssp.MSSP)
def enableLocal(self, option):
@ -60,18 +60,18 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def disableLocal(self, option):
if option == MCCP:
self.mccp.no_mccp(option)
return True
return True
else:
return super(TelnetProtocol, self).disableLocal(option)
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
the disconnect method
"""
self.sessionhandler.disconnect(self)
"""
self.sessionhandler.disconnect(self)
self.transport.loseConnection()
def dataReceived(self, data):
@ -83,7 +83,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# print "dataRcv:", data,
# try:
# for b in data:
# print ord(b),
# print ord(b),
# print ""
# except Exception, e:
# print str(e) + ":", str(data)
@ -91,19 +91,19 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if data and data[0] == IAC:
try:
super(TelnetProtocol, self).dataReceived(data)
return
return
except Exception:
pass
StatefulTelnetProtocol.dataReceived(self, data)
def _write(self, data):
"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')
super(TelnetProtocol, self)._write(mccp_compress(self, data))
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)
#escape IAC in line mode, and correctly add \r\n
line += self.delimiter
@ -112,17 +112,17 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
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.
"""
"""
self.sessionhandler.data_in(self, string)
# Session hooks
# Session hooks
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.
"""
if reason:
@ -131,25 +131,25 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
def data_out(self, string, data=None):
"""
generic hook method for engine to call in order to send data
through the telnet connection.
Data Evennia -> Player.
generic hook method for engine to call in order to send data
through the telnet connection.
Data Evennia -> Player.
data argument may contain a dict with output flags.
"""
try:
string = utils.to_str(string, encoding=self.encoding)
except Exception, e:
self.sendLine(str(e))
return
try:
string = utils.to_str(string, encoding=self.encoding)
except Exception, e:
self.sendLine(str(e))
return
ttype = self.protocol_flags.get('TTYPE', {})
nomarkup = not (ttype.get('256 COLORS') or ttype.get('ANSI') or not ttype.get("init_done"))
raw = False
if type(data) == dict:
if type(data) == dict:
# check if we want escape codes to go through unparsed.
raw = data.get("raw", False)
# check if we want to remove all markup (TTYPE override)
nomarkup = data.get("nomarkup", False)
if raw:
self.sendLine(string)
if raw:
self.sendLine(string)
else:
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)
# terminal capabilities and their codes
MTTS = [(128,'PROXY'),
(64, 'SCREEN READER'),
(32, 'OSC COLOR PALETTE'),
(16, 'MOUSE TRACKING'),
(8, '256 COLORS'),
MTTS = [(128,'PROXY'),
(64, 'SCREEN READER'),
(32, 'OSC COLOR PALETTE'),
(16, 'MOUSE TRACKING'),
(8, '256 COLORS'),
(4, 'UTF-8'),
(2, 'VT100'),
(1, 'ANSI')]
class Ttype(object):
"""
Handles ttype negotiations. Called and initiated by the
Handles ttype negotiations. Called and initiated by the
telnet protocol.
"""
def __init__(self, protocol):
@ -39,26 +39,26 @@ class Ttype(object):
the ttype_step indicates how far in the data retrieval we've
gotten.
"""
self.ttype_step = 0
self.ttype_step = 0
self.protocol = protocol
self.protocol.protocol_flags['TTYPE'] = {"init_done":False}
# 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.
self.protocol.will(TTYPE).addCallbacks(self.do_ttype, self.no_ttype)
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):
"""
Handles negotiation of the ttype protocol once the
client has confirmed that it supports the ttype
protocol.
Handles negotiation of the ttype protocol once the
client has confirmed that it supports the ttype
protocol.
The negotiation proceeds in several steps, each returning a
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']:
return
return
self.ttype_step += 1
if self.ttype_step == 1:
# set up info storage and initialize subnegotiation
if self.ttype_step == 1:
# set up info storage and initialize subnegotiation
self.protocol.requestNegotiation(TTYPE, SEND)
else:
# receive data
# receive data
option = "".join(option).lstrip(IS)
if self.ttype_step == 2:
self.protocol.protocol_flags['TTYPE']['CLIENTNAME'] = option
@ -82,17 +82,16 @@ class Ttype(object):
elif self.ttype_step == 3:
self.protocol.protocol_flags['TTYPE']['TERM'] = option
self.protocol.requestNegotiation(TTYPE, SEND)
elif self.ttype_step == 4:
elif self.ttype_step == 4:
option = int(option.strip('MTTS '))
self.protocol.protocol_flags['TTYPE']['MTTS'] = option
for codenum, standard in MTTS:
self.protocol.protocol_flags['TTYPE']['MTTS'] = option
for codenum, standard in MTTS:
if option == 0:
break
break
status = option % codenum < option
self.protocol.protocol_flags['TTYPE'][standard] = status
if status:
if status:
option = option % codenum
self.protocol.protocol_flags['TTYPE']['init_done'] = True
#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
on twisted and django. They are both a part of the Evennia
website url tree (so the testing website might be located
on http://localhost:8000/, whereas the webclient can be
found on http://localhost:8000/webclient.)
on http://localhost:8000/, whereas the webclient can be
found on http://localhost:8000/webclient.)
/webclient - this url is handled through django's template
system and serves the html page for the client
itself along with its javascript chat program.
/webclientdata - this url is called by the ajax chat using
POST requests (long-polling when necessary)
The WebClient resource in this module will
handle these requests and act as a gateway
to sessions connected over the webclient.
The WebClient resource in this module will
handle these requests and act as a gateway
to sessions connected over the webclient.
"""
import time
from hashlib import md5
@ -25,7 +25,7 @@ from twisted.internet import defer, reactor
from django.utils import simplejson
from django.utils.functional import Promise
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.text2html import parse_html
from src.server import session
@ -34,7 +34,7 @@ SERVERNAME = settings.SERVERNAME
ENCODINGS = settings.ENCODINGS
# 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
# complex database objects too.
@ -51,36 +51,36 @@ def jsonify(obj):
# WebClient resource - this is called by the ajax client
# using POST requests to /webclientdata.
#
class WebClient(resource.Resource):
"""
An ajax/comet long-polling transport
An ajax/comet long-polling transport
"""
isLeaf = True
isLeaf = True
allowedMethods = ('POST',)
def __init__(self):
self.requests = {}
self.databuffer = {}
def getChild(self, path, request):
"""
This is the place to put dynamic content.
"""
return self
return self
def _responseFailed(self, failure, suid, request):
"callback if a request is lost/timed out"
"callback if a request is lost/timed out"
try:
self.requests.get(suid, []).remove(request)
except ValueError:
pass
pass
def lineSend(self, suid, string, data=None):
"""
This adds the data to the buffer and/or sends it to
the client as soon as possible.
"""
"""
requests = self.requests.get(suid, None)
if requests:
request = requests.pop(0)
@ -88,22 +88,22 @@ class WebClient(resource.Resource):
request.write(jsonify({'msg':string, 'data':data}))
request.finish()
self.requests[suid] = requests
else:
else:
# no waiting request. Store data in buffer
dataentries = self.databuffer.get(suid, [])
dataentries.append(jsonify({'msg':string, 'data':data}))
self.databuffer[suid] = dataentries
def client_disconnect(self, suid):
"""
Disconnect session with given suid.
"""
"""
if self.requests.has_key(suid):
for request in self.requests.get(suid, []):
request.finish()
del self.requests[suid]
if self.databuffer.has_key(suid):
del self.databuffer[suid]
del self.databuffer[suid]
def mode_init(self, request):
"""
@ -120,10 +120,10 @@ class WebClient(resource.Resource):
# creating a unique id hash string
suid = md5(str(time.time())).hexdigest()
self.requests[suid] = []
self.databuffer[suid] = []
self.databuffer[suid] = []
sess = WebClientSession()
sess.client = self
sess.client = self
sess.init_session("comet", remote_addr, self.sessionhandler)
sess.suid = suid
sess.sessionhandler.connect(sess)
@ -154,22 +154,22 @@ class WebClient(resource.Resource):
available.
"""
suid = request.args.get('suid', ['0'])[0]
if suid == '0':
if suid == '0':
return ''
dataentries = self.databuffer.get(suid, [])
if dataentries:
return dataentries.pop(0)
reqlist = self.requests.get(suid, [])
request.notifyFinish().addErrback(self._responseFailed, suid, request)
reqlist.append(request)
reqlist.append(request)
self.requests[suid] = reqlist
return server.NOT_DONE_YET
def mode_close(self, request):
"""
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]
if suid == '0':
@ -184,8 +184,8 @@ class WebClient(resource.Resource):
initializing or sending/receving data through the request. It
uses a long-polling mechanism to avoid sending data unless
there is actual data available.
"""
dmode = request.args.get('mode', [None])[0]
"""
dmode = request.args.get('mode', [None])[0]
if dmode == 'init':
# startup. Setup the server.
return self.mode_init(request)
@ -201,11 +201,11 @@ class WebClient(resource.Resource):
else:
# this should not happen if client sends valid data.
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):
"""
@ -215,38 +215,38 @@ class WebClientSession(session.Session):
def disconnect(self, reason=None):
"""
Disconnect from server
"""
"""
if reason:
self.client.lineSend(self.suid, reason)
self.client.client_disconnect(self.suid)
def data_out(self, string='', data=None):
"""
Data Evennia -> Player access hook.
Data Evennia -> Player access hook.
data argument may be used depending on
the client-server implementation.
the client-server implementation.
"""
if data:
# treat data?
pass
# string handling is similar to telnet
try:
string = utils.to_str(string, encoding=self.encoding)
string = utils.to_str(string, encoding=self.encoding)
nomarkup = False
raw = False
raw = False
if type(data) == dict:
# check if we want escape codes to go through unparsed.
raw = data.get("raw", False)
# check if we want to remove all markup
nomarkup = data.get("nomarkup", False)
# check if we want to remove all markup
nomarkup = data.get("nomarkup", False)
if raw:
self.client.lineSend(self.suid, string)
else:
self.client.lineSend(self.suid, parse_html(ansi.parse_ansi(string, strip_ansi=nomarkup)))
return
except Exception, e:
return
except Exception, e:
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.python import threadpool
from twisted.internet import reactor
from twisted.application import service, internet
from twisted.application import service, internet
from twisted.web.wsgi import WSGIResource
from django.core.handlers.wsgi import WSGIHandler
@ -26,19 +26,19 @@ from django.core.handlers.wsgi import WSGIHandler
class DjangoWebRoot(resource.Resource):
"""
This creates a web root (/) that Django
understands by tweaking the way the
child instancee are recognized.
understands by tweaking the way the
child instancee are recognized.
"""
def __init__(self, pool):
"""
Setup the django+twisted resource
"""
resource.Resource.__init__(self)
resource.Resource.__init__(self)
self.wsgi_resource = WSGIResource(reactor, pool , WSGIHandler())
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.
"""
path0 = request.prepath.pop(0)
@ -62,9 +62,9 @@ class WSGIWebServer(internet.TCPServer):
internet.TCPServer.__init__(self, *args, **kwargs)
def startService(self):
"Start the pool after the service"
internet.TCPServer.startService(self)
self.pool.start()
def stopService(self):
internet.TCPServer.startService(self)
self.pool.start()
def stopService(self):
"Safely stop the pool after service stop."
internet.TCPServer.stopService(self)
internet.TCPServer.stopService(self)
self.pool.stop()

View file

@ -16,19 +16,19 @@ always be sure of what you have changed and what is default behaviour.
import os
###################################################
# Evennia base server config
# Evennia base server config
###################################################
# This is the name of your game. Make it catchy!
SERVERNAME = "Evennia"
SERVERNAME = "Evennia"
# Activate telnet service
TELNET_ENABLED = True
TELNET_ENABLED = True
# A list of ports the Evennia telnet server listens on
# Can be one or many.
TELNET_PORTS = [4000]
# Interface addresses to listen to. If 0.0.0.0, listen to all.
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
# (Obs - further web configuration can be found below
# in the section 'Config for Django web features')
@ -42,21 +42,21 @@ WEBSERVER_INTERFACES = ['0.0.0.0']
WEBCLIENT_ENABLED = True
# Activate SSH protocol (SecureShell)
SSH_ENABLED = False
# Ports to use for SSH
# Ports to use for SSH
SSH_PORTS = [8022]
# Interface addresses to listen to. If 0.0.0.0, listen to all.
SSH_INTERFACES = ['0.0.0.0']
# Actiave SSL protocol (SecureSocketLibrary)
SSL_ENABLED = False
# Ports to use for SSL
# Ports to use for SSL
SSL_PORTS = [4001]
# Interface addresses to listen to. If 0.0.0.0, listen to all.
SSL_INTERFACES = ['0.0.0.0']
# If multisessions are allowed, a user can log into the game
# 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
# when a new connects.
# when a new connects.
ALLOW_MULTISESSION = True
# Make this unique, and don't share it with anybody.
# 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')
SERVER_LOG_FILE = os.path.join(LOG_DIR, 'server.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
# file should be removed at regular intervals.
# Where to log server requests to the web server. This is VERY spammy, so this
# file should be removed at regular intervals.
HTTP_LOG_FILE = os.path.join(LOG_DIR, 'http_requests.log')
# 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
@ -81,57 +81,57 @@ TIME_ZONE = 'UTC'
# Language code for this installation. All choices can be found here:
# http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
LANGUAGE_CODE = 'en-us'
# Should the default MUX help files be imported? This might be
# interesting to developers for reference, but is frustrating to users
# since it creates a lot of help entries that has nothing to do
# Should the default MUX help files be imported? This might be
# interesting to developers for reference, but is frustrating to users
# since it creates a lot of help entries that has nothing to do
# 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
# 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
# at regular intervals. Set <=0 to deactivate idle timout completely.
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
# the idle timer.
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
# 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.
# (see http://en.wikipedia.org/wiki/Character_encoding)
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
# operating between the two processes on the same machine. Don't change unless
# you know what you are doing.
# operating between the two processes on the same machine. Don't change unless
# you know what you are doing.
AMP_HOST = 'localhost'
AMP_PORT = 5000
###################################################
# Evennia Database config
# Evennia Database config
###################################################
# Database config syntax for Django 1.2+. You can add several
# database engines in the dictionary (untested).
# Database config syntax for Django 1.2+. You can add several
# database engines in the dictionary (untested).
# ENGINE - path to the the database backend (replace
# sqlite3 in the example with the one you want.
# Supported database engines are
# Supported database engines are
# 'postgresql_psycopg2', 'postgresql', 'mysql',
# 'sqlite3' and 'oracle').
# NAME - database name, or path the db file for sqlite3
# USER - db admin (unused in sqlite3)
# PASSWORD - db admin password (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 = {
'default':{
'ENGINE':'django.db.backends.sqlite3',
'NAME':os.path.join(GAME_DIR, 'evennia.db3'),
'ENGINE':'django.db.backends.sqlite3',
'NAME':os.path.join(GAME_DIR, 'evennia.db3'),
'USER':'',
'PASSWORD':'',
'HOST':'',
'PORT':''
'PORT':''
}}
# Engine Config style for Django versions < 1.2. See above.
DATABASE_ENGINE = 'sqlite3'
@ -142,10 +142,10 @@ DATABASE_HOST = ''
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"
# The handler that outputs errors when searching
# 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
# objects without using dbrefs).
SEARCH_AT_MULTIMATCH_INPUT = "src.commands.cmdparser.at_multimatch_input"
# The module holding text strings for the connection screen.
# This module should contain one or more variables
# The module holding text strings for the connection screen.
# This module should contain one or more variables
# with strings defining the look of the screen.
CONNECTION_SCREEN_MODULE = "src.commands.connection_screen"
# An option al module that, if existing, must hold a function
# 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 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 = ""
# Module holding at_server_start(), at_server_reload() and
# at_server_stop() methods. These methods will be called every time
# the server starts, reloads and resets/stops.
AT_SERVER_STARTSTOP_MODULE = ""# Module holding server-side functions for out-of-band protocols to call.
OOB_FUNC_MODULE = ""
# Module holding MSSP meta data
# Module holding MSSP meta data
MSSP_META_MODULE = ""
###################################################
# Default command sets
# Default command sets
###################################################
# Note that with the exception of the unloggedin set (which is not
# stored anywhere), changing these paths will only affect NEW created
@ -211,9 +211,9 @@ BASE_ROOM_TYPECLASS = "src.objects.objects.Room"
BASE_EXIT_TYPECLASS = "src.objects.objects.Exit"
# Typeclass for Scripts (fallback)
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
# start locations, copy the "create" command from
# start locations, copy the "create" command from
# src/commands/default/unloggedin.py and customize.
CHARACTER_DEFAULT_HOME = "2"
@ -221,7 +221,7 @@ CHARACTER_DEFAULT_HOME = "2"
# 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).
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)
# or slower (timefactor<1) than the real world.
TIME_FACTOR = 2.0
# The tick is the smallest unit of time in the game. Smallest value is 1s.
TIME_FACTOR = 2.0
# The tick is the smallest unit of time in the game. Smallest value is 1s.
TIME_TICK = 1.0
# These measures might or might not make sense to your game world.
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
@ -268,7 +268,7 @@ LOCK_FUNC_MODULES = ("src.locks.lockfuncs",)
# Defines a dict with one key for each from-start
# channel. Each key points to a tuple containing
# (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.
# Default user channel for communication
CHANNEL_PUBLIC = ("Public", ('ooc',), 'Public discussion',
@ -281,17 +281,17 @@ CHANNEL_CONNECTINFO = ("MUDconnections", '', 'Connection log',
"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
# the public to use the external connections, they
# operate as long as you have an internet connection,
# just like stand-alone chat clients. IRC and IMC2
# requires that you have twisted.words installed.
# just like stand-alone chat clients. IRC and IMC2
# requires that you have twisted.words installed.
# Evennia can connect to external IRC channels and
# echo what is said on the channel to IRC and vice
# Evennia can connect to external IRC channels and
# echo what is said on the channel to IRC and vice
# versa. Obs - make sure the IRC network allows bots.
# When enabled, command @irc2chan will be available in-game
IRC_ENABLED = False
@ -307,7 +307,7 @@ IRC_ENABLED = False
# command @imc2chan becomes available in-game and allows you to
# connect Evennia channels to IMC channels on the network. The Evennia
# discussion channel 'ievennia' is on server01.mudbytes.net:5000.
IMC2_ENABLED = False
IMC2_ENABLED = False
IMC2_NETWORK = "server01.mudbytes.net"
IMC2_PORT = 5000
IMC2_CLIENT_PWD = ""
@ -316,7 +316,7 @@ IMC2_SERVER_PWD = ""
# an in-game channel. The channel will be updated when the rss feed
# updates. Use @rss2chan in game to connect if this setting is
# 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/)
RSS_ENABLED=False
RSS_UPDATE_INTERVAL = 60*10 # 10 minutes
@ -366,7 +366,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = False
USE_I18N = False
# Where to find locales (no need to change this, most likely)
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)
SERVE_MEDIA = False
# The master urlconf file that contains all of the sub-branches to the
@ -383,7 +383,7 @@ LOGOUT_URL = '/accounts/login'
MEDIA_URL = '/media/'
# URL prefix for admin media -- CSS, JavaScript and images. Make sure
# to use a trailing slash. Django1.4+ will look for admin files under
# STATIC_URL/admin.
# STATIC_URL/admin.
STATIC_URL = '/media/'
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
@ -431,26 +431,26 @@ INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.admindocs',
'django.contrib.flatpages',
'src.server',
'src.server',
'src.players',
'src.objects',
'src.comms',
'src.comms',
'src.help',
'src.scripts',
'src.web.news',
'src.web.website',)
# 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"
# Use a custom test runner that just tests Evennia-specific apps.
TEST_RUNNER = 'src.utils.test_utils.EvenniaTestSuiteRunner'
###################################################
# Django extensions
# Django extensions
###################################################
# 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:
import django_extensions
INSTALLED_APPS = INSTALLED_APPS + ('django_extensions',)