Moved dummyrunner under bin/.

This commit is contained in:
Griatch 2015-01-10 17:56:33 +01:00
parent f075d4aec1
commit 8e020bfb62
10 changed files with 415 additions and 377 deletions

View file

@ -15,7 +15,7 @@ import signal
import shutil import shutil
import importlib import importlib
from argparse import ArgumentParser from argparse import ArgumentParser
from subprocess import Popen, check_output from subprocess import Popen, check_output, call
import django import django
# Signal processing # Signal processing
@ -27,6 +27,8 @@ EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")
EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "evennia") EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "evennia")
EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "evennia_runner.py") EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "evennia_runner.py")
EVENNIA_TEMPLATE = os.path.join(EVENNIA_ROOT, "game_template") EVENNIA_TEMPLATE = os.path.join(EVENNIA_ROOT, "game_template")
EVENNIA_BINTESTING = os.path.join(EVENNIA_BIN, "testing")
EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_BINTESTING, "dummyrunner.py")
TWISTED_BINARY = "twistd" TWISTED_BINARY = "twistd"
@ -675,6 +677,22 @@ def init_game_directory(path):
print INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path) print INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path)
def run_dummyrunner(number_of_dummies):
"""
Start an instance of the dummyrunner
The dummy players' behavior can be customized by adding a
dummyrunner_settings.py config file in the game's conf directory.
"""
number_of_dummies = str(int(number_of_dummies)) if number_of_dummies else 1
cmdstr = [sys.executable, EVENNIA_DUMMYRUNNER, "-N", number_of_dummies]
config_file = os.path.join(SETTINGS_PATH, "dummyrunner_settings.py")
if os.path.exists(config_file):
cmdstr.extend(["--config", config_file])
try:
call(cmdstr, env=getenv())
except KeyboardInterrupt:
pass
def run_menu(): def run_menu():
""" """
@ -834,8 +852,10 @@ def main():
help="Start given processes in interactive mode.") help="Start given processes in interactive mode.")
parser.add_argument('--init', action='store', dest="init", metavar="name", parser.add_argument('--init', action='store', dest="init", metavar="name",
help="Creates a new game directory 'name' at the current location.") help="Creates a new game directory 'name' at the current location.")
parser.add_argument('-p', '--prof', action='store_true', dest='profiler', default=False, parser.add_argument('--profile', action='store_true', dest='profiler', default=False,
help="Start given server component under the Python profiler.") help="Start given server component under the Python profiler.")
parser.add_argument('--dummyrunner', nargs=1, action='store', dest='dummyrunner', metavar="N",
help="Tests a running server by connecting N dummy players to it.")
parser.add_argument("mode", metavar="option", nargs='?', default="help", parser.add_argument("mode", metavar="option", nargs='?', default="help",
help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).") help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).")
parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all", parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all",
@ -858,7 +878,7 @@ def main():
if args.show_version: if args.show_version:
print show_version_info(mode=="help") print show_version_info(mode=="help")
sys.exit() sys.exit()
if mode == "help": if mode == "help" and not args.dummyrunner:
print ABOUT_INFO print ABOUT_INFO
sys.exit() sys.exit()
@ -866,7 +886,10 @@ def main():
# and initializes django for the game directory # and initializes django for the game directory
init_game_directory(CURRENT_DIR) init_game_directory(CURRENT_DIR)
if mode == 'menu': if args.dummyrunner:
# launch the dummy runner
run_dummyrunner(args.dummyrunner[0])
elif mode == 'menu':
# launch menu for operation # launch menu for operation
run_menu() run_menu()
elif mode in ('start', 'reload', 'stop'): elif mode in ('start', 'reload', 'stop'):

View file

@ -31,36 +31,56 @@ for instructions on how to define this module.
""" """
import os, sys, time, random import time, random
from optparse import OptionParser from argparse import ArgumentParser
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.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
#TODO
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
#os.environ["DJANGO_SETTINGS_MODULE"] = "game.settings"
#from game import settings
#try:
# from django.conf import settings as settings2
# settings2.configure()
#except RuntimeError:
# pass
#finally:
# del settings2
from django.conf import settings from django.conf import settings
from evennia.utils import utils from evennia.utils import mod_import
# Load the dummyrunner settings module
DUMMYRUNNER_SETTINGS = mod_import(settings.DUMMYRUNNER_SETTINGS_MODULE)
DATESTRING = "%Y%m%d%H%M%S"
# Settings
# number of clients to launch if no input is given on command line
NCLIENTS = 1
# time between each 'tick', in seconds, if not set on command
# line. All launched clients will be called upon to possibly do an
# action with this frequency.
TIMESTEP = DUMMYRUNNER_SETTINGS.TIMESTEP
# chance of a client performing an action, per timestep. This helps to
# spread out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = DUMMYRUNNER_SETTINGS.CHANCE_OF_ACTION
# spread out the login action separately, having many players create accounts
# and connect simultaneously is generally unlikely.
CHANCE_OF_LOGIN = DUMMYRUNNER_SETTINGS.CHANCE_OF_LOGIN
# Port to use, if not specified on command line
TELNET_PORT = DUMMYRUNNER_SETTINGS.TELNET_PORT or settings.TELNET_PORTS[0]
#
NLOGGED_IN = 0
# Messages
INFO_STARTING = \
"""
Dummyrunner starting, {N} dummy player(s).
Use Ctrl-C to stop/disconnect clients.
"""
ERROR_FEW_ACTIONS = \
"""
Dummyrunner error: The ACTIONS tuple is too short: it must contain at
least login- and logout functions.
"""
HELPTEXT = """ HELPTEXT = """
Usage: dummyrunner.py [-h][-v][-V] [nclients]
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
@ -81,60 +101,44 @@ Setup:
PERMISSION_PLAYER_DEFAULT="Builders" PERMISSION_PLAYER_DEFAULT="Builders"
3a) Start Evennia like normal. You can also customize the dummyrunner by modifying
3b) If you want profiling, start Evennia like this instead: a setting file specified by DUMMYRUNNER_SETTINGS_MODULE
python runner.py -S start 3) Start Evennia like normal, optionally with profiling (--profile)
4) run this dummy runner via the evennia launcher:
this will start Evennia under cProfiler with output server.prof. evennia --dummyrunner <nr_of_clients>
4) run this dummy runner:
python dummyclients.py <nr_of_clients> [timestep] [port]
Default is to connect one client to port 4000, using a 5 second
timestep. Increase the number of clients and shorten the
timestep (minimum is 1s) to further stress the game.
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
heavier load. Note that if you do profiling, there is an heavier load. Note that if you do profiling, there is an
additional overhead from the profiler too! additional overhead from the profiler too!
6) If you use profiling, let the game run long enough to gather 6) If you use profiling, let the game run long enough to gather
data, then stop the server. You can inspect the server.prof file data, then stop the server, ideally from inside it with
from a python prompt (see Python's manual on cProfiler). @shutdown. You can inspect the server.prof file from a python
prompt (see Python's manual on cProfiler).
""" """
# number of clients to launch if no input is given on command line
DEFAULT_NCLIENTS = 1
# time between each 'tick', in seconds, if not set on command
# line. All launched clients will be called upon to possibly do an
# action with this frequency.
DEFAULT_TIMESTEP = 2
# chance of a client performing an action, per timestep. This helps to
# spread out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = 0.05
# spread out the login action separately, having many players create accounts
# and connect simultaneously is generally unlikely.
CHANCE_OF_LOGIN = 0.5
# Port to use, if not specified on command line
DEFAULT_PORT = settings.TELNET_PORTS[0]
#
NLOGGED_IN = 0
NCLIENTS = 0
#------------------------------------------------------------ #------------------------------------------------------------
# Helper functions # Helper functions
#------------------------------------------------------------ #------------------------------------------------------------
ICOUNT = 0
def idcounter(): def idcounter():
"generates subsequent id numbers" "makes unique ids"
idcount = 0 global ICOUNT
while True: ICOUNT += 1
idcount += 1 return str(ICOUNT)
yield idcount
OID = idcounter()
CID = idcounter() GCOUNT = 0
def gidcounter():
"makes globally unique ids"
global GCOUNT
GCOUNT += 1
return "%s-%s" % (time.strftime(DATESTRING), ICOUNT)
def makeiter(obj): def makeiter(obj):
"makes everything iterable" "makes everything iterable"
@ -156,35 +160,38 @@ class DummyClient(telnet.StatefulTelnetProtocol):
def connectionMade(self): def connectionMade(self):
# public properties # public properties
self.cid = CID.next() self.cid = idcounter()
self.key = "Dummy-%s" % self.cid
self.gid = "%s-%s" % (time.strftime(DATESTRING), self.cid)
self.istep = 0 self.istep = 0
self.loggedin = False
self.exits = [] # exit names created self.exits = [] # exit names created
self.objs = [] # obj names created self.objs = [] # obj names created
self._report = "" self._report = ""
self._cmdlist = [] # already stepping in a cmd definition self._cmdlist = [] # already stepping in a cmd definition
self._ncmds = 0 nactions = len(self.factory.actions) # this has already been normalized
self._actions = self.factory.actions if nactions < 2:
self._echo_brief = self.factory.verbose == 1 raise RuntimeError(ERROR_FEW_ACTIONS)
self._echo_all = self.factory.verbose == 2 self._login = self.factory.actions[0]
#print " ** client %i connected." % self.cid self._logout = self.factory.actions[1]
self._actions = self.factory.actions[2:]
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)
# dissipate exact step by up to +/- 0.5 second # dissipate exact step by up to +/- 0.5 second
timestep = self.factory.timestep + (-0.5 + (random.random()*1.0)) timestep = TIMESTEP + (-0.5 + (random.random()*1.0))
d.start(timestep, now=True).addErrback(self.error) d.start(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: pass
print data
def connectionLost(self, reason): def connectionLost(self, reason):
"loosing the connection" "loosing the connection"
#print " ** client %i lost connection." % self.cid
def error(self, err): def error(self, err):
"error callback" "error callback"
@ -192,12 +199,12 @@ class DummyClient(telnet.StatefulTelnetProtocol):
def counter(self): def counter(self):
"produces a unique id, also between clients" "produces a unique id, also between clients"
return OID.next() return gidcounter()
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 = self._logout(self)
print "client %i %s (%s actions)" % (self.cid, report, self.istep) print "client %s(%s) logout (%s actions)" % (self.key, self.cid, self.istep)
self.sendLine(cmd) self.sendLine(cmd)
def step(self): def step(self):
@ -206,52 +213,49 @@ class DummyClient(telnet.StatefulTelnetProtocol):
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 self.istep == 0 and random.random() > CHANCE_OF_LOGIN:
return
elif random.random() > CHANCE_OF_ACTION:
return
global NLOGGED_IN global NLOGGED_IN
rand = random.random()
if not self._cmdlist: if not self._cmdlist:
# no cmdlist in store, get a new one # no commands ready. Load some.
if self.istep == 0:
NLOGGED_IN += 1 if not self.loggedin:
cfunc = self._actions[0] if rand < CHANCE_OF_LOGIN:
else: # random selection using cumulative probabilities # get the login commands
rand = random.random() self._cmdlist = list(makeiter(self._login(self)))
cfunc = [func for cprob, func in self._actions[2] if cprob >= rand][0] NLOGGED_IN += 1 # this is for book-keeping
# assign to internal cmdlist print "connecting client %s (%i/%i)..." % (self.key, NLOGGED_IN, NCLIENTS)
cmd, self._report = cfunc(self) self.loggedin = True
self._cmdlist = list(makeiter(cmd)) else:
self._ncmds = len(self._cmdlist) # we always pick a cumulatively random function
# output crand = random.random()
if self.istep == 0 and not (self._echo_brief or self._echo_all): cfunc = [func for cprob, func in self._actions if cprob >= crand][0]
# only print login self._cmdlist = list(makeiter(cfunc(self)))
print "client %i %s (%i/%i)" % (self.cid, self._report, NLOGGED_IN, NCLIENTS)
elif self.istep == 0 or self._echo_brief or self._echo_all: # at this point we always have a list of commands
print "client %i %s (%i/%i)" % (self.cid, self._report, self._ncmds-(len(self._cmdlist)-1), self._ncmds) if rand < CHANCE_OF_ACTION:
# launch the action by popping the first element from cmdlist (don't hide tracebacks) # send to the game
self.sendLine(str(self._cmdlist.pop(0))) self.sendLine(str(self._cmdlist.pop(0)))
self.istep += 1 # only steps up if an action is taken self.istep += 1
class DummyFactory(protocol.ClientFactory): class DummyFactory(protocol.ClientFactory):
protocol = DummyClient protocol = DummyClient
def __init__(self, actions, timestep, verbose): def __init__(self, actions):
"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.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(nclients):
# validating and preparing the action tuple
global NCLIENTS global NCLIENTS
NCLIENTS = nclients NCLIENTS = int(nclients)
actions = DUMMYRUNNER_SETTINGS.ACTIONS
# 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:])
@ -262,9 +266,9 @@ def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, v
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)
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()
@ -275,39 +279,14 @@ def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, v
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 = ArgumentParser(description=HELPTEXT)
description="This program requires some preparations to run properly. Start it without any arguments or options for full help.") parser.add_argument("-N", nargs=1, default=1, dest="nclients",
parser.add_option('-v', '--verbose', action='store_const', const=1, dest='verbose', help="Number of clients to start")
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',
default=0,help="echo all client returns to stdout (hint: use only with nclients=1!)")
options, args = parser.parse_args() args = parser.parse_args()
nargs = len(args) print INFO_STARTING.format(N=args.nclients[0])
nclients = DEFAULT_NCLIENTS
timestep = DEFAULT_TIMESTEP
port = DEFAULT_PORT
try:
if not args : raise Exception
if nargs > 0: nclients = max(1, int(args[0]))
if nargs > 1: timestep = max(1, int(args[1]))
if nargs > 2: port = int(args[2])
except Exception:
print HELPTEXT
sys.exit()
# import the ACTION tuple from a given module
try:
action_modpath = settings.DUMMYRUNNER_ACTIONS_MODULE
except AttributeError:
# use default
action_modpath = "evennia.utils.dummyrunner.dummyrunner_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)
t0 = time.time() t0 = time.time()
start_all_dummy_clients(actions, nclients, timestep, port, start_all_dummy_clients(nclients=args.nclients[0])
verbose=options.verbose)
ttot = time.time() - t0 ttot = time.time() - t0
print "... dummy client runner finished after %i seconds." % ttot print "... dummy client runner stopped after %i seconds." % ttot

View file

@ -0,0 +1,264 @@
"""
Settings and actions for the dummyrunner
This module defines dummyrunner settings and sets up
the actions available to dummy players.
The settings are global variables:
TIMESTEP - time in seconds between each 'tick'
CHANCE_OF_ACTION - chance 0-1 of action happening
CHANCE_OF_LOGIN - chance 0-1 of login happening
TELNET_PORT - port to use, defaults to settings.TELNET_PORT
ACTIONS - see below
ACTIONS is a tuple
(login_func, logout_func, (0.3, func1), (0.1, func2) ... )
where the first entry is the function to call on first connect, with a
chance of occurring given by CHANCE_OF_LOGIN. This function is usually
responsible for logging in the player. The second entry is always
called when the dummyrunner disconnects from the server and should
thus issue a logout command. The other entries are tuples (chance,
func). They are picked randomly, their commonality based on the
cumulative chance given (the chance is normalized between all options
so if will still work also if the given chances don't add up to 1).
Since each function can return a list of game-command strings, each
function may result in multiple operations.
An action-function is called with a "client" argument which is a
reference to the dummy client currently performing the action. It
returns a string or a list of command strings to execute. Use the
client object for optionally saving data between actions.
The client object has the following relevant properties and methods:
key - an optional client key. This is only used for dummyrunner output.
Default is "Dummy-<cid>"
cid - client id
gid - globally unique id, hashed with time stamp
istep - the current step
exits - an empty list. Can be used to store exit names
objs - an empty list. Can be used to store object names
counter() - returns a unique increasing id, hashed with time stamp
to make it unique also between dummyrunner instances.
The return should either be a single command string or a tuple of
command strings. This list of commands will always be executed every
TIMESTEP with a chance given by CHANCE_OF_ACTION by in the order given
(no randomness) and allows for setting up a more complex chain of
commands (such as creating an account and logging in).
"""
# Dummy runner settings
# Time between each dummyrunner "tick", in seconds. Each dummy
# will be called with this frequency.
TIMESTEP = 2
# Chance of a dummy actually performing an action on a given tick.
# This spreads out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = 0.05
# Chance of a currently unlogged-in dummy performing its login
# action every tick. This emulates not all players logging in
# at exactly the same time.
CHANCE_OF_LOGIN = 0.33
# Which telnet port to connect to. If set to None, uses the first
# default telnet port of the running server.
TELNET_PORT = None
# Setup actions tuple
# some convenient templates
DUMMY_NAME = "Dummy-%s"
DUMMY_PWD = "password-%s"
START_ROOM = "testing_room_start_%s"
ROOM_TEMPLATE = "testing_room_%s"
EXIT_TEMPLATE = "exit_%s"
OBJ_TEMPLATE = "testing_obj_%s"
TOBJ_TEMPLATE = "testing_button_%s"
TOBJ_TYPECLASS = "contrib.tutorial_examples.red_button.RedButton"
# action function definitions (pick and choose from
# these to build a client "usage profile"
# login/logout
def c_login(client):
"logins to the game"
# we always use a new client name
cname = DUMMY_NAME % client.gid
cpwd = DUMMY_PWD % client.gid
# set up for digging a first room (to move to and keep the
# login room clean)
roomname = ROOM_TEMPLATE % client.counter()
exitname1 = EXIT_TEMPLATE % client.counter()
exitname2 = EXIT_TEMPLATE % client.counter()
client.exits.extend([exitname1, exitname2])
cmds = ('create %s %s' % (cname, cpwd),
'connect %s %s' % (cname, cpwd),
'@dig %s' % START_ROOM % client.cid,
'@teleport %s' % START_ROOM % client.cid,
'@dig %s = %s, %s' % (roomname, exitname1, exitname2)
)
return cmds
def c_login_nodig(client):
"logins, don't dig its own room"
cname = DUMMY_NAME % client.gid
cpwd = DUMMY_PWD % client.gid
cmds = ('create %s %s' % (cname, cpwd),
'connect %s %s' % (cname, cpwd))
return cmds
def c_logout(client):
"logouts of the game"
return "@quit"
# random commands
def c_looks(client):
"looks at various objects"
cmds = ["look %s" % obj for obj in client.objs]
if not cmds:
cmds = ["look %s" % exi for exi in client.exits]
if not cmds:
cmds = "look"
return cmds
def c_examines(client):
"examines various objects"
cmds = ["examine %s" % obj for obj in client.objs]
if not cmds:
cmds = ["examine %s" % exi for exi in client.exits]
if not cmds:
cmds = "examine me"
return cmds
def c_help(client):
"reads help files"
cmds = ('help',
'help @teleport',
'help look',
'help @tunnel',
'help @dig')
return cmds
def c_digs(client):
"digs a new room, storing exit names on client"
roomname = ROOM_TEMPLATE % client.counter()
exitname1 = EXIT_TEMPLATE % client.counter()
exitname2 = EXIT_TEMPLATE % client.counter()
client.exits.extend([exitname1, exitname2])
return '@dig/tel %s = %s, %s' % (roomname, exitname1, exitname2)
def c_creates_obj(client):
"creates normal objects, storing their name on client"
objname = OBJ_TEMPLATE % client.counter()
client.objs.append(objname)
cmds = ('@create %s' % objname,
'@desc %s = "this is a test object' % objname,
'@set %s/testattr = this is a test attribute value.' % objname,
'@set %s/testattr2 = this is a second test attribute.' % objname)
return cmds
def c_creates_button(client):
"creates example button, storing name on client"
objname = TOBJ_TEMPLATE % client.counter()
client.objs.append(objname)
cmds = ('@create %s:%s' % (objname, TOBJ_TYPECLASS),
'@desc %s = test red button!' % objname)
return cmds
def c_socialize(client):
"socializechats on channel"
cmds = ('ooc Hello!',
'ooc Testing ...',
'ooc Testing ... times 2',
'say Yo!',
'emote stands looking around.')
return cmds
def c_moves(client):
"moves to a previously created room, using the stored exits"
cmds = client.exits # try all exits - finally one will work
return "look" if not cmds else cmds
def c_moves_n(client):
"move through north exit if available"
return "north"
def c_moves_s(client):
"move through south exit if available"
return "south"
# Action tuple (required)
#
# This is a tuple of client action functions. The first element is the
# function the client should use to log into the game and move to
# STARTROOM . The second element is the logout command, for cleanly
# exiting the mud. The following elements are 2-tuples of (probability,
# action_function). The probablities should normally sum up to 1,
# otherwise the system will normalize them.
#
## "normal builder" definitionj
#ACTIONS = ( c_login,
# c_logout,
# (0.5, c_looks),
# (0.08, c_examines),
# (0.1, c_help),
# (0.01, c_digs),
# (0.01, c_creates_obj),
# (0.3, c_moves))
## "heavy" builder definition
#ACTIONS = ( c_login,
# c_logout,
# (0.2, c_looks),
# (0.1, c_examines),
# (0.2, c_help),
# (0.1, c_digs),
# (0.1, c_creates_obj),
# #(0.01, c_creates_button),
# (0.2, c_moves))
## "passive player" definition
#ACTIONS = ( c_login,
# c_logout,
# (0.7, c_looks),
# #(0.1, c_examines),
# (0.3, c_help))
# #(0.1, c_digs),
# #(0.1, c_creates_obj),
# #(0.1, c_creates_button),
# #(0.4, c_moves))
## "normal player" definition
ACTIONS = ( c_login,
c_logout,
(0.01, c_digs),
(0.39, c_looks),
(0.2, c_help),
(0.4, c_moves))
#ACTIONS = (c_login_nodig,
# c_logout,
# (1.0, c_moves_n))
## "socializing heavy builder" definition
#ACTIONS = (c_login,
# c_logout,
# (0.1, c_socialize),
# (0.1, c_looks),
# (0.2, c_help),
# (0.1, c_creates_obj),
# (0.2, c_digs),
# (0.3, c_moves))
## "heavy digger memory tester" definition
#ACTIONS = (c_login,
# c_logout,
# (1.0, c_digs))

View file

@ -243,6 +243,9 @@ LOCK_FUNC_MODULES = ("evennia.locks.lockfuncs", "server.conf.lockfuncs",)
# and expansion of which hooks OOB protocols are allowed to call on the server # and expansion of which hooks OOB protocols are allowed to call on the server
# protocols for attaching tracker hooks for when various object field change # protocols for attaching tracker hooks for when various object field change
OOB_PLUGIN_MODULES = ["evennia.server.oob_cmds", "server.conf.oobfuncs"] OOB_PLUGIN_MODULES = ["evennia.server.oob_cmds", "server.conf.oobfuncs"]
# Module holding settings/actions for the dummyrunner program (see the
# dummyrunner for more information)
DUMMYRUNNER_SETTINGS_MODULE = os.path.join(ROOT_DIR, "bin/testing/dummyrunner_settings")
###################################################################### ######################################################################
# Default command sets # Default command sets

View file

@ -1,231 +0,0 @@
"""
These are actions for the dummy client runner, using
the default command set and intended for unmodified Evennia.
Each client action is defined as a function. The clients
will perform these actions randomly (except the login action).
Each action-definition function should take one argument- "client",
which is a reference to the client currently performing the action
Use the client object for saving data between actions.
The client object has the following relevant properties and methods:
cid - unique client id
istep - the current step
exits - an empty list. Can be used to store exit names
objs - an empty list. Can be used to store object names
counter() - get an integer value. This counts up for every call and
is always unique between clients.
The action-definition function should return the command that the
client should send to the server (as if it was input in a mud client).
It should also return a string detailing the action taken. This string is
used by the "brief verbose" mode of the runner and is prepended by
"Client N " to produce output like "Client 3 is creating objects ..."
This module *must* also define a variable named ACTIONS. This is a tuple
where the first element is the function object for the action function
to call when the client logs onto the server. The following elements
are 2-tuples (probability, action_func), where probability defines how
common it is for that particular action to happen. The runner will
randomly pick between those functions based on the probability.
ACTIONS = (login_func, (0.3, func1), (0.1, func2) ... )
To change the runner to use your custom ACTION and/or action
definitions, edit settings.py and add
DUMMYRUNNER_ACTIONS_MODULE = "path.to.your.module"
"""
# it's very useful to have a unique id for this run to avoid any risk
# of clashes
import time
RUNID = time.time()
# some convenient templates
START_ROOM = "testing_room_start-%s-%s" % (RUNID, "%i")
ROOM_TEMPLATE = "testing_room_%s-%s" % (RUNID, "%i")
EXIT_TEMPLATE = "exit_%s-%s" % (RUNID, "%i")
OBJ_TEMPLATE = "testing_obj_%s-%s" % (RUNID, "%i")
TOBJ_TEMPLATE = "testing_button_%s-%s" % (RUNID, "%i")
TOBJ_TYPECLASS = "examples.red_button.RedButton"
# action function definitions
def c_login(client):
"logins to the game"
cname = "Dummy-%s-%i" % (RUNID, client.cid)
#cemail = "%s@dummy.com" % (cname.lower())
cpwd = "%s-%s" % (RUNID, client.cid)
# set up for digging a first room (to move to)
roomname = ROOM_TEMPLATE % client.counter()
exitname1 = EXIT_TEMPLATE % client.counter()
exitname2 = EXIT_TEMPLATE % client.counter()
client.exits.extend([exitname1, exitname2])
#cmd = '@dig %s = %s, %s' % (roomname, exitname1, exitname2)
cmd = ('create %s %s' % (cname, cpwd),
'connect %s %s' % (cname, cpwd),
'@dig %s' % START_ROOM % client.cid,
'@teleport %s' % START_ROOM % client.cid,
'@dig %s = %s, %s' % (roomname, exitname1, exitname2)
)
return cmd, "logs in as %s ..." % cname
def c_login_nodig(client):
"logins, don't dig its own room"
cname = "Dummy-%s-%i" % (RUNID, client.cid)
cpwd = "%s-%s" % (RUNID, client.cid)
cmd = ('create %s %s' % (cname, cpwd),
'connect %s %s' % (cname, cpwd))
return cmd, "logs in as %s ..." % cname
def c_logout(client):
"logouts of the game"
return "@quit", "logs out"
def c_looks(client):
"looks at various objects"
cmd = ["look %s" % obj for obj in client.objs]
if not cmd:
cmd = ["look %s" % exi for exi in client.exits]
if not cmd:
cmd = "look"
return cmd, "looks ..."
def c_examines(client):
"examines various objects"
cmd = ["examine %s" % obj for obj in client.objs]
if not cmd:
cmd = ["examine %s" % exi for exi in client.exits]
if not cmd:
cmd = "examine me"
return cmd, "examines objs ..."
def c_help(client):
"reads help files"
cmd = ('help',
'help @teleport',
'help look',
'help @tunnel',
'help @dig')
return cmd, "reads help ..."
def c_digs(client):
"digs a new room, storing exit names on client"
roomname = ROOM_TEMPLATE % client.counter()
exitname1 = EXIT_TEMPLATE % client.counter()
exitname2 = EXIT_TEMPLATE % client.counter()
client.exits.extend([exitname1, exitname2])
cmd = '@dig/tel %s = %s, %s' % (roomname, exitname1, exitname2)
return cmd, "digs ..."
def c_creates_obj(client):
"creates normal objects, storing their name on client"
objname = OBJ_TEMPLATE % client.counter()
client.objs.append(objname)
cmd = ('@create %s' % objname,
'@desc %s = "this is a test object' % objname,
'@set %s/testattr = this is a test attribute value.' % objname,
'@set %s/testattr2 = this is a second test attribute.' % objname)
return cmd, "creates obj ..."
def c_creates_button(client):
"creates example button, storing name on client"
objname = TOBJ_TEMPLATE % client.counter()
client.objs.append(objname)
cmd = ('@create %s:%s' % (objname, TOBJ_TYPECLASS),
'@desc %s = test red button!' % objname)
return cmd, "creates button ..."
def c_socialize(client):
"socializechats on channel"
cmd = ('ooc Hello!',
'ooc Testing ...',
'ooc Testing ... times 2',
'say Yo!',
'emote stands looking around.')
return cmd, "socializes ..."
def c_moves(client):
"moves to a previously created room, using the stored exits"
cmd = client.exits # try all exits - finally one will work
if not cmd: cmd = "look"
return cmd, "moves ..."
def c_moves_n(client):
"move through north exit if available"
cmd = ("north",)
return cmd, "moves n..."
def c_moves_s(client):
"move through north exit if available"
cmd = ("north",)
return cmd, "moves s..."
# Action tuple (required)
#
# This is a tuple of client action functions. The first element is the
# function the client should use to log into the game and move to
# STARTROOM . The second element is the logout command, for cleanly
# exiting the mud. The following elements are 2-tuples of (probability,
# action_function). The probablities should normally sum up to 1,
# otherwise the system will normalize them.
#
## "normal builder" definitionj
#ACTIONS = ( c_login,
# c_logout,
# (0.5, c_looks),
# (0.08, c_examines),
# (0.1, c_help),
# (0.01, c_digs),
# (0.01, c_creates_obj),
# (0.3, c_moves))
## "heavy" builder definition
#ACTIONS = ( c_login,
# c_logout,
# (0.2, c_looks),
# (0.1, c_examines),
# (0.2, c_help),
# (0.1, c_digs),
# (0.1, c_creates_obj),
# #(0.01, c_creates_button),
# (0.2, c_moves))
## "passive player" definition
#ACTIONS = ( c_login,
# c_logout,
# (0.7, c_looks),
# #(0.1, c_examines),
# (0.3, c_help))
# #(0.1, c_digs),
# #(0.1, c_creates_obj),
# #(0.1, c_creates_button),
# #(0.4, c_moves))
## "normal player" definition
ACTIONS = ( c_login,
c_logout,
(0.01, c_digs),
(0.39, c_looks),
(0.2, c_help),
(0.4, c_moves))
#ACTIONS = (c_login_nodig,
# c_logout,
# (1.0, c_moves_n))
## "socializing heavy builder" definition
#ACTIONS = (c_login,
# c_logout,
# (0.1, c_socialize),
# (0.1, c_looks),
# (0.2, c_help),
# (0.1, c_creates_obj),
# (0.2, c_digs),
# (0.3, c_moves))
## "heavy digger memory tester" definition
#ACTIONS = (c_login,
# c_logout,
# (1.0, c_digs))

View file

@ -765,7 +765,7 @@ def mod_import(module):
module - this can be either a Python path (dot-notation like module - this can be either a Python path (dot-notation like
evennia.objects.models), an absolute path evennia.objects.models), an absolute path
(e.g. /home/eve/evennia/evennia/objects.models.py) (e.g. /home/eve/evennia/evennia/objects.models.py)
or an already import module object (e.g. models) or an already imported module object (e.g. models)
Returns: Returns:
an imported module. If the input argument was already a model, an imported module. If the input argument was already a model,
this is returned as-is, otherwise the path is parsed and imported. this is returned as-is, otherwise the path is parsed and imported.