Multiple fixes to ev and utils:
Made utils.variable_from_module more generic (it can now load pretty much any form of module it's given and also supports searching and returning multiple variables). Removed the variable-load functionality from utils.load_module; this is now purely a loader - use variable_from_module instead. I found out that one couldn't import from src.commands.default due to the __init__ file being restrictive for the sake of the ev API. Removed that and instead imported the default commands into ev.py with the help of utils.variable_from_module instead. Some more fixes in ev followed on this.
This commit is contained in:
parent
3306e36d82
commit
4678234e9a
11 changed files with 211 additions and 188 deletions
96
ev.py
96
ev.py
|
|
@ -2,21 +2,18 @@
|
||||||
|
|
||||||
Central API for the Evennia MUD/MUX/MU* creation system.
|
Central API for the Evennia MUD/MUX/MU* creation system.
|
||||||
|
|
||||||
This basically a set of shortcuts to the main modules in src/. Import this from your code or
|
This is basically a set of shortcuts for accessing things in src/ with less boiler plate.
|
||||||
explore it interactively from a python shell.
|
Import this from your code or explore it interactively from a python shell.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
1) You should import things explicitly from the root of this module - you can not use
|
1) You should import things explicitly from the root of this module - you can not use
|
||||||
dot-notation to import deeper. Hence, to access a default command, you can do the
|
dot-notation to import deeper. Hence, to access a default command, you can do
|
||||||
following:
|
|
||||||
|
|
||||||
import ev
|
import ev
|
||||||
ev.default_cmds.CmdLook
|
ev.default_cmds.CmdLook
|
||||||
or
|
or
|
||||||
from ev import default_cmds
|
from ev import default_cmds
|
||||||
default_cmds.CmdLook
|
default_cmds.CmdLook
|
||||||
|
|
||||||
But trying to import CmdLook directly with "from ev.default_cmds import CmdLook" will
|
But trying to import CmdLook directly with "from ev.default_cmds import CmdLook" will
|
||||||
not work since default_cmds is a property on the "ev" module, not a module of its own.
|
not work since default_cmds is a property on the "ev" module, not a module of its own.
|
||||||
|
|
||||||
|
|
@ -38,34 +35,48 @@ Notes:
|
||||||
some more error checking.
|
some more error checking.
|
||||||
|
|
||||||
5) "settings" links to Evennia's game/settings file. "settings_full" shows all of django's available
|
5) "settings" links to Evennia's game/settings file. "settings_full" shows all of django's available
|
||||||
settings. Note that you cannot change settings from here in a meaningful way, you need to update
|
settings. Note that this is for viewing only - you cannot *change* settings from here in a meaningful
|
||||||
game/settings.py and restart the server.
|
way but have to update game/settings.py and restart the server.
|
||||||
|
|
||||||
6) The API accesses all relevant and most-neeeded functions/classes from src/, but might not
|
6) The API accesses all relevant and most-neeeded functions/classes from src/ but might not
|
||||||
always include all helper-functions referenced from each such entity. To get to those, access
|
always include all helper-functions referenced from each such entity. To get to those, access
|
||||||
the modules in src/ directly. You can always do this anyway, if you do not want to go through
|
the modules in src/ directly. You can always do this anyway, if you do not want to go through
|
||||||
this API.
|
this API.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# set Evennia version in __version__ property
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
try:
|
||||||
|
f = open(os.path.dirname(os.path.abspath(__file__)) + os.sep + "VERSION", 'r')
|
||||||
|
__version__ = "Evennia %s-r%s" % (f.read().strip(), os.popen("hg id -i").read().strip())
|
||||||
|
f.close()
|
||||||
|
del f
|
||||||
|
except IOError:
|
||||||
|
__version__ = "Evennia (unknown version)"
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Stop erroneous direct run (would give a traceback since django is
|
# Stop erroneous direct run (would give a traceback since django is
|
||||||
# not yet initialized)
|
# not yet initialized)
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
info = __doc__ + \
|
print \
|
||||||
"""
|
"""
|
||||||
| This module gives access to Evennia's programming API. It should
|
Evennia MU* creation system (%s)
|
||||||
| not be run on its own, but be imported and accessed as described
|
|
||||||
| above.
|
This module gives access to Evennia's API (Application Programming
|
||||||
|
|
Interface). It should *not* be run on its own, but be imported and
|
||||||
| To start the Evennia server, see game/manage.py and game/evennia.py.
|
accessed from your code or explored interactively from a Python
|
||||||
| More help can be found at http://www.evennia.com.
|
shell.
|
||||||
"""
|
|
||||||
print info
|
For help configuring and starting the Evennia server, see the
|
||||||
|
INSTALL file. More help can be found at http://www.evennia.com.
|
||||||
|
""" % __version__
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
@ -79,18 +90,6 @@ from game import settings
|
||||||
setup_environ(settings)
|
setup_environ(settings)
|
||||||
del setup_environ
|
del setup_environ
|
||||||
from django.conf import settings as settings_full
|
from django.conf import settings as settings_full
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# set Evennia version in __version__ property
|
|
||||||
######################################################################
|
|
||||||
|
|
||||||
try:
|
|
||||||
f = open(os.path.dirname(os.path.abspath(__file__)) + os.sep + "VERSION", 'r')
|
|
||||||
__version__ = "Evennia %s-r%s" % (f.read().strip(), os.popen("hg id -i").read().strip())
|
|
||||||
f.close()
|
|
||||||
del f
|
|
||||||
except IOError:
|
|
||||||
__version__ = "Evennia (unknown version)"
|
|
||||||
del sys, os
|
del sys, os
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
@ -110,7 +109,7 @@ from src.players.models import PlayerDB, PlayerAttribute, PlayerNick
|
||||||
# commands
|
# commands
|
||||||
from src.commands.command import Command
|
from src.commands.command import Command
|
||||||
from src.commands.cmdset import CmdSet
|
from src.commands.cmdset import CmdSet
|
||||||
from src.commands import default as default_cmds
|
# (default_cmds is created below)
|
||||||
|
|
||||||
# locks
|
# locks
|
||||||
from src.locks import lockfuncs
|
from src.locks import lockfuncs
|
||||||
|
|
@ -175,6 +174,37 @@ class DBmanagers(object):
|
||||||
managers = DBmanagers()
|
managers = DBmanagers()
|
||||||
del DBmanagers
|
del DBmanagers
|
||||||
|
|
||||||
|
class DefaultCmds(object):
|
||||||
|
"""
|
||||||
|
This container holds direct shortcuts to all default commands in
|
||||||
|
Evennia.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.commands.default.cmdset_default import DefaultCmdSet
|
||||||
|
from src.commands.default.cmdset_ooc import OOCCmdSet
|
||||||
|
from src.commands.default.cmdset_unloggedin import UnloggedinCmdSet
|
||||||
|
from src.commands.default.muxcommand import MuxCommand
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"populate the object with commands"
|
||||||
|
|
||||||
|
def add_cmds(module):
|
||||||
|
"helper method for populating this object with cmds"
|
||||||
|
cmdlist = utils.variable_from_module(module, module.__all__)
|
||||||
|
self.__dict__.update(dict([(c.__name__, c) for c in cmdlist]))
|
||||||
|
|
||||||
|
from src.commands.default import admin, batchprocess, building, comms, general, help, system, unloggedin
|
||||||
|
add_cmds(building)
|
||||||
|
add_cmds(batchprocess)
|
||||||
|
add_cmds(building)
|
||||||
|
add_cmds(comms)
|
||||||
|
add_cmds(general)
|
||||||
|
add_cmds(help)
|
||||||
|
add_cmds(system)
|
||||||
|
add_cmds(unloggedin)
|
||||||
|
default_cmds = DefaultCmds()
|
||||||
|
del DefaultCmds
|
||||||
|
|
||||||
class SystemCmds(object):
|
class SystemCmds(object):
|
||||||
"""
|
"""
|
||||||
Creating commands with keys set to these constants will make
|
Creating commands with keys set to these constants will make
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ __all__ = ("cmdhandler",)
|
||||||
|
|
||||||
# This decides which command parser is to be used.
|
# This decides which command parser is to be used.
|
||||||
# You have to restart the server for changes to take effect.
|
# You have to restart the server for changes to take effect.
|
||||||
_COMMAND_PARSER = utils.mod_import(*settings.COMMAND_PARSER.rsplit('.', 1))
|
_COMMAND_PARSER = utils.variable_from_module(*settings.COMMAND_PARSER.rsplit('.', 1))
|
||||||
|
|
||||||
# System command names - import these variables rather than trying to
|
# System command names - import these variables rather than trying to
|
||||||
# remember the actual string constants. If not defined, Evennia
|
# remember the actual string constants. If not defined, Evennia
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,4 @@
|
||||||
"""
|
"""
|
||||||
Groups all default commands for access from the API.
|
This package contains all default commands of Evennia, grouped after category.
|
||||||
|
|
||||||
To use via ev API, import this module with
|
|
||||||
from ev import default_cmds,
|
|
||||||
you can then access the command classes as members of default_cmds.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from src.commands.default.cmdset_default import DefaultCmdSet
|
|
||||||
from src.commands.default.cmdset_ooc import OOCCmdSet
|
|
||||||
from src.commands.default.cmdset_unloggedin import UnloggedinCmdSet
|
|
||||||
|
|
||||||
from src.commands.default.muxcommand import MuxCommand
|
|
||||||
|
|
||||||
from src.commands.default.admin import *
|
|
||||||
from src.commands.default.batchprocess import *
|
|
||||||
from src.commands.default.building import *
|
|
||||||
from src.commands.default.comms import *
|
|
||||||
from src.commands.default.general import *
|
|
||||||
from src.commands.default.help import *
|
|
||||||
from src.commands.default import syscommands
|
|
||||||
from src.commands.default.system import *
|
|
||||||
from src.commands.default.unloggedin import *
|
|
||||||
|
|
||||||
del cmdset_default, cmdset_ooc, cmdset_unloggedin, muxcommand
|
|
||||||
del admin, batchprocess, building, comms, general,
|
|
||||||
del help, system, unloggedin
|
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,6 @@ class DefaultCmdSet(CmdSet):
|
||||||
self.add(help.CmdSetHelp())
|
self.add(help.CmdSetHelp())
|
||||||
|
|
||||||
# System commands
|
# System commands
|
||||||
self.add(system.CmdReload())
|
|
||||||
self.add(system.CmdReset())
|
|
||||||
self.add(system.CmdShutdown())
|
|
||||||
self.add(system.CmdPy())
|
self.add(system.CmdPy())
|
||||||
self.add(system.CmdScripts())
|
self.add(system.CmdScripts())
|
||||||
self.add(system.CmdObjects())
|
self.add(system.CmdObjects())
|
||||||
|
|
@ -51,9 +48,7 @@ class DefaultCmdSet(CmdSet):
|
||||||
self.add(admin.CmdBoot())
|
self.add(admin.CmdBoot())
|
||||||
self.add(admin.CmdBan())
|
self.add(admin.CmdBan())
|
||||||
self.add(admin.CmdUnban())
|
self.add(admin.CmdUnban())
|
||||||
self.add(admin.CmdDelPlayer())
|
|
||||||
self.add(admin.CmdEmit())
|
self.add(admin.CmdEmit())
|
||||||
self.add(admin.CmdNewPassword())
|
|
||||||
self.add(admin.CmdPerm())
|
self.add(admin.CmdPerm())
|
||||||
self.add(admin.CmdWall())
|
self.add(admin.CmdWall())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,20 @@ a Player object as caller rather than a Character.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from src.commands.cmdset import CmdSet
|
from src.commands.cmdset import CmdSet
|
||||||
from src.commands.default import help, comms, general, admin
|
from src.commands.default import help, comms, general, admin, system
|
||||||
|
|
||||||
class OOCCmdSet(CmdSet):
|
class OOCCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
Implements the player command set.
|
Implements the player command set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "DefaultOOC"
|
key = "DefaultOOC"
|
||||||
priority = -5
|
priority = -5
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
"Populates the cmdset"
|
"Populates the cmdset"
|
||||||
|
|
||||||
# general commands
|
# General commands
|
||||||
self.add(general.CmdOOCLook())
|
self.add(general.CmdOOCLook())
|
||||||
self.add(general.CmdIC())
|
self.add(general.CmdIC())
|
||||||
self.add(general.CmdOOC())
|
self.add(general.CmdOOC())
|
||||||
|
|
@ -27,11 +27,15 @@ class OOCCmdSet(CmdSet):
|
||||||
self.add(general.CmdQuit())
|
self.add(general.CmdQuit())
|
||||||
self.add(general.CmdPassword())
|
self.add(general.CmdPassword())
|
||||||
|
|
||||||
# help command
|
# Help command
|
||||||
self.add(help.CmdHelp())
|
self.add(help.CmdHelp())
|
||||||
|
|
||||||
# admin commands
|
# system commands
|
||||||
self.add(admin.CmdBoot())
|
self.add(system.CmdReload())
|
||||||
|
self.add(system.CmdReset())
|
||||||
|
self.add(system.CmdShutdown())
|
||||||
|
|
||||||
|
# Admin commands
|
||||||
self.add(admin.CmdDelPlayer())
|
self.add(admin.CmdDelPlayer())
|
||||||
self.add(admin.CmdNewPassword())
|
self.add(admin.CmdNewPassword())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ __all__ = ("CmdHome", "CmdLook", "CmdPassword", "CmdNick",
|
||||||
"CmdSay", "CmdPose", "CmdEncoding", "CmdAccess",
|
"CmdSay", "CmdPose", "CmdEncoding", "CmdAccess",
|
||||||
"CmdOOCLook", "CmdIC", "CmdOOC")
|
"CmdOOCLook", "CmdIC", "CmdOOC")
|
||||||
|
|
||||||
AT_SEARCH_RESULT = utils.mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
||||||
BASE_PLAYER_TYPECLASS = settings.BASE_PLAYER_TYPECLASS
|
BASE_PLAYER_TYPECLASS = settings.BASE_PLAYER_TYPECLASS
|
||||||
|
|
||||||
class CmdHome(MuxCommand):
|
class CmdHome(MuxCommand):
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ __all__ = ("ObjectManager",)
|
||||||
|
|
||||||
# Try to use a custom way to parse id-tagged multimatches.
|
# Try to use a custom way to parse id-tagged multimatches.
|
||||||
|
|
||||||
_AT_MULTIMATCH_INPUT = utils.mod_import(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1))
|
_AT_MULTIMATCH_INPUT = utils.variable_from_module(*settings.SEARCH_AT_MULTIMATCH_INPUT.rsplit('.', 1))
|
||||||
|
|
||||||
class ObjectManager(TypedObjectManager):
|
class ObjectManager(TypedObjectManager):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,12 @@ from src.commands.cmdsethandler import CmdSetHandler
|
||||||
from src.commands import cmdhandler
|
from src.commands import cmdhandler
|
||||||
from src.scripts.scripthandler import ScriptHandler
|
from src.scripts.scripthandler import ScriptHandler
|
||||||
from src.utils import logger
|
from src.utils import logger
|
||||||
from src.utils.utils import make_iter, to_unicode, mod_import
|
from src.utils.utils import make_iter, to_unicode, variable_from_module
|
||||||
|
|
||||||
#__all__ = ("ObjAttribute", "Alias", "ObjectNick", "ObjectDB")
|
#__all__ = ("ObjAttribute", "Alias", "ObjectNick", "ObjectDB")
|
||||||
|
|
||||||
|
|
||||||
_AT_SEARCH_RESULT = mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
||||||
|
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ from src.commands import cmdhandler
|
||||||
|
|
||||||
__all__ = ("PlayerAttribute", "PlayerNick", "PlayerDB")
|
__all__ = ("PlayerAttribute", "PlayerNick", "PlayerDB")
|
||||||
|
|
||||||
_AT_SEARCH_RESULT = utils.mod_import(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
_AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ an Evennia game. It will launch any number of fake clients. These
|
||||||
clients will log into the server and start doing random operations.
|
clients will log into the server and start doing random operations.
|
||||||
Customizing and weighing these operations differently depends on
|
Customizing and weighing these operations differently depends on
|
||||||
which type of game is tested. The module contains a testing module
|
which type of game is tested. The module contains a testing module
|
||||||
for plain Evennia.
|
for plain Evennia.
|
||||||
|
|
||||||
Please note that you shouldn't run this on a production server!
|
Please note that you shouldn't run this on a production server!
|
||||||
Launch the program without any arguments or options to see a
|
Launch the program without any arguments or options to see a
|
||||||
full step-by-step setup help.
|
full step-by-step setup help.
|
||||||
|
|
||||||
Basically (for testing default Evennia):
|
Basically (for testing default Evennia):
|
||||||
|
|
||||||
- Use an empty/testing database.
|
- Use an empty/testing database.
|
||||||
- set PERMISSION_PLAYER_DEFAULT = "Builders"
|
- set PERMISSION_PLAYER_DEFAULT = "Builders"
|
||||||
|
|
@ -22,7 +22,7 @@ Basically (for testing default Evennia):
|
||||||
If you want to customize the runner's client actions
|
If you want to customize the runner's client actions
|
||||||
(because you changed the cmdset or needs to better
|
(because you changed the cmdset or needs to better
|
||||||
match your use cases or add more actions), you can
|
match your use cases or add more actions), you can
|
||||||
change which actions by adding a path to
|
change which actions by adding a path to
|
||||||
|
|
||||||
DUMMYRUNNER_ACTIONS_MODULE = <path.to.your.module>
|
DUMMYRUNNER_ACTIONS_MODULE = <path.to.your.module>
|
||||||
|
|
||||||
|
|
@ -31,13 +31,13 @@ for instructions on how to define this module.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os, sys, time, random
|
import os, sys, time, random
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
from twisted.conch import telnet
|
from twisted.conch import telnet
|
||||||
from twisted.internet import reactor, protocol
|
from twisted.internet import reactor, protocol
|
||||||
# from twisted.application import internet, service
|
# from twisted.application import internet, service
|
||||||
# from twisted.web import client
|
# from twisted.web import client
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
|
|
||||||
# Tack on the root evennia directory to the python path and initialize django settings
|
# Tack on the root evennia directory to the python path and initialize django settings
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
|
|
@ -49,7 +49,7 @@ from django.conf import settings
|
||||||
from src.utils import utils
|
from src.utils import utils
|
||||||
|
|
||||||
HELPTEXT = """
|
HELPTEXT = """
|
||||||
DO NOT RUN THIS ON A PRODUCTION SERVER! USE A CLEAN/TESTING DATABASE!
|
DO NOT RUN THIS ON A PRODUCTION SERVER! USE A CLEAN/TESTING DATABASE!
|
||||||
|
|
||||||
This stand-alone program launches dummy telnet clients against a
|
This stand-alone program launches dummy telnet clients against a
|
||||||
running Evennia server. The idea is to mimic real players logging in
|
running Evennia server. The idea is to mimic real players logging in
|
||||||
|
|
@ -61,28 +61,28 @@ running clients will create new objects and rooms all over the place
|
||||||
as part of their running, so using a clean/testing database is
|
as part of their running, so using a clean/testing database is
|
||||||
strongly recommended.
|
strongly recommended.
|
||||||
|
|
||||||
Setup:
|
Setup:
|
||||||
1) setup a fresh/clean database (if using sqlite, just safe-copy
|
1) setup a fresh/clean database (if using sqlite, just safe-copy
|
||||||
away your real evennia.db3 file and create a new one with
|
away your real evennia.db3 file and create a new one with
|
||||||
manage.py)
|
manage.py)
|
||||||
2) in game/settings.py, add
|
2) in game/settings.py, add
|
||||||
|
|
||||||
PERMISSION_PLAYER_DEFAULT="Builders"
|
PERMISSION_PLAYER_DEFAULT="Builders"
|
||||||
|
|
||||||
3a) Start Evennia like normal.
|
3a) Start Evennia like normal.
|
||||||
3b) If you want profiling, start Evennia like this instead:
|
3b) If you want profiling, start Evennia like this instead:
|
||||||
|
|
||||||
python runner.py -S start
|
python runner.py -S start
|
||||||
|
|
||||||
this will start Evennia under cProfiler with output server.prof.
|
this will start Evennia under cProfiler with output server.prof.
|
||||||
4) run this dummy runner:
|
4) run this dummy runner:
|
||||||
|
|
||||||
python dummyclients.py <nr_of_clients> [timestep] [port]
|
python dummyclients.py <nr_of_clients> [timestep] [port]
|
||||||
|
|
||||||
Default is to connect one client to port 4000, using a 5 second
|
Default is to connect one client to port 4000, using a 5 second
|
||||||
timestep. Increase the number of clients and shorten the
|
timestep. Increase the number of clients and shorten the
|
||||||
timestep (minimum is 1s) to further stress the game.
|
timestep (minimum is 1s) to further stress the game.
|
||||||
|
|
||||||
You can stop the dummy runner with Ctrl-C.
|
You can stop the dummy runner with Ctrl-C.
|
||||||
|
|
||||||
5) Log on and determine if game remains responsive despite the
|
5) Log on and determine if game remains responsive despite the
|
||||||
|
|
@ -98,12 +98,12 @@ DEFAULT_NCLIENTS = 1
|
||||||
# time between each 'tick', in seconds, if not set on command
|
# time between each 'tick', in seconds, if not set on command
|
||||||
# line. All launched clients will be called upon to possibly do an
|
# line. All launched clients will be called upon to possibly do an
|
||||||
# action with this frequency.
|
# action with this frequency.
|
||||||
DEFAULT_TIMESTEP = 5
|
DEFAULT_TIMESTEP = 5
|
||||||
# Port to use, if not specified on command line
|
# Port to use, if not specified on command line
|
||||||
DEFAULT_PORT = settings.TELNET_PORTS[0]
|
DEFAULT_PORT = settings.TELNET_PORTS[0]
|
||||||
# chance of an action happening, per timestep. This helps to
|
# chance of an action happening, per timestep. This helps to
|
||||||
# spread out usage randomly, like it would be in reality.
|
# spread out usage randomly, like it would be in reality.
|
||||||
CHANCE_OF_ACTION = 0.1
|
CHANCE_OF_ACTION = 0.1
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
@ -111,37 +111,37 @@ CHANCE_OF_ACTION = 0.1
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
def idcounter():
|
def idcounter():
|
||||||
"generates subsequent id numbers"
|
"generates subsequent id numbers"
|
||||||
idcount = 0
|
idcount = 0
|
||||||
while True:
|
while True:
|
||||||
idcount += 1
|
idcount += 1
|
||||||
yield idcount
|
yield idcount
|
||||||
OID = idcounter()
|
OID = idcounter()
|
||||||
CID = idcounter()
|
CID = idcounter()
|
||||||
|
|
||||||
def makeiter(obj):
|
def makeiter(obj):
|
||||||
"makes everything iterable"
|
"makes everything iterable"
|
||||||
if not hasattr(obj, '__iter__'):
|
if not hasattr(obj, '__iter__'):
|
||||||
return [obj]
|
return [obj]
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
# Client classes
|
# Client classes
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
class DummyClient(telnet.StatefulTelnetProtocol):
|
class DummyClient(telnet.StatefulTelnetProtocol):
|
||||||
"""
|
"""
|
||||||
Handles connection to a running Evennia server,
|
Handles connection to a running Evennia server,
|
||||||
mimicking a real player by sending commands on
|
mimicking a real player by sending commands on
|
||||||
a timer.
|
a timer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
|
|
||||||
# public properties
|
# public properties
|
||||||
self.cid = CID.next()
|
self.cid = CID.next()
|
||||||
self.istep = 0
|
self.istep = 0
|
||||||
self.exits = [] # exit names created
|
self.exits = [] # exit names created
|
||||||
self.objs = [] # obj names created
|
self.objs = [] # obj names created
|
||||||
|
|
||||||
self._actions = self.factory.actions
|
self._actions = self.factory.actions
|
||||||
|
|
@ -150,16 +150,16 @@ class DummyClient(telnet.StatefulTelnetProtocol):
|
||||||
#print " ** client %i connected." % self.cid
|
#print " ** client %i connected." % self.cid
|
||||||
|
|
||||||
reactor.addSystemEventTrigger('before', 'shutdown', self.logout)
|
reactor.addSystemEventTrigger('before', 'shutdown', self.logout)
|
||||||
|
|
||||||
# start client tick
|
# start client tick
|
||||||
d = LoopingCall(self.step)
|
d = LoopingCall(self.step)
|
||||||
d.start(self.factory.timestep, now=True).addErrback(self.error)
|
d.start(self.factory.timestep, now=True).addErrback(self.error)
|
||||||
|
|
||||||
def dataReceived(self, data):
|
def dataReceived(self, data):
|
||||||
"Echo incoming data to stdout"
|
"Echo incoming data to stdout"
|
||||||
if self._echo_all:
|
if self._echo_all:
|
||||||
print data
|
print data
|
||||||
|
|
||||||
def connectionLost(self, reason):
|
def connectionLost(self, reason):
|
||||||
"loosing the connection"
|
"loosing the connection"
|
||||||
#print " ** client %i lost connection." % self.cid
|
#print " ** client %i lost connection." % self.cid
|
||||||
|
|
@ -175,25 +175,25 @@ class DummyClient(telnet.StatefulTelnetProtocol):
|
||||||
def logout(self):
|
def logout(self):
|
||||||
"Causes the client to log out of the server. Triggered by ctrl-c signal."
|
"Causes the client to log out of the server. Triggered by ctrl-c signal."
|
||||||
cmd, report = self._actions[1](self)
|
cmd, report = self._actions[1](self)
|
||||||
print "client %i %s (%s actions)" % (self.cid, report, self.istep)
|
print "client %i %s (%s actions)" % (self.cid, report, self.istep)
|
||||||
self.sendLine(cmd)
|
self.sendLine(cmd)
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
"""
|
"""
|
||||||
Perform a step. This is called repeatedly by the runner
|
Perform a step. This is called repeatedly by the runner
|
||||||
and causes the client to issue commands to the server.
|
and causes the client to issue commands to the server.
|
||||||
This holds all "intelligence" of the dummy client.
|
This holds all "intelligence" of the dummy client.
|
||||||
"""
|
"""
|
||||||
if random.random() > CHANCE_OF_ACTION:
|
if random.random() > CHANCE_OF_ACTION:
|
||||||
return
|
return
|
||||||
if self.istep == 0:
|
if self.istep == 0:
|
||||||
cfunc = self._actions[0]
|
cfunc = self._actions[0]
|
||||||
else: # random selection using cumulative probabilities
|
else: # random selection using cumulative probabilities
|
||||||
rand = random.random()
|
rand = random.random()
|
||||||
cfunc = [func for cprob, func in self._actions[2] if cprob >= rand][0]
|
cfunc = [func for cprob, func in self._actions[2] if cprob >= rand][0]
|
||||||
# launch the action (don't hide tracebacks)
|
# launch the action (don't hide tracebacks)
|
||||||
cmd, report = cfunc(self)
|
cmd, report = cfunc(self)
|
||||||
# handle the result
|
# handle the result
|
||||||
cmd = "\n".join(makeiter(cmd))
|
cmd = "\n".join(makeiter(cmd))
|
||||||
if self.istep == 0 or self._echo_brief or self._echo_all:
|
if self.istep == 0 or self._echo_brief or self._echo_all:
|
||||||
print "client %i %s" % (self.cid, report)
|
print "client %i %s" % (self.cid, report)
|
||||||
|
|
@ -205,74 +205,74 @@ class DummyFactory(protocol.ClientFactory):
|
||||||
|
|
||||||
def __init__(self, actions, timestep, verbose):
|
def __init__(self, actions, timestep, verbose):
|
||||||
"Setup the factory base (shared by all clients)"
|
"Setup the factory base (shared by all clients)"
|
||||||
self.actions = actions
|
self.actions = actions
|
||||||
self.timestep = timestep
|
self.timestep = timestep
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
# Access method:
|
# Access method:
|
||||||
# Starts clients and connects them to a running server.
|
# Starts clients and connects them to a running server.
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, verbose=0):
|
def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, verbose=0):
|
||||||
|
|
||||||
# validating and preparing the action tuple
|
# validating and preparing the action tuple
|
||||||
|
|
||||||
# make sure the probabilities add up to 1
|
# make sure the probabilities add up to 1
|
||||||
pratio = 1.0 / sum(tup[0] for tup in actions[2:])
|
pratio = 1.0 / sum(tup[0] for tup in actions[2:])
|
||||||
flogin, flogout, probs, cfuncs = actions[0], actions[1], [tup[0] * pratio for tup in actions[2:]], [tup[1] for tup in actions[2:]]
|
flogin, flogout, probs, cfuncs = actions[0], actions[1], [tup[0] * pratio for tup in actions[2:]], [tup[1] for tup in actions[2:]]
|
||||||
# create cumulative probabilies for the random actions
|
# create cumulative probabilies for the random actions
|
||||||
cprobs = [sum(v for i,v in enumerate(probs) if i<=k) for k in range(len(probs))]
|
cprobs = [sum(v for i,v in enumerate(probs) if i<=k) for k in range(len(probs))]
|
||||||
# rebuild a new, optimized action structure
|
# rebuild a new, optimized action structure
|
||||||
actions = (flogin, flogout, zip(cprobs, cfuncs))
|
actions = (flogin, flogout, zip(cprobs, cfuncs))
|
||||||
|
|
||||||
# setting up all clients (they are automatically started)
|
# setting up all clients (they are automatically started)
|
||||||
factory = DummyFactory(actions, timestep, verbose)
|
factory = DummyFactory(actions, timestep, verbose)
|
||||||
for i in range(nclients):
|
for i in range(nclients):
|
||||||
reactor.connectTCP("localhost", telnet_port, factory)
|
reactor.connectTCP("localhost", telnet_port, factory)
|
||||||
# start reactor
|
# start reactor
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
# Command line interface
|
# Command line interface
|
||||||
#------------------------------------------------------------
|
#------------------------------------------------------------
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
# parsing command line with default vals
|
# parsing command line with default vals
|
||||||
parser = OptionParser(usage="%prog [options] <nclients> [timestep, [port]]",
|
parser = OptionParser(usage="%prog [options] <nclients> [timestep, [port]]",
|
||||||
description="This program requires some preparations to run properly. Start it without any arguments or options for full help.")
|
description="This program requires some preparations to run properly. Start it without any arguments or options for full help.")
|
||||||
parser.add_option('-v', '--verbose', action='store_const', const=1, dest='verbose',
|
parser.add_option('-v', '--verbose', action='store_const', const=1, dest='verbose',
|
||||||
default=0,help="echo brief description of what clients do every timestep.")
|
default=0,help="echo brief description of what clients do every timestep.")
|
||||||
parser.add_option('-V', '--very-verbose', action='store_const',const=2, dest='verbose',
|
parser.add_option('-V', '--very-verbose', action='store_const',const=2, dest='verbose',
|
||||||
default=0,help="echo all client returns to stdout (hint: use only with nclients=1!)")
|
default=0,help="echo all client returns to stdout (hint: use only with nclients=1!)")
|
||||||
|
|
||||||
options, args = parser.parse_args()
|
options, args = parser.parse_args()
|
||||||
|
|
||||||
nargs = len(args)
|
nargs = len(args)
|
||||||
nclients = DEFAULT_NCLIENTS
|
nclients = DEFAULT_NCLIENTS
|
||||||
timestep = DEFAULT_TIMESTEP
|
timestep = DEFAULT_TIMESTEP
|
||||||
port = DEFAULT_PORT
|
port = DEFAULT_PORT
|
||||||
try:
|
try:
|
||||||
if not args : raise Exception
|
if not args : raise Exception
|
||||||
if nargs > 0: nclients = max(1, int(args[0]))
|
if nargs > 0: nclients = max(1, int(args[0]))
|
||||||
if nargs > 1: timestep = max(1, int(args[1]))
|
if nargs > 1: timestep = max(1, int(args[1]))
|
||||||
if nargs > 2: port = int(args[2])
|
if nargs > 2: port = int(args[2])
|
||||||
except Exception:
|
except Exception:
|
||||||
print HELPTEXT
|
print HELPTEXT
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
# import the ACTION tuple from a given module
|
# import the ACTION tuple from a given module
|
||||||
try:
|
try:
|
||||||
action_modpath = settings.DUMMYRUNNER_ACTIONS_MODULE
|
action_modpath = settings.DUMMYRUNNER_ACTIONS_MODULE
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# use default
|
# use default
|
||||||
action_modpath = "src.utils.dummyrunner_actions"
|
action_modpath = "src.utils.dummyrunner_actions"
|
||||||
actions = utils.mod_import(action_modpath, "ACTIONS")
|
actions = utils.variable_from_module(action_modpath, "ACTIONS")
|
||||||
|
|
||||||
print "Connecting %i dummy client(s) to port %i using a %i second timestep ... " % (nclients, port, timestep)
|
print "Connecting %i dummy client(s) to port %i using a %i second timestep ... " % (nclients, port, timestep)
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
start_all_dummy_clients(actions, nclients, timestep, port,
|
start_all_dummy_clients(actions, nclients, timestep, port,
|
||||||
verbose=options.verbose)
|
verbose=options.verbose)
|
||||||
ttot = time.time() - t0
|
ttot = time.time() - t0
|
||||||
print "... dummy client runner finished after %i seconds." % ttot
|
print "... dummy client runner finished after %i seconds." % ttot
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ be of use when designing your own game.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from inspect import ismodule
|
from inspect import ismodule
|
||||||
import os, sys, imp
|
import os, sys, imp, types
|
||||||
import textwrap
|
import textwrap
|
||||||
import datetime
|
import datetime
|
||||||
import random
|
import random
|
||||||
|
|
@ -538,11 +538,19 @@ def has_parent(basepath, obj):
|
||||||
# instance. Not sure if one should defend against this.
|
# instance. Not sure if one should defend against this.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def mod_import(mod_path, propname=None):
|
def mod_import(module):
|
||||||
"""
|
"""
|
||||||
Takes filename of a module (a python path or a full pathname)
|
A generic Python module loader.
|
||||||
and imports it. If property is given, return the named
|
|
||||||
property from this module instead of the module itself.
|
Args:
|
||||||
|
module - this can be either a Python path (dot-notation like src.objects.models),
|
||||||
|
an absolute path (e.g. /home/eve/evennia/src/objects.models.py)
|
||||||
|
or an already import module object (e.g. models)
|
||||||
|
Returns:
|
||||||
|
an imported module. If the input argument was already a model, this is returned as-is,
|
||||||
|
otherwise the path is parsed and imported.
|
||||||
|
Error:
|
||||||
|
returns None. The error is also logged.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def log_trace(errmsg=None):
|
def log_trace(errmsg=None):
|
||||||
|
|
@ -567,74 +575,83 @@ def mod_import(mod_path, propname=None):
|
||||||
for line in errmsg.splitlines():
|
for line in errmsg.splitlines():
|
||||||
log.msg('[EE] %s' % line)
|
log.msg('[EE] %s' % line)
|
||||||
|
|
||||||
if not mod_path:
|
if not module:
|
||||||
return None
|
return None
|
||||||
# first try to import as a python path
|
|
||||||
try:
|
|
||||||
mod = __import__(mod_path, fromlist=["None"])
|
|
||||||
except ImportError:
|
|
||||||
|
|
||||||
# try absolute path import instead
|
|
||||||
|
|
||||||
if not os.path.isabs(mod_path):
|
|
||||||
mod_path = os.path.abspath(mod_path)
|
|
||||||
path, filename = mod_path.rsplit(os.path.sep, 1)
|
|
||||||
modname = filename.rstrip('.py')
|
|
||||||
|
|
||||||
|
if type(module) == types.ModuleType:
|
||||||
|
# if this is already a module, we are done
|
||||||
|
mod = module
|
||||||
|
else:
|
||||||
|
# first try to import as a python path
|
||||||
try:
|
try:
|
||||||
result = imp.find_module(modname, [path])
|
mod = __import__(module, fromlist=["None"])
|
||||||
except ImportError:
|
except ImportError:
|
||||||
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
mod = imp.load_module(modname, *result)
|
|
||||||
except ImportError:
|
|
||||||
log_trace("Could not find or import module %s at path '%s'" % (modname, path))
|
|
||||||
mod = None
|
|
||||||
# we have to close the file handle manually
|
|
||||||
result[0].close()
|
|
||||||
|
|
||||||
if mod and propname:
|
# try absolute path import instead
|
||||||
# we have a module, extract the sought property from it.
|
|
||||||
try:
|
if not os.path.isabs(module):
|
||||||
mod_prop = mod.__dict__[to_str(propname)]
|
module = os.path.abspath(module)
|
||||||
except KeyError:
|
path, filename = module.rsplit(os.path.sep, 1)
|
||||||
log_trace("Could not import property '%s' from module %s." % (propname, mod_path))
|
modname = filename.rstrip('.py')
|
||||||
return None
|
|
||||||
return mod_prop
|
try:
|
||||||
|
result = imp.find_module(modname, [path])
|
||||||
|
except ImportError:
|
||||||
|
log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path))
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
mod = imp.load_module(modname, *result)
|
||||||
|
except ImportError:
|
||||||
|
log_trace("Could not find or import module %s at path '%s'" % (modname, path))
|
||||||
|
mod = None
|
||||||
|
# we have to close the file handle manually
|
||||||
|
result[0].close()
|
||||||
return mod
|
return mod
|
||||||
|
|
||||||
def variable_from_module(modpath, variable=None, default=None):
|
def variable_from_module(module, variable=None, default=None):
|
||||||
"""
|
"""
|
||||||
Retrieve a variable from a module. The variable must be defined
|
Retrieve a variable or list of variables from a module. The variable(s) must be defined
|
||||||
globally in the module. If no variable is given, a random variable
|
globally in the module. If no variable is given (or a list entry is None), a random variable
|
||||||
is returned from the module.
|
is extracted from the module.
|
||||||
|
|
||||||
If module cannot be imported or given variable not found, default
|
If module cannot be imported or given variable not found, default
|
||||||
is returned.
|
is returned.
|
||||||
"""
|
|
||||||
if not modpath:
|
|
||||||
return default
|
|
||||||
try:
|
|
||||||
mod = __import__(modpath, fromlist=["None"])
|
|
||||||
except ImportError:
|
|
||||||
return default
|
|
||||||
if variable:
|
|
||||||
# try to pick a named variable
|
|
||||||
return mod.__dict__.get(variable, default)
|
|
||||||
else:
|
|
||||||
# random selection
|
|
||||||
mvars = [val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val))]
|
|
||||||
return mvars and random.choice(mvars)
|
|
||||||
|
|
||||||
def string_from_module(modpath, variable=None, default=None):
|
Args:
|
||||||
|
module (string or module)- python path, absolute path or a module
|
||||||
|
variable (string or iterable) - single variable name or iterable of variable names to extract
|
||||||
|
default (string) - default value to use if a variable fails to be extracted.
|
||||||
|
Returns:
|
||||||
|
a single value or a list of values depending on the type of 'variable' argument. Errors in lists
|
||||||
|
are replaced by the 'default' argument."""
|
||||||
|
|
||||||
|
if not module:
|
||||||
|
return default
|
||||||
|
mod = mod_import(module)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for var in make_iter(variable):
|
||||||
|
if var:
|
||||||
|
# try to pick a named variable
|
||||||
|
result.append(mod.__dict__.get(var, default))
|
||||||
|
else:
|
||||||
|
# random selection
|
||||||
|
mvars = [val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val))]
|
||||||
|
result.append((mvars and random.choice(mvars)) or default)
|
||||||
|
if len(result) == 1:
|
||||||
|
return result[0]
|
||||||
|
return result
|
||||||
|
|
||||||
|
def string_from_module(module, variable=None, default=None):
|
||||||
"""
|
"""
|
||||||
This is a wrapper for variable_from_module that requires return
|
This is a wrapper for variable_from_module that requires return
|
||||||
value to be a string to pass. It's primarily used by login screen.
|
value to be a string to pass. It's primarily used by login screen.
|
||||||
"""
|
"""
|
||||||
val = variable_from_module(modpath, variable=variable, default=default)
|
val = variable_from_module(module, variable=variable, default=default)
|
||||||
if isinstance(val, basestring):
|
if isinstance(val, basestring):
|
||||||
return val
|
return val
|
||||||
|
elif is_iter(val):
|
||||||
|
return [(isinstance(v, basestring) and v or default) for v in val]
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def init_new_player(player):
|
def init_new_player(player):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue