From 45741f6c6fc9d818e54ce70557a09734799f4d99 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 2 Jun 2021 23:00:12 +0200 Subject: [PATCH] Updated HTML docs --- docs/0.9.5/.buildinfo | 2 +- .../server/portal/portalsessionhandler.html | 3 +- .../evennia/server/profiling/dummyrunner.html | 268 ++++++++++++-- .../profiling/dummyrunner_settings.html | 250 +++++++------ .../_modules/evennia/server/throttle.html | 7 +- .../api/evennia.commands.default.account.html | 4 +- .../api/evennia.commands.default.admin.html | 4 +- .../evennia.commands.default.building.html | 8 +- .../api/evennia.commands.default.comms.html | 4 +- .../api/evennia.commands.default.general.html | 12 +- .../api/evennia.commands.default.system.html | 8 +- .../evennia.commands.default.unloggedin.html | 20 +- docs/0.9.5/api/evennia.contrib.barter.html | 4 +- docs/0.9.5/api/evennia.contrib.chargen.html | 4 +- .../api/evennia.contrib.email_login.html | 20 +- .../api/evennia.contrib.extended_room.html | 4 +- docs/0.9.5/api/evennia.contrib.rpsystem.html | 4 +- ....contrib.tutorial_examples.red_button.html | 16 +- ...vennia.contrib.tutorial_world.objects.html | 16 +- .../evennia.contrib.tutorial_world.rooms.html | 12 +- .../evennia.server.profiling.dummyrunner.html | 94 ++++- ...server.profiling.dummyrunner_settings.html | 29 +- docs/0.9.5/api/evennia.server.throttle.html | 3 +- docs/0.9.5/api/evennia.utils.eveditor.html | 4 +- docs/0.9.5/api/evennia.utils.evmenu.html | 4 +- docs/0.9.5/api/evennia.utils.evmore.html | 4 +- docs/0.9.5/genindex.html | 38 +- docs/0.9.5/objects.inv | Bin 70795 -> 70919 bytes docs/0.9.5/searchindex.js | 2 +- docs/1.0-dev/.buildinfo | 2 +- docs/1.0-dev/Coding/Profiling.html | 330 +++++++++++++----- docs/1.0-dev/Components/Channels.html | 2 +- .../server/portal/portalsessionhandler.html | 3 +- .../evennia/server/profiling/dummyrunner.html | 268 ++++++++++++-- .../profiling/dummyrunner_settings.html | 250 +++++++------ .../_modules/evennia/server/throttle.html | 7 +- docs/1.0-dev/_sources/Coding/Profiling.md.txt | 280 +++++++++++---- .../_sources/Components/Channels.md.txt | 2 +- docs/1.0-dev/_sources/toc.md.txt | 2 +- .../api/evennia.commands.default.account.html | 4 +- .../api/evennia.commands.default.admin.html | 4 +- ...evennia.commands.default.batchprocess.html | 4 +- .../evennia.commands.default.building.html | 12 +- .../api/evennia.commands.default.comms.html | 8 +- .../api/evennia.commands.default.general.html | 16 +- .../api/evennia.commands.default.system.html | 4 +- .../evennia.commands.default.unloggedin.html | 12 +- docs/1.0-dev/api/evennia.contrib.chargen.html | 4 +- .../api/evennia.contrib.email_login.html | 12 +- .../evennia.contrib.evscaperoom.commands.html | 28 +- .../api/evennia.contrib.extended_room.html | 4 +- ...vennia.contrib.ingame_python.commands.html | 4 +- .../1.0-dev/api/evennia.contrib.rpsystem.html | 8 +- ....contrib.tutorial_examples.red_button.html | 4 +- ...vennia.contrib.tutorial_world.objects.html | 16 +- .../evennia.contrib.tutorial_world.rooms.html | 8 +- .../evennia.server.profiling.dummyrunner.html | 94 ++++- ...server.profiling.dummyrunner_settings.html | 29 +- docs/1.0-dev/api/evennia.server.throttle.html | 3 +- docs/1.0-dev/api/evennia.utils.eveditor.html | 4 +- docs/1.0-dev/api/evennia.utils.evmenu.html | 4 +- docs/1.0-dev/api/evennia.utils.evmore.html | 4 +- docs/1.0-dev/genindex.html | 38 +- docs/1.0-dev/objects.inv | Bin 87226 -> 87367 bytes docs/1.0-dev/searchindex.js | 2 +- docs/1.0-dev/toc.html | 1 - 66 files changed, 1684 insertions(+), 641 deletions(-) diff --git a/docs/0.9.5/.buildinfo b/docs/0.9.5/.buildinfo index 7e928d2d77..c3f4785828 100644 --- a/docs/0.9.5/.buildinfo +++ b/docs/0.9.5/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 307de55c15128b2adf4bc8f918a22cf3 +config: accce89ccef2551c3083282d22ac39af tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/0.9.5/_modules/evennia/server/portal/portalsessionhandler.html b/docs/0.9.5/_modules/evennia/server/portal/portalsessionhandler.html index aee9949a71..91d3b876a2 100644 --- a/docs/0.9.5/_modules/evennia/server/portal/portalsessionhandler.html +++ b/docs/0.9.5/_modules/evennia/server/portal/portalsessionhandler.html @@ -78,7 +78,8 @@ # Portal-SessionHandler class # ------------------------------------------------------------- -DOS_PROTECTION_MSG = _("{servername} DoS protection is active. You are queued to connect in {num} seconds ...") +DOS_PROTECTION_MSG = _("{servername} DoS protection is active." + "You are queued to connect in {num} seconds ...")
[docs]class PortalSessionHandler(SessionHandler): diff --git a/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner.html b/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner.html index 84d3d31e78..7ee582c81c 100644 --- a/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner.html +++ b/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner.html @@ -81,8 +81,16 @@ from twisted.internet import reactor, protocol from twisted.internet.task import LoopingCall -from django.conf import settings -from evennia.utils import mod_import, time_format +import django +django.setup() +import evennia # noqa +evennia._init() + +from django.conf import settings # noqa +from evennia.utils import mod_import, time_format # noqa +from evennia.commands.command import Command # noqa +from evennia.commands.cmdset import CmdSet # noqa +from evennia.utils.ansi import strip_ansi # noqa # Load the dummyrunner settings module @@ -92,8 +100,10 @@ "Error: Dummyrunner could not find settings file at %s" % settings.DUMMYRUNNER_SETTINGS_MODULE ) +IDMAPPER_CACHE_MAXSIZE = settings.IDMAPPER_CACHE_MAXSIZE DATESTRING = "%Y%m%d%H%M%S" +CLIENTS = [] # Settings @@ -112,18 +122,37 @@ # Port to use, if not specified on command line TELNET_PORT = DUMMYRUNNER_SETTINGS.TELNET_PORT or settings.TELNET_PORTS[0] # -NLOGGED_IN = 0 +NCONNECTED = 0 # client has received a connection +NLOGIN_SCREEN = 0 # client has seen the login screen (server responded) +NLOGGING_IN = 0 # client starting login procedure +NLOGGED_IN = 0 # client has authenticated and logged in - -# Messages +# time when all clients have logged_in +TIME_ALL_LOGIN = 0 +# actions since all logged in +TOTAL_ACTIONS = 0 +TOTAL_LAG_MEASURES = 0 +# lag per 30s for all logged in +TOTAL_LAG = 0 +TOTAL_LAG_IN = 0 +TOTAL_LAG_OUT = 0 INFO_STARTING = """ - Dummyrunner starting using {N} dummy account(s). If you don't see + Dummyrunner starting using {nclients} dummy account(s). If you don't see any connection messages, make sure that the Evennia server is running. - Use Ctrl-C to stop/disconnect clients. + TELNET_PORT = {port} + IDMAPPER_CACHE_MAXSIZE = {idmapper_cache_size} MB + TIMESTEP = {timestep} (rate {rate}/s) + CHANCE_OF_LOGIN = {chance_of_login}% per time step + CHANCE_OF_ACTION = {chance_of_action}% per time step + -> avg rate (per client, after login): {avg_rate} cmds/s + -> total avg rate (after login): {avg_rate_total} cmds/s + + Use Ctrl-C (or Cmd-C) to stop/disconnect all clients. + """ ERROR_NO_MIXIN = """ @@ -138,6 +167,7 @@ to test all commands - change PASSWORD_HASHERS to use a faster (but less safe) algorithm when creating large numbers of accounts at the same time + - set LOGIN_THROTTLE/CREATION_THROTTLE=None to disable it If you don't want to use the custom settings of the mixin for some reason, you can change their values manually after the import, or @@ -209,6 +239,39 @@ """ + +
[docs]class CmdDummyRunnerEchoResponse(Command): + """ + Dummyrunner command measuring the round-about response time + from sending to receiving a result. + + Usage: + dummyrunner_echo_response <timestamp> + + Responds with + dummyrunner_echo_response:<timestamp>,<current_time> + + The dummyrunner will send this and then compare the send time + with the receive time on both ends. + + """ + key = "dummyrunner_echo_response" + +
[docs] def func(self): + # returns (dummy_client_timestamp,current_time) + self.msg(f"dummyrunner_echo_response:{self.args},{time.time()}") + if self.caller.account.is_superuser: + print(f"cmddummyrunner lag in: {time.time() - float(self.args)}s")
+ + +
[docs]class DummyRunnerCmdSet(CmdSet): + """ + Dummyrunner injected cmdset. + + """ +
[docs] def at_cmdset_creation(self): + self.add(CmdDummyRunnerEchoResponse())
+ # ------------------------------------------------------------ # Helper functions # ------------------------------------------------------------ @@ -222,12 +285,12 @@ Makes unique ids. Returns: - count (int): A globally unique counter. + str: A globally unique id. """ global ICOUNT ICOUNT += 1 - return str(ICOUNT)
+ return str("{:03d}".format(ICOUNT)) GCOUNT = 0 @@ -243,7 +306,7 @@ """ global GCOUNT GCOUNT += 1 - return "%s-%s" % (time.strftime(DATESTRING), GCOUNT) + return "%s_%s" % (time.strftime(DATESTRING), GCOUNT)
[docs]def makeiter(obj): @@ -263,7 +326,6 @@ # Client classes # ------------------------------------------------------------ -
[docs]class DummyClient(telnet.StatefulTelnetProtocol): """ Handles connection to a running Evennia server, @@ -272,29 +334,80 @@ """ +
[docs] def report(self, text, clientkey): + pad = " " * (25 - len(text)) + tim = round(time.time() - self.connection_timestamp) + print(f"{text} {clientkey}{pad}\t" + f"conn: {NCONNECTED} -> " + f"welcome screen: {NLOGIN_SCREEN} -> " + f"authing: {NLOGGING_IN} -> " + f"loggedin/tot: {NLOGGED_IN}/{NCLIENTS} (after {tim}s)")
+
[docs] def connectionMade(self): """ Called when connection is first established. """ + global NCONNECTED # public properties self.cid = idcounter() - self.key = "Dummy-%s" % self.cid - self.gid = "%s-%s" % (time.strftime(DATESTRING), self.cid) + self.key = f"Dummy-{self.cid}" + self.gid = f"{time.strftime(DATESTRING)}_{self.cid}" self.istep = 0 self.exits = [] # exit names created self.objs = [] # obj names created + self.connection_timestamp = time.time() + self.connection_attempt = 0 + self.action_started = 0 self._connected = False self._loggedin = False self._logging_out = False + self._ready = False self._report = "" self._cmdlist = [] # already stepping in a cmd definition self._login = self.factory.actions[0] self._logout = self.factory.actions[1] self._actions = self.factory.actions[2:] - reactor.addSystemEventTrigger("before", "shutdown", self.logout)
+ reactor.addSystemEventTrigger("before", "shutdown", self.logout) + + NCONNECTED += 1 + self.report("-> connected", self.key) + + reactor.callLater(30, self._retry_welcome_screen)
+ + def _retry_welcome_screen(self): + if not self._connected and not self._ready: + # we have connected but not received anything for 30s. + # (unclear why this would be - overload?) + # try sending a look to get something to start with + self.report("?? retrying welcome screen", self.key) + self.sendLine(bytes("look", 'utf-8')) + # make sure to check again later + reactor.callLater(30, self._retry_welcome_screen) + + def _print_statistics(self): + global TIME_ALL_LOGIN, TOTAL_ACTIONS + global TOTAL_LAG, TOTAL_LAG_MEASURES, TOTAL_LAG_IN, TOTAL_LAG_OUT + + tim = time.time() - TIME_ALL_LOGIN + avgrate = round(TOTAL_ACTIONS / tim) + lag = TOTAL_LAG / (TOTAL_LAG_MEASURES or 1) + lag_in = TOTAL_LAG_IN / (TOTAL_LAG_MEASURES or 1) + lag_out = TOTAL_LAG_OUT / (TOTAL_LAG_MEASURES or 1) + + TOTAL_ACTIONS = 0 + TOTAL_LAG = 0 + TOTAL_LAG_IN = 0 + TOTAL_LAG_OUT = 0 + TOTAL_LAG_MEASURES = 0 + TIME_ALL_LOGIN = time.time() + + print(f".. running 30s average: ~{avgrate} actions/s " + f"lag: {lag:.2}s (in: {lag_in:.2}s, out: {lag_out:.2}s)") + + reactor.callLater(30, self._print_statistics)
[docs] def dataReceived(self, data): """ @@ -305,15 +418,67 @@ data (str): Incoming data. """ - if not self._connected and not data.startswith(chr(255)): - # wait until we actually get text back (not just telnet - # negotiation) - self._connected = True - # start client tick - d = LoopingCall(self.step) - # dissipate exact step by up to +/- 0.5 second - timestep = TIMESTEP + (-0.5 + (random.random() * 1.0)) - d.start(timestep, now=True).addErrback(self.error)
+ global NLOGIN_SCREEN, NLOGGED_IN, NLOGGING_IN, NCONNECTED + global TOTAL_ACTIONS, TIME_ALL_LOGIN + global TOTAL_LAG, TOTAL_LAG_MEASURES, TOTAL_LAG_IN, TOTAL_LAG_OUT + + if not data.startswith(b"\xff"): + # regular text, not a telnet command + + if NCLIENTS == 1: + print("dummy-client sees:", str(data, "utf-8")) + + if not self._connected: + # waiting for connection + # wait until we actually get text back (not just telnet + # negotiation) + # start client tick + d = LoopingCall(self.step) + df = max(abs(TIMESTEP * 0.001), min(TIMESTEP/10, 0.5)) + # dither next attempt with random time + timestep = TIMESTEP + (-df + (random.random() * df)) + d.start(timestep, now=True).addErrback(self.error) + self.connection_attempt += 1 + + self._connected = True + NLOGIN_SCREEN += 1 + NCONNECTED -= 1 + self.report("<- server sent login screen", self.key) + + elif self._loggedin: + if not self._ready: + # logged in, ready to run + NLOGGED_IN += 1 + NLOGGING_IN -= 1 + self._ready = True + self.report("== logged in", self.key) + if NLOGGED_IN == NCLIENTS and not TIME_ALL_LOGIN: + # all are logged in! We can start collecting statistics + print(".. All clients connected and logged in!") + TIME_ALL_LOGIN = time.time() + reactor.callLater(30, self._print_statistics) + + elif TIME_ALL_LOGIN: + TOTAL_ACTIONS += 1 + + try: + data = strip_ansi(str(data, "utf-8").strip()) + if data.startswith("dummyrunner_echo_response:"): + # handle special lag-measuring command. This returns + # dummyrunner_echo_response:<starttime>,<midpointtime> + now = time.time() + _, data = data.split(":", 1) + start_time, mid_time = (float(part) for part in data.split(",", 1)) + lag_in = mid_time - start_time + lag_out = now - mid_time + total_lag = now - start_time # full round-about time + + TOTAL_LAG += total_lag + TOTAL_LAG_IN += lag_in + TOTAL_LAG_OUT += lag_out + TOTAL_LAG_MEASURES += 1 + except Exception: + pass
[docs] def connectionLost(self, reason): """ @@ -324,7 +489,7 @@ """ if not self._logging_out: - print("client %s(%s) lost connection (%s)" % (self.key, self.cid, reason))
+ self.report("XX lost connection", self.key)
[docs] def error(self, err): """ @@ -351,9 +516,9 @@ """ self._logging_out = True - cmd = self._logout(self) - print("client %s(%s) logout (%s actions)" % (self.key, self.cid, self.istep)) - self.sendLine(cmd)
+ cmd = self._logout(self)[0] + self.report(f"-> logout/disconnect ({self.istep} actions)", self.key) + self.sendLine(bytes(cmd, 'utf-8'))
[docs] def step(self): """ @@ -362,7 +527,7 @@ all "intelligence" of the dummy client. """ - global NLOGGED_IN + global NLOGGING_IN, NLOGIN_SCREEN rand = random.random() @@ -370,11 +535,13 @@ # no commands ready. Load some. if not self._loggedin: - if rand < CHANCE_OF_LOGIN: + if rand < CHANCE_OF_LOGIN or NLOGGING_IN < 10: + # lower rate of logins, but not below 1 / s # get the login commands self._cmdlist = list(makeiter(self._login(self))) - NLOGGED_IN += 1 # this is for book-keeping - print("connecting client %s (%i/%i)..." % (self.key, NLOGGED_IN, NCLIENTS)) + NLOGGING_IN += 1 # this is for book-keeping + NLOGIN_SCREEN -= 1 + self.report("-> create/login", self.key) self._loggedin = True else: # no login yet, so cmdlist not yet set @@ -388,12 +555,26 @@ # at this point we always have a list of commands if rand < CHANCE_OF_ACTION: # send to the game - self.sendLine(str(self._cmdlist.pop(0))) - self.istep += 1
+ cmd = str(self._cmdlist.pop(0)) + + if cmd.startswith("dummyrunner_echo_response"): + # we need to set the timer element as close to + # the send as possible + cmd = cmd.format(timestamp=time.time()) + + self.sendLine(bytes(cmd, 'utf-8')) + self.action_started = time.time() + self.istep += 1 + + if NCLIENTS == 1: + print(f"dummy-client sent: {cmd}") -
[docs]class DummyFactory(protocol.ClientFactory): +
[docs]class DummyFactory(protocol.ReconnectingClientFactory): protocol = DummyClient + initialDelay = 1 + maxDelay = 1 + noisy = False
[docs] def __init__(self, actions): "Setup the factory base (shared by all clients)" @@ -438,7 +619,7 @@ # setting up all clients (they are automatically started) factory = DummyFactory(actions) for i in range(NCLIENTS): - reactor.connectTCP("localhost", TELNET_PORT, factory) + reactor.connectTCP("127.0.0.1", TELNET_PORT, factory) # start reactor reactor.run()
@@ -463,12 +644,23 @@ ) args = parser.parse_args() + nclients = int(args.nclients[0]) - print(INFO_STARTING.format(N=args.nclients[0])) + print(INFO_STARTING.format( + nclients=nclients, + port=TELNET_PORT, + idmapper_cache_size=IDMAPPER_CACHE_MAXSIZE, + timestep=TIMESTEP, + rate=1/TIMESTEP, + chance_of_login=CHANCE_OF_LOGIN * 100, + chance_of_action=CHANCE_OF_ACTION * 100, + avg_rate=(1 / TIMESTEP) * CHANCE_OF_ACTION, + avg_rate_total=(1 / TIMESTEP) * CHANCE_OF_ACTION * nclients + )) # run the dummyrunner - t0 = time.time() - start_all_dummy_clients(nclients=args.nclients[0]) + TIME_START = t0 = time.time() + start_all_dummy_clients(nclients=nclients) ttot = time.time() - t0 # output runtime diff --git a/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner_settings.html b/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner_settings.html index 4ac870d76f..4b281211b8 100644 --- a/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner_settings.html +++ b/docs/0.9.5/_modules/evennia/server/profiling/dummyrunner_settings.html @@ -47,9 +47,9 @@ 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 +- TIMESTEP - time in seconds between each 'tick'. 1 is a good start. +- CHANCE_OF_ACTION - chance 0-1 of action happening. Default is 0.5. +- CHANCE_OF_LOGIN - chance 0-1 of login happening. 0.01 is a good number. - TELNET_PORT - port to use, defaults to settings.TELNET_PORT - ACTIONS - see below @@ -57,23 +57,25 @@ ```python (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 account. 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, +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. + +The PROFILE variable define pre-made ACTION tuples for convenience. + +Each function should return an iterable of one or more command-call +strings (like "look here"), so each can group multiple command 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. +reference to the dummy client currently performing the action. The client object has the following relevant properties and methods: @@ -96,11 +98,15 @@ ---- """ +import random +import string + # Dummy runner settings # Time between each dummyrunner "tick", in seconds. Each dummy # will be called with this frequency. -TIMESTEP = 2 +TIMESTEP = 1 +# TIMESTEP = 0.025 # 40/s # Chance of a dummy actually performing an action on a given tick. # This spreads out usage randomly, like it would be in reality. @@ -109,7 +115,7 @@ # Chance of a currently unlogged-in dummy performing its login # action every tick. This emulates not all accounts logging in # at exactly the same time. -CHANCE_OF_LOGIN = 1.0 +CHANCE_OF_LOGIN = 0.01 # Which telnet port to connect to. If set to None, uses the first # default telnet port of the running server. @@ -120,9 +126,10 @@ # some convenient templates -DUMMY_NAME = "Dummy-%s" -DUMMY_PWD = "password-%s" -START_ROOM = "testing_room_start_%s" +DUMMY_NAME = "Dummy_{gid}" +DUMMY_PWD = (''.join(random.choice(string.ascii_letters + string.digits) + for _ in range(20)) + "-{gid}") +START_ROOM = "testing_room_start_{gid}" ROOM_TEMPLATE = "testing_room_%s" EXIT_TEMPLATE = "exit_%s" OBJ_TEMPLATE = "testing_obj_%s" @@ -135,26 +142,27 @@ # login/logout -
[docs]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 + cname = DUMMY_NAME.format(gid=client.gid) + cpwd = DUMMY_PWD.format(gid=client.gid) + room_name = START_ROOM.format(gid=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]) + # we assign the dummyrunner cmdsert to ourselves so # we can use special commands + add_cmdset = ( + "py from evennia.server.profiling.dummyrunner import DummyRunnerCmdSet;" + "self.cmdset.add(DummyRunnerCmdSet, persistent=False)" + ) + # create character, log in, then immediately dig a new location and + # teleport it (to keep the login room clean) cmds = ( - "create %s %s" % (cname, cpwd), - "connect %s %s" % (cname, cpwd), - "@dig %s" % START_ROOM % client.gid, - "@teleport %s" % START_ROOM % client.gid, - "@dig %s = %s, %s" % (roomname, exitname1, exitname2), + f"create {cname} {cpwd}", + f"connect {cname} {cpwd}", + f"dig {room_name}", + f"teleport {room_name}", + add_cmdset, ) return cmds
@@ -163,14 +171,16 @@ "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)) + cmds = ( + f"create {cname} {cpwd}", + f"connect {cname} {cpwd}" + ) return cmds
[docs]def c_logout(client): "logouts of the game" - return "@quit"
+ return ("quit",)
# random commands @@ -182,7 +192,7 @@ if not cmds: cmds = ["look %s" % exi for exi in client.exits] if not cmds: - cmds = "look" + cmds = ("look",) return cmds @@ -192,7 +202,7 @@ if not cmds: cmds = ["examine %s" % exi for exi in client.exits] if not cmds: - cmds = "examine me" + cmds = ("examine me",) return cmds @@ -204,7 +214,7 @@
[docs]def c_help(client): "reads help files" - cmds = ("help", "help @teleport", "help look", "help @tunnel", "help @dig") + cmds = ("help", "dummyrunner_echo_response",) return cmds
@@ -214,7 +224,7 @@ exitname1 = EXIT_TEMPLATE % client.counter() exitname2 = EXIT_TEMPLATE % client.counter() client.exits.extend([exitname1, exitname2]) - return "@dig/tel %s = %s, %s" % (roomname, exitname1, exitname2) + return ("@dig/tel %s = %s, %s" % (roomname, exitname1, exitname2),)
[docs]def c_creates_obj(client): @@ -241,9 +251,7 @@
[docs]def c_socialize(client): "socializechats on channel" cmds = ( - "ooc Hello!", - "ooc Testing ...", - "ooc Testing ... times 2", + "pub Hello!", "say Yo!", "emote stands looking around.", ) @@ -253,84 +261,120 @@
[docs]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
+ return ("look",) if not cmds else cmds
[docs]def c_moves_n(client): "move through north exit if available" - return "north"
+ return ("north",)
[docs]def c_moves_s(client): "move through south exit if available" - return "south"
+ 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. +
[docs]def c_measure_lag(client): + """ + Special dummyrunner command, injected in c_login. It measures + response time. Including this in the ACTION tuple will give more + dummyrunner output about just how fast commands are being processed. + + The dummyrunner will treat this special and inject the + {timestamp} just before sending. + + """ + return ("dummyrunner_echo_response {timestamp}",)
+ + +# Action profile (required) + +# Some pre-made profiles to test. To make your own, just assign a tuple to ACTIONS. # +# idler - does nothing after logging in +# looker - just looks around +# normal_player - moves around, reads help, looks around (digs rarely) (spammy) +# normal_builder - digs now and then, examines, creates objects, moves +# heavy_builder - digs and creates a lot, moves and examines +# socializing_builder - builds a lot, creates help entries, moves, chat (spammy) +# only_digger - extreme builder that only digs room after room + +PROFILE = "looker" -# "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 account" 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)) -# "inactive account" definition -# ACTIONS = (c_login_nodig, -# c_logout, -# (1.0, c_idles)) -# "normal account" definition -ACTIONS = (c_login, c_logout, (0.01, c_digs), (0.39, c_looks), (0.2, c_help), (0.4, c_moves)) -# walking tester. This requires a pre-made -# "loop" of multiple rooms that ties back -# to limbo (using @tunnel and @open) -# 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)) +if PROFILE == 'idler': + ACTIONS = ( + c_login, + c_logout, + (0.9, c_idles), + (0.1, c_measure_lag), + ) +elif PROFILE == 'looker': + ACTIONS = ( + c_login, + c_logout, + (0.8, c_looks), + (0.2, c_measure_lag) + ) +elif PROFILE == 'normal_player': + ACTIONS = ( + c_login, + c_logout, + (0.01, c_digs), + (0.29, c_looks), + (0.2, c_help), + (0.3, c_moves), + (0.05, c_socialize), + (0.1, c_measure_lag) + ) +elif PROFILE == 'normal_builder': + 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.2, c_moves) + (0.1, c_measure_lag) + ) +elif PROFILE == 'heavy_builder': + ACTIONS = ( + c_login, + c_logout, + (0.1, c_looks), + (0.1, c_examines), + (0.2, c_help), + (0.1, c_digs), + (0.1, c_creates_obj), + (0.2, c_moves), + (0.1, c_measure_lag) + ) +elif PROFILE == 'socializing_builder': + ACTIONS = ( + c_login, + c_logout, + (0.1, c_socialize), + (0.1, c_looks), + (0.1, c_help), + (0.1, c_creates_obj), + (0.2, c_digs), + (0.3, c_moves), + (0.1, c_measure_lag) + ) +elif PROFILE == 'only_digger': + ACTIONS = ( + c_login, + c_logout, + (0.9, c_digs), + (0.1, c_measure_lag) + ) + +else: + print("No dummyrunner ACTION profile defined.") + import sys + sys.exit()
diff --git a/docs/0.9.5/_modules/evennia/server/throttle.html b/docs/0.9.5/_modules/evennia/server/throttle.html index 096836c188..f9621d8cae 100644 --- a/docs/0.9.5/_modules/evennia/server/throttle.html +++ b/docs/0.9.5/_modules/evennia/server/throttle.html @@ -67,7 +67,8 @@ Keyword Args: name (str): Name of this throttle. - limit (int): Max number of failures before imposing limiter + limit (int): Max number of failures before imposing limiter. If `None`, + the throttle is disabled. timeout (int): number of timeout seconds after max number of tries has been reached. cache_size (int): Max number of attempts to record per IP within a @@ -238,6 +239,10 @@ False otherwise. """ + if self.limit is None: + # throttle is disabled + return False + now = time.time() ip = str(ip) diff --git a/docs/0.9.5/api/evennia.commands.default.account.html b/docs/0.9.5/api/evennia.commands.default.account.html index 4f3e3d9900..78b2b2919a 100644 --- a/docs/0.9.5/api/evennia.commands.default.account.html +++ b/docs/0.9.5/api/evennia.commands.default.account.html @@ -70,7 +70,7 @@ method. Otherwise all text will be returned to all connected sessions.

-aliases = ['l', 'ls']ΒΆ
+aliases = ['ls', 'l']ΒΆ
@@ -101,7 +101,7 @@ method. Otherwise all text will be returned to all connected sessions.

-search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look while out-of-character\n\n Usage:\n look\n\n Look in the ooc state.\n '}ΒΆ
+search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look while out-of-character\n\n Usage:\n look\n\n Look in the ooc state.\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.commands.default.admin.html b/docs/0.9.5/api/evennia.commands.default.admin.html index b2f8811083..6dc6896860 100644 --- a/docs/0.9.5/api/evennia.commands.default.admin.html +++ b/docs/0.9.5/api/evennia.commands.default.admin.html @@ -254,7 +254,7 @@ to accounts respectively.

-aliases = ['pemit', 'remit']ΒΆ
+aliases = ['remit', 'pemit']ΒΆ
@@ -285,7 +285,7 @@ to accounts respectively.

-search_index_entry = {'aliases': 'pemit remit', 'category': 'admin', 'key': 'emit', 'tags': '', 'text': '\n admin command for emitting message to multiple objects\n\n Usage:\n emit[/switches] [<obj>, <obj>, ... =] <message>\n remit [<obj>, <obj>, ... =] <message>\n pemit [<obj>, <obj>, ... =] <message>\n\n Switches:\n room - limit emits to rooms only (default)\n accounts - limit emits to accounts only\n contents - send to the contents of matched objects too\n\n Emits a message to the selected objects or to\n your immediate surroundings. If the object is a room,\n send to its contents. remit and pemit are just\n limited forms of emit, for sending to rooms and\n to accounts respectively.\n '}ΒΆ
+search_index_entry = {'aliases': 'remit pemit', 'category': 'admin', 'key': 'emit', 'tags': '', 'text': '\n admin command for emitting message to multiple objects\n\n Usage:\n emit[/switches] [<obj>, <obj>, ... =] <message>\n remit [<obj>, <obj>, ... =] <message>\n pemit [<obj>, <obj>, ... =] <message>\n\n Switches:\n room - limit emits to rooms only (default)\n accounts - limit emits to accounts only\n contents - send to the contents of matched objects too\n\n Emits a message to the selected objects or to\n your immediate surroundings. If the object is a room,\n send to its contents. remit and pemit are just\n limited forms of emit, for sending to rooms and\n to accounts respectively.\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.commands.default.building.html b/docs/0.9.5/api/evennia.commands.default.building.html index 6d9466c565..9be96c7f3d 100644 --- a/docs/0.9.5/api/evennia.commands.default.building.html +++ b/docs/0.9.5/api/evennia.commands.default.building.html @@ -1268,7 +1268,7 @@ server settings.

-aliases = ['update', 'type', 'swap', 'parent']ΒΆ
+aliases = ['type', 'parent', 'swap', 'update']ΒΆ
@@ -1299,7 +1299,7 @@ server settings.

-search_index_entry = {'aliases': 'update type swap parent', 'category': 'building', 'key': 'typeclass', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object.\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}ΒΆ
+search_index_entry = {'aliases': 'type parent swap update', 'category': 'building', 'key': 'typeclass', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object.\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}ΒΆ
@@ -1452,7 +1452,7 @@ If object is not specified, the current location is examined.

-aliases = ['ex', 'exam']ΒΆ
+aliases = ['exam', 'ex']ΒΆ
@@ -1549,7 +1549,7 @@ non-persistent data stored on object

-search_index_entry = {'aliases': 'ex exam', 'category': 'building', 'key': 'examine', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}ΒΆ
+search_index_entry = {'aliases': 'exam ex', 'category': 'building', 'key': 'examine', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.commands.default.comms.html b/docs/0.9.5/api/evennia.commands.default.comms.html index 168cb09f04..790633e56d 100644 --- a/docs/0.9.5/api/evennia.commands.default.comms.html +++ b/docs/0.9.5/api/evennia.commands.default.comms.html @@ -801,7 +801,7 @@ for that channel.

-aliases = ['delaliaschan', 'delchanalias']ΒΆ
+aliases = ['delchanalias', 'delaliaschan']ΒΆ
@@ -832,7 +832,7 @@ for that channel.

-search_index_entry = {'aliases': 'delaliaschan delchanalias', 'category': 'comms', 'key': 'delcom', 'tags': '', 'text': "\n remove a channel alias and/or unsubscribe from channel\n\n Usage:\n delcom <alias or channel>\n delcom/all <channel>\n\n If the full channel name is given, unsubscribe from the\n channel. If an alias is given, remove the alias but don't\n unsubscribe. If the 'all' switch is used, remove all aliases\n for that channel.\n "}ΒΆ
+search_index_entry = {'aliases': 'delchanalias delaliaschan', 'category': 'comms', 'key': 'delcom', 'tags': '', 'text': "\n remove a channel alias and/or unsubscribe from channel\n\n Usage:\n delcom <alias or channel>\n delcom/all <channel>\n\n If the full channel name is given, unsubscribe from the\n channel. If an alias is given, remove the alias but don't\n unsubscribe. If the 'all' switch is used, remove all aliases\n for that channel.\n "}ΒΆ
diff --git a/docs/0.9.5/api/evennia.commands.default.general.html b/docs/0.9.5/api/evennia.commands.default.general.html index ab304fba99..506666ba6c 100644 --- a/docs/0.9.5/api/evennia.commands.default.general.html +++ b/docs/0.9.5/api/evennia.commands.default.general.html @@ -112,7 +112,7 @@ look *<account&g
-aliases = ['l', 'ls']ΒΆ
+aliases = ['ls', 'l']ΒΆ
@@ -143,7 +143,7 @@ look *<account&g
-search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look at location or object\n\n Usage:\n look\n look <obj>\n look *<account>\n\n Observes your location or objects in your vicinity.\n '}ΒΆ
+search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look at location or object\n\n Usage:\n look\n look <obj>\n look *<account>\n\n Observes your location or objects in your vicinity.\n '}ΒΆ
@@ -205,7 +205,7 @@ for everyone to use, you need build privileges and the alias command.

-aliases = ['nickname', 'nicks']ΒΆ
+aliases = ['nicks', 'nickname']ΒΆ
@@ -237,7 +237,7 @@ for everyone to use, you need build privileges and the alias command.

-search_index_entry = {'aliases': 'nickname nicks', 'category': 'general', 'key': 'nick', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}ΒΆ
+search_index_entry = {'aliases': 'nicks nickname', 'category': 'general', 'key': 'nick', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}ΒΆ
@@ -700,7 +700,7 @@ which permission groups you are a member of.

-aliases = ['groups', 'hierarchy']ΒΆ
+aliases = ['hierarchy', 'groups']ΒΆ
@@ -731,7 +731,7 @@ which permission groups you are a member of.

-search_index_entry = {'aliases': 'groups hierarchy', 'category': 'general', 'key': 'access', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}ΒΆ
+search_index_entry = {'aliases': 'hierarchy groups', 'category': 'general', 'key': 'access', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.commands.default.system.html b/docs/0.9.5/api/evennia.commands.default.system.html index 8012cfb18a..315bf42e84 100644 --- a/docs/0.9.5/api/evennia.commands.default.system.html +++ b/docs/0.9.5/api/evennia.commands.default.system.html @@ -385,7 +385,7 @@ given, <nr> defaults to 10.

-aliases = ['db', 'listobjects', 'stats', 'listobjs']ΒΆ
+aliases = ['db', 'listobjects', 'listobjs', 'stats']ΒΆ
@@ -411,7 +411,7 @@ given, <nr> defaults to 10.

-search_index_entry = {'aliases': 'db listobjects stats listobjs', 'category': 'system', 'key': 'objects', 'tags': '', 'text': '\n statistics on objects in the database\n\n Usage:\n objects [<nr>]\n\n Gives statictics on objects in database as well as\n a list of <nr> latest objects in database. If not\n given, <nr> defaults to 10.\n '}ΒΆ
+search_index_entry = {'aliases': 'db listobjects listobjs stats', 'category': 'system', 'key': 'objects', 'tags': '', 'text': '\n statistics on objects in the database\n\n Usage:\n objects [<nr>]\n\n Gives statictics on objects in database as well as\n a list of <nr> latest objects in database. If not\n given, <nr> defaults to 10.\n '}ΒΆ
@@ -612,7 +612,7 @@ the released memory will instead be re-used by the program.

-aliases = ['serverprocess', 'serverload']ΒΆ
+aliases = ['serverload', 'serverprocess']ΒΆ
@@ -643,7 +643,7 @@ the released memory will instead be re-used by the program.

-search_index_entry = {'aliases': 'serverprocess serverload', 'category': 'system', 'key': 'server', 'tags': '', 'text': "\n show server load and memory statistics\n\n Usage:\n server[/mem]\n\n Switches:\n mem - return only a string of the current memory usage\n flushmem - flush the idmapper cache\n\n This command shows server load statistics and dynamic memory\n usage. It also allows to flush the cache of accessed database\n objects.\n\n Some Important statistics in the table:\n\n |wServer load|n is an average of processor usage. It's usually\n between 0 (no usage) and 1 (100% usage), but may also be\n temporarily higher if your computer has multiple CPU cores.\n\n The |wResident/Virtual memory|n displays the total memory used by\n the server process.\n\n Evennia |wcaches|n all retrieved database entities when they are\n loaded by use of the idmapper functionality. This allows Evennia\n to maintain the same instances of an entity and allowing\n non-persistent storage schemes. The total amount of cached objects\n are displayed plus a breakdown of database object types.\n\n The |wflushmem|n switch allows to flush the object cache. Please\n note that due to how Python's memory management works, releasing\n caches may not show you a lower Residual/Virtual memory footprint,\n the released memory will instead be re-used by the program.\n\n "}ΒΆ
+search_index_entry = {'aliases': 'serverload serverprocess', 'category': 'system', 'key': 'server', 'tags': '', 'text': "\n show server load and memory statistics\n\n Usage:\n server[/mem]\n\n Switches:\n mem - return only a string of the current memory usage\n flushmem - flush the idmapper cache\n\n This command shows server load statistics and dynamic memory\n usage. It also allows to flush the cache of accessed database\n objects.\n\n Some Important statistics in the table:\n\n |wServer load|n is an average of processor usage. It's usually\n between 0 (no usage) and 1 (100% usage), but may also be\n temporarily higher if your computer has multiple CPU cores.\n\n The |wResident/Virtual memory|n displays the total memory used by\n the server process.\n\n Evennia |wcaches|n all retrieved database entities when they are\n loaded by use of the idmapper functionality. This allows Evennia\n to maintain the same instances of an entity and allowing\n non-persistent storage schemes. The total amount of cached objects\n are displayed plus a breakdown of database object types.\n\n The |wflushmem|n switch allows to flush the object cache. Please\n note that due to how Python's memory management works, releasing\n caches may not show you a lower Residual/Virtual memory footprint,\n the released memory will instead be re-used by the program.\n\n "}ΒΆ
diff --git a/docs/0.9.5/api/evennia.commands.default.unloggedin.html b/docs/0.9.5/api/evennia.commands.default.unloggedin.html index a3873d69b5..bd9a73a35e 100644 --- a/docs/0.9.5/api/evennia.commands.default.unloggedin.html +++ b/docs/0.9.5/api/evennia.commands.default.unloggedin.html @@ -59,7 +59,7 @@ connect β€œaccount name” β€œpass word”

-aliases = ['conn', 'co', 'con']ΒΆ
+aliases = ['conn', 'con', 'co']ΒΆ
@@ -94,7 +94,7 @@ there is no object yet before the account has logged in)

-search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}ΒΆ
+search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}ΒΆ
@@ -118,7 +118,7 @@ create β€œaccount name” β€œpass word”

-aliases = ['cre', 'cr']ΒΆ
+aliases = ['cr', 'cre']ΒΆ
@@ -149,7 +149,7 @@ create β€œaccount name” β€œpass word”

-search_index_entry = {'aliases': 'cre cr', 'category': 'general', 'key': 'create', 'tags': '', 'text': '\n create a new account account\n\n Usage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\n This creates a new account account.\n\n If you have spaces in your name, enclose it in double quotes.\n '}ΒΆ
+search_index_entry = {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'tags': '', 'text': '\n create a new account account\n\n Usage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\n This creates a new account account.\n\n If you have spaces in your name, enclose it in double quotes.\n '}ΒΆ
@@ -173,7 +173,7 @@ version is a bit more complicated.

-aliases = ['qu', 'q']ΒΆ
+aliases = ['q', 'qu']ΒΆ
@@ -199,7 +199,7 @@ version is a bit more complicated.

-search_index_entry = {'aliases': 'qu q', 'category': 'general', 'key': 'quit', 'tags': '', 'text': '\n quit when in unlogged-in state\n\n Usage:\n quit\n\n We maintain a different version of the quit command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}ΒΆ
+search_index_entry = {'aliases': 'q qu', 'category': 'general', 'key': 'quit', 'tags': '', 'text': '\n quit when in unlogged-in state\n\n Usage:\n quit\n\n We maintain a different version of the quit command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}ΒΆ
@@ -223,7 +223,7 @@ All it does is display the connect screen.

-aliases = ['l', 'look']ΒΆ
+aliases = ['look', 'l']ΒΆ
@@ -249,7 +249,7 @@ All it does is display the connect screen.

-search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}ΒΆ
+search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}ΒΆ
@@ -272,7 +272,7 @@ for simplicity. It shows a pane of info.

-aliases = ['?', 'h']ΒΆ
+aliases = ['h', '?']ΒΆ
@@ -298,7 +298,7 @@ for simplicity. It shows a pane of info.

-search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n get help when in unconnected-in state\n\n Usage:\n help\n\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}ΒΆ
+search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n get help when in unconnected-in state\n\n Usage:\n help\n\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.contrib.barter.html b/docs/0.9.5/api/evennia.contrib.barter.html index b1c1b4e224..fd44276735 100644 --- a/docs/0.9.5/api/evennia.contrib.barter.html +++ b/docs/0.9.5/api/evennia.contrib.barter.html @@ -681,7 +681,7 @@ try to influence the other part in the deal.

-aliases = ['deal', 'offers']ΒΆ
+aliases = ['offers', 'deal']ΒΆ
@@ -707,7 +707,7 @@ try to influence the other part in the deal.

-search_index_entry = {'aliases': 'deal offers', 'category': 'trading', 'key': 'status', 'tags': '', 'text': "\n show a list of the current deal\n\n Usage:\n status\n deal\n offers\n\n Shows the currently suggested offers on each sides of the deal. To\n accept the current deal, use the 'accept' command. Use 'offer' to\n change your deal. You might also want to use 'say', 'emote' etc to\n try to influence the other part in the deal.\n "}ΒΆ
+search_index_entry = {'aliases': 'offers deal', 'category': 'trading', 'key': 'status', 'tags': '', 'text': "\n show a list of the current deal\n\n Usage:\n status\n deal\n offers\n\n Shows the currently suggested offers on each sides of the deal. To\n accept the current deal, use the 'accept' command. Use 'offer' to\n change your deal. You might also want to use 'say', 'emote' etc to\n try to influence the other part in the deal.\n "}ΒΆ
diff --git a/docs/0.9.5/api/evennia.contrib.chargen.html b/docs/0.9.5/api/evennia.contrib.chargen.html index b343048ffd..32a3d0e683 100644 --- a/docs/0.9.5/api/evennia.contrib.chargen.html +++ b/docs/0.9.5/api/evennia.contrib.chargen.html @@ -77,7 +77,7 @@ at them with this command.

-aliases = ['l', 'ls']ΒΆ
+aliases = ['ls', 'l']ΒΆ
@@ -109,7 +109,7 @@ that is checked by the @ic command directly.

-search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n ooc look\n\n Usage:\n look\n look <character>\n\n This is an OOC version of the look command. Since an Account doesn\'t\n have an in-game existence, there is no concept of location or\n "self".\n\n If any characters are available for you to control, you may look\n at them with this command.\n '}ΒΆ
+search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n ooc look\n\n Usage:\n look\n look <character>\n\n This is an OOC version of the look command. Since an Account doesn\'t\n have an in-game existence, there is no concept of location or\n "self".\n\n If any characters are available for you to control, you may look\n at them with this command.\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.contrib.email_login.html b/docs/0.9.5/api/evennia.contrib.email_login.html index 053726c004..ec4543b734 100644 --- a/docs/0.9.5/api/evennia.contrib.email_login.html +++ b/docs/0.9.5/api/evennia.contrib.email_login.html @@ -74,7 +74,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE.

-aliases = ['conn', 'co', 'con']ΒΆ
+aliases = ['conn', 'con', 'co']ΒΆ
@@ -104,7 +104,7 @@ there is no object yet before the account has logged in)

-search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}ΒΆ
+search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}ΒΆ
@@ -126,7 +126,7 @@ there is no object yet before the account has logged in)

-aliases = ['cre', 'cr']ΒΆ
+aliases = ['cr', 'cre']ΒΆ
@@ -162,7 +162,7 @@ name enclosed in quotes:

-search_index_entry = {'aliases': 'cre cr', 'category': 'general', 'key': 'create', 'tags': '', 'text': '\n Create a new account.\n\n Usage (at login screen):\n create "accountname" <email> <password>\n\n This creates a new account account.\n\n '}ΒΆ
+search_index_entry = {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'tags': '', 'text': '\n Create a new account.\n\n Usage (at login screen):\n create "accountname" <email> <password>\n\n This creates a new account account.\n\n '}ΒΆ
@@ -181,7 +181,7 @@ version is a bit more complicated.

-aliases = ['qu', 'q']ΒΆ
+aliases = ['q', 'qu']ΒΆ
@@ -207,7 +207,7 @@ version is a bit more complicated.

-search_index_entry = {'aliases': 'qu q', 'category': 'general', 'key': 'quit', 'tags': '', 'text': '\n We maintain a different version of the `quit` command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}ΒΆ
+search_index_entry = {'aliases': 'q qu', 'category': 'general', 'key': 'quit', 'tags': '', 'text': '\n We maintain a different version of the `quit` command\n here for unconnected accounts for the sake of simplicity. The logged in\n version is a bit more complicated.\n '}ΒΆ
@@ -226,7 +226,7 @@ All it does is display the connect screen.

-aliases = ['l', 'look']ΒΆ
+aliases = ['look', 'l']ΒΆ
@@ -252,7 +252,7 @@ All it does is display the connect screen.

-search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}ΒΆ
+search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}ΒΆ
@@ -270,7 +270,7 @@ for simplicity. It shows a pane of info.

-aliases = ['?', 'h']ΒΆ
+aliases = ['h', '?']ΒΆ
@@ -296,7 +296,7 @@ for simplicity. It shows a pane of info.

-search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}ΒΆ
+search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'tags': '', 'text': '\n This is an unconnected version of the help command,\n for simplicity. It shows a pane of info.\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.contrib.extended_room.html b/docs/0.9.5/api/evennia.contrib.extended_room.html index 32590f9f50..f40cee98b4 100644 --- a/docs/0.9.5/api/evennia.contrib.extended_room.html +++ b/docs/0.9.5/api/evennia.contrib.extended_room.html @@ -276,7 +276,7 @@ look *<account&g
-aliases = ['l', 'ls']ΒΆ
+aliases = ['ls', 'l']ΒΆ
@@ -296,7 +296,7 @@ look *<account&g
-search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look\n\n Usage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects in your vicinity.\n '}ΒΆ
+search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look\n\n Usage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects in your vicinity.\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.contrib.rpsystem.html b/docs/0.9.5/api/evennia.contrib.rpsystem.html index e77d158964..a29f5624b4 100644 --- a/docs/0.9.5/api/evennia.contrib.rpsystem.html +++ b/docs/0.9.5/api/evennia.contrib.rpsystem.html @@ -801,7 +801,7 @@ Using the command without arguments will list all current recogs.

-aliases = ['recognize', 'forget']ΒΆ
+aliases = ['forget', 'recognize']ΒΆ
@@ -828,7 +828,7 @@ Using the command without arguments will list all current recogs.

-search_index_entry = {'aliases': 'recognize forget', 'category': 'general', 'key': 'recog', 'tags': '', 'text': '\n Recognize another person in the same room.\n\n Usage:\n recog\n recog sdesc as alias\n forget alias\n\n Example:\n recog tall man as Griatch\n forget griatch\n\n This will assign a personal alias for a person, or forget said alias.\n Using the command without arguments will list all current recogs.\n\n '}ΒΆ
+search_index_entry = {'aliases': 'forget recognize', 'category': 'general', 'key': 'recog', 'tags': '', 'text': '\n Recognize another person in the same room.\n\n Usage:\n recog\n recog sdesc as alias\n forget alias\n\n Example:\n recog tall man as Griatch\n forget griatch\n\n This will assign a personal alias for a person, or forget said alias.\n Using the command without arguments will list all current recogs.\n\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button.html b/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button.html index 63753128db..c55648be60 100644 --- a/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button.html +++ b/docs/0.9.5/api/evennia.contrib.tutorial_examples.red_button.html @@ -79,7 +79,7 @@ such as when closing the lid and un-blinding a character.

-aliases = ['push', 'press button', 'press']ΒΆ
+aliases = ['press', 'press button', 'push']ΒΆ
@@ -108,7 +108,7 @@ check if the lid is open or closed.

-search_index_entry = {'aliases': 'push press button press', 'category': 'general', 'key': 'push button', 'tags': '', 'text': '\n Push the red button (lid closed)\n\n Usage:\n push button\n\n '}ΒΆ
+search_index_entry = {'aliases': 'press press button push', 'category': 'general', 'key': 'push button', 'tags': '', 'text': '\n Push the red button (lid closed)\n\n Usage:\n push button\n\n '}ΒΆ
@@ -178,7 +178,7 @@ check if the lid is open or closed.

-aliases = ['smash', 'break lid', 'smash lid']ΒΆ
+aliases = ['break lid', 'smash', 'smash lid']ΒΆ
@@ -205,7 +205,7 @@ break.

-search_index_entry = {'aliases': 'smash break lid smash lid', 'category': 'general', 'key': 'smash glass', 'tags': '', 'text': '\n Smash the protective glass.\n\n Usage:\n smash glass\n\n Try to smash the glass of the button.\n\n '}ΒΆ
+search_index_entry = {'aliases': 'break lid smash smash lid', 'category': 'general', 'key': 'smash glass', 'tags': '', 'text': '\n Smash the protective glass.\n\n Usage:\n smash glass\n\n Try to smash the glass of the button.\n\n '}ΒΆ
@@ -305,7 +305,7 @@ be mutually exclusive.

-aliases = ['push', 'press button', 'press']ΒΆ
+aliases = ['press', 'press button', 'push']ΒΆ
@@ -334,7 +334,7 @@ set in self.parse())

-search_index_entry = {'aliases': 'push press button press', 'category': 'general', 'key': 'push button', 'tags': '', 'text': '\n Push the red button\n\n Usage:\n push button\n\n '}ΒΆ
+search_index_entry = {'aliases': 'press press button push', 'category': 'general', 'key': 'push button', 'tags': '', 'text': '\n Push the red button\n\n Usage:\n push button\n\n '}ΒΆ
@@ -432,7 +432,7 @@ be mutually exclusive.

-aliases = ['feel', 'examine', 'get', 'l', 'ex', 'listen']ΒΆ
+aliases = ['examine', 'listen', 'get', 'l', 'feel', 'ex']ΒΆ
@@ -458,7 +458,7 @@ be mutually exclusive.

-search_index_entry = {'aliases': 'feel examine get l ex listen', 'category': 'general', 'key': 'look', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}ΒΆ
+search_index_entry = {'aliases': 'examine listen get l feel ex', 'category': 'general', 'key': 'look', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}ΒΆ
diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html b/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html index 44725f4d0e..fea17d074e 100644 --- a/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html +++ b/docs/0.9.5/api/evennia.contrib.tutorial_world.objects.html @@ -361,7 +361,7 @@ of the object. We overload it with our own version.

-aliases = ['burn', 'light']ΒΆ
+aliases = ['light', 'burn']ΒΆ
@@ -388,7 +388,7 @@ to sit on a β€œlightable” object, we operate only on self.obj.

-search_index_entry = {'aliases': 'burn light', 'category': 'tutorialworld', 'key': 'on', 'tags': '', 'text': '\n Creates light where there was none. Something to burn.\n '}ΒΆ
+search_index_entry = {'aliases': 'light burn', 'category': 'tutorialworld', 'key': 'on', 'tags': '', 'text': '\n Creates light where there was none. Something to burn.\n '}ΒΆ
@@ -492,7 +492,7 @@ shift green root up/down

-aliases = ['push', 'pull', 'move', 'shiftroot']ΒΆ
+aliases = ['pull', 'push', 'move', 'shiftroot']ΒΆ
@@ -528,7 +528,7 @@ yellow/green - horizontal roots

-search_index_entry = {'aliases': 'push pull move shiftroot', 'category': 'tutorialworld', 'key': 'shift', 'tags': '', 'text': '\n Shifts roots around.\n\n Usage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n '}ΒΆ
+search_index_entry = {'aliases': 'pull push move shiftroot', 'category': 'tutorialworld', 'key': 'shift', 'tags': '', 'text': '\n Shifts roots around.\n\n Usage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n '}ΒΆ
@@ -545,7 +545,7 @@ yellow/green - horizontal roots

-aliases = ['press button', 'push button', 'button']ΒΆ
+aliases = ['push button', 'press button', 'button']ΒΆ
@@ -571,7 +571,7 @@ yellow/green - horizontal roots

-search_index_entry = {'aliases': 'press button push button button', 'category': 'tutorialworld', 'key': 'press', 'tags': '', 'text': '\n Presses a button.\n '}ΒΆ
+search_index_entry = {'aliases': 'push button press button button', 'category': 'tutorialworld', 'key': 'press', 'tags': '', 'text': '\n Presses a button.\n '}ΒΆ
@@ -715,7 +715,7 @@ parry - forgoes your attack but will make you harder to hit on next

-aliases = ['fight', 'parry', 'defend', 'hit', 'stab', 'thrust', 'bash', 'kill', 'pierce', 'slash', 'chop']ΒΆ
+aliases = ['chop', 'kill', 'stab', 'thrust', 'parry', 'defend', 'slash', 'hit', 'bash', 'pierce', 'fight']ΒΆ
@@ -741,7 +741,7 @@ parry - forgoes your attack but will make you harder to hit on next

-search_index_entry = {'aliases': 'fight parry defend hit stab thrust bash kill pierce slash chop', 'category': 'tutorialworld', 'key': 'attack', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}ΒΆ
+search_index_entry = {'aliases': 'chop kill stab thrust parry defend slash hit bash pierce fight', 'category': 'tutorialworld', 'key': 'attack', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.contrib.tutorial_world.rooms.html b/docs/0.9.5/api/evennia.contrib.tutorial_world.rooms.html index cfdce7422d..b0788225d7 100644 --- a/docs/0.9.5/api/evennia.contrib.tutorial_world.rooms.html +++ b/docs/0.9.5/api/evennia.contrib.tutorial_world.rooms.html @@ -184,7 +184,7 @@ code except for adding in the details.

-aliases = ['l', 'ls']ΒΆ
+aliases = ['ls', 'l']ΒΆ
@@ -199,7 +199,7 @@ code except for adding in the details.

-search_index_entry = {'aliases': 'l ls', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n looks at the room and on details\n\n Usage:\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects\n in your vicinity.\n\n Tutorial: This is a child of the default Look command, that also\n allows us to look at "details" in the room. These details are\n things to examine and offers some extra description without\n actually having to be actual database objects. It uses the\n return_detail() hook on TutorialRooms for this.\n '}ΒΆ
+search_index_entry = {'aliases': 'ls l', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n looks at the room and on details\n\n Usage:\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects\n in your vicinity.\n\n Tutorial: This is a child of the default Look command, that also\n allows us to look at "details" in the room. These details are\n things to examine and offers some extra description without\n actually having to be actual database objects. It uses the\n return_detail() hook on TutorialRooms for this.\n '}ΒΆ
@@ -713,7 +713,7 @@ if they fall off the bridge.

-aliases = ['?', 'h']ΒΆ
+aliases = ['h', '?']ΒΆ
@@ -739,7 +739,7 @@ if they fall off the bridge.

-search_index_entry = {'aliases': '? h', 'category': 'tutorial world', 'key': 'help', 'tags': '', 'text': '\n Overwritten help command while on the bridge.\n '}ΒΆ
+search_index_entry = {'aliases': 'h ?', 'category': 'tutorial world', 'key': 'help', 'tags': '', 'text': '\n Overwritten help command while on the bridge.\n '}ΒΆ
@@ -865,7 +865,7 @@ to find something.

-aliases = ['feel', 'feel around', 'l', 'fiddle', 'search']ΒΆ
+aliases = ['feel around', 'fiddle', 'l', 'feel', 'search']ΒΆ
@@ -893,7 +893,7 @@ random chance of eventually finding a light source.

-search_index_entry = {'aliases': 'feel feel around l fiddle search', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}ΒΆ
+search_index_entry = {'aliases': 'feel around fiddle l feel search', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.server.profiling.dummyrunner.html b/docs/0.9.5/api/evennia.server.profiling.dummyrunner.html index 7477ba6798..80eb5c460b 100644 --- a/docs/0.9.5/api/evennia.server.profiling.dummyrunner.html +++ b/docs/0.9.5/api/evennia.server.profiling.dummyrunner.html @@ -67,13 +67,83 @@ change which actions by adding a path to

in your settings. See utils.dummyrunner_actions.py for instructions on how to define this module.

+
+
+class evennia.server.profiling.dummyrunner.CmdDummyRunnerEchoResponse(**kwargs)[source]ΒΆ
+

Bases: evennia.commands.command.Command

+

Dummyrunner command measuring the round-about response time +from sending to receiving a result.

+
+
Usage:

dummyrunner_echo_response <timestamp>

+
+
Responds with

dummyrunner_echo_response:<timestamp>,<current_time>

+
+
+

The dummyrunner will send this and then compare the send time +with the receive time on both ends.

+
+
+key = 'dummyrunner_echo_response'ΒΆ
+
+ +
+
+func()[source]ΒΆ
+

This is the actual executing part of the command. It is +called directly after self.parse(). See the docstring of this +module for which object properties are available (beyond those +set in self.parse())

+
+ +
+
+aliases = []ΒΆ
+
+ +
+
+help_category = 'general'ΒΆ
+
+ +
+
+lock_storage = 'cmd:all();'ΒΆ
+
+ +
+
+search_index_entry = {'aliases': '', 'category': 'general', 'key': 'dummyrunner_echo_response', 'tags': '', 'text': '\n Dummyrunner command measuring the round-about response time\n from sending to receiving a result.\n\n Usage:\n dummyrunner_echo_response <timestamp>\n\n Responds with\n dummyrunner_echo_response:<timestamp>,<current_time>\n\n The dummyrunner will send this and then compare the send time\n with the receive time on both ends.\n\n '}ΒΆ
+
+ +
+ +
+
+class evennia.server.profiling.dummyrunner.DummyRunnerCmdSet(cmdsetobj=None, key=None)[source]ΒΆ
+

Bases: evennia.commands.cmdset.CmdSet

+

Dummyrunner injected cmdset.

+
+
+at_cmdset_creation()[source]ΒΆ
+

Hook method - this should be overloaded in the inheriting +class, and should take care of populating the cmdset by use of +self.add().

+
+ +
+
+path = 'evennia.server.profiling.dummyrunner.DummyRunnerCmdSet'ΒΆ
+
+ +
+
evennia.server.profiling.dummyrunner.idcounter()[source]ΒΆ

Makes unique ids.

Returns
-

count (int) – A globally unique counter.

+

str – A globally unique id.

@@ -110,6 +180,11 @@ for instructions on how to define this module.

Handles connection to a running Evennia server, mimicking a real account by sending commands on a timer.

+
+
+report(text, clientkey)[source]ΒΆ
+
+
connectionMade()[source]ΒΆ
@@ -180,13 +255,28 @@ all β€œintelligence” of the dummy client.

class evennia.server.profiling.dummyrunner.DummyFactory(actions)[source]ΒΆ
-

Bases: twisted.internet.protocol.ClientFactory

+

Bases: twisted.internet.protocol.ReconnectingClientFactory

protocolΒΆ

alias of DummyClient

+
+
+initialDelay = 1ΒΆ
+
+ +
+
+maxDelay = 1ΒΆ
+
+ +
+
+noisy = FalseΒΆ
+
+
__init__(actions)[source]ΒΆ
diff --git a/docs/0.9.5/api/evennia.server.profiling.dummyrunner_settings.html b/docs/0.9.5/api/evennia.server.profiling.dummyrunner_settings.html index 2c4a646dee..7a3b7ec7cc 100644 --- a/docs/0.9.5/api/evennia.server.profiling.dummyrunner_settings.html +++ b/docs/0.9.5/api/evennia.server.profiling.dummyrunner_settings.html @@ -44,9 +44,9 @@ the actions available to dummy accounts.

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

  • +
  • TIMESTEP - time in seconds between each β€˜tick’. 1 is a good start.

  • +
  • CHANCE_OF_ACTION - chance 0-1 of action happening. Default is 0.5.

  • +
  • CHANCE_OF_LOGIN - chance 0-1 of login happening. 0.01 is a good number.

  • TELNET_PORT - port to use, defaults to settings.TELNET_PORT

  • ACTIONS - see below

@@ -58,16 +58,15 @@ the actions available to dummy accounts.

chance of occurring given by CHANCE_OF_LOGIN. This function is usually responsible for logging in the account. 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, +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.

+so if will still work also if the given chances don’t add up to 1).

+

The PROFILE variable define pre-made ACTION tuples for convenience.

+

Each function should return an iterable of one or more command-call +strings (like β€œlook here”), so each can group multiple command 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.

+reference to the dummy client currently performing the action.

The client object has the following relevant properties and methods:

  • key - an optional client key. This is only used for dummyrunner output. @@ -170,6 +169,16 @@ commands (such as creating an account and logging in).

    move through south exit if available

+
+
+evennia.server.profiling.dummyrunner_settings.c_measure_lag(client)[source]ΒΆ
+

Special dummyrunner command, injected in c_login. It measures +response time. Including this in the ACTION tuple will give more +dummyrunner output about just how fast commands are being processed.

+

The dummyrunner will treat this special and inject the +{timestamp} just before sending.

+
+ diff --git a/docs/0.9.5/api/evennia.server.throttle.html b/docs/0.9.5/api/evennia.server.throttle.html index 9ecf93b930..ead77dec9c 100644 --- a/docs/0.9.5/api/evennia.server.throttle.html +++ b/docs/0.9.5/api/evennia.server.throttle.html @@ -63,7 +63,8 @@ caches for automatic key eviction and persistence configurability.

Keyword Arguments
  • name (str) – Name of this throttle.

  • -
  • limit (int) – Max number of failures before imposing limiter

  • +
  • limit (int) – Max number of failures before imposing limiter. If None, +the throttle is disabled.

  • timeout (int) – number of timeout seconds after max number of tries has been reached.

  • cache_size (int) – Max number of attempts to record per IP within a diff --git a/docs/0.9.5/api/evennia.utils.eveditor.html b/docs/0.9.5/api/evennia.utils.eveditor.html index 81c5e0c703..9abcae69ea 100644 --- a/docs/0.9.5/api/evennia.utils.eveditor.html +++ b/docs/0.9.5/api/evennia.utils.eveditor.html @@ -274,7 +274,7 @@ indentation.

    -aliases = [':w', ':x', ':r', ':UU', '::', ':dd', ':uu', ':s', ':q!', ':y', ':fi', ':=', ':<', ':p', ':A', ':::', ':q', ':dw', ':h', ':i', ':', ':!', ':f', ':wq', ':I', ':u', ':S', ':fd', ':j', ':>', ':echo', ':DD']ΒΆ
    +aliases = ['::', ':uu', ':r', ':x', ':I', ':UU', ':dd', ':y', ':', ':S', ':dw', ':h', ':<', ':::', ':A', ':!', ':s', ':fd', ':wq', ':j', ':fi', ':w', ':p', ':>', ':echo', ':=', ':i', ':u', ':DD', ':f', ':q!', ':q']ΒΆ
    @@ -302,7 +302,7 @@ efficient presentation.

    -search_index_entry = {'aliases': ':w :x :r :UU :: :dd :uu :s :q! :y :fi := :< :p :A ::: :q :dw :h :i : :! :f :wq :I :u :S :fd :j :> :echo :DD', 'category': 'general', 'key': ':editor_command_group', 'tags': '', 'text': '\n Commands for the editor\n '}ΒΆ
    +search_index_entry = {'aliases': ':: :uu :r :x :I :UU :dd :y : :S :dw :h :< ::: :A :! :s :fd :wq :j :fi :w :p :> :echo := :i :u :DD :f :q! :q', 'category': 'general', 'key': ':editor_command_group', 'tags': '', 'text': '\n Commands for the editor\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.utils.evmenu.html b/docs/0.9.5/api/evennia.utils.evmenu.html index adfac495e7..48e45088aa 100644 --- a/docs/0.9.5/api/evennia.utils.evmenu.html +++ b/docs/0.9.5/api/evennia.utils.evmenu.html @@ -940,7 +940,7 @@ single question.

-aliases = ['no', 'n', 'yes', 'a', 'abort', '__nomatch_command', 'y']ΒΆ
+aliases = ['y', 'no', 'a', 'yes', 'abort', '__nomatch_command', 'n']ΒΆ
@@ -966,7 +966,7 @@ single question.

-search_index_entry = {'aliases': 'no n yes a abort __nomatch_command y', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}ΒΆ
+search_index_entry = {'aliases': 'y no a yes abort __nomatch_command n', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}ΒΆ
diff --git a/docs/0.9.5/api/evennia.utils.evmore.html b/docs/0.9.5/api/evennia.utils.evmore.html index 9f0671a2dc..87caf3630e 100644 --- a/docs/0.9.5/api/evennia.utils.evmore.html +++ b/docs/0.9.5/api/evennia.utils.evmore.html @@ -75,7 +75,7 @@ the caller.msg() construct every time the page is updated.

-aliases = ['b', 'next', 'e', 'end', 'q', 'n', 'a', 'abort', 'top', 'quit', 't', 'back']ΒΆ
+aliases = ['next', 'end', 'b', 'quit', 'top', 'a', 't', 'abort', 'q', 'n', 'e', 'back']ΒΆ
@@ -101,7 +101,7 @@ the caller.msg() construct every time the page is updated.

-search_index_entry = {'aliases': 'b next e end q n a abort top quit t back', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Manipulate the text paging\n '}ΒΆ
+search_index_entry = {'aliases': 'next end b quit top a t abort q n e back', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Manipulate the text paging\n '}ΒΆ
diff --git a/docs/0.9.5/genindex.html b/docs/0.9.5/genindex.html index e3b1b944f6..c5709413dc 100644 --- a/docs/0.9.5/genindex.html +++ b/docs/0.9.5/genindex.html @@ -961,6 +961,8 @@
  • (evennia.help.models.HelpEntry attribute)
  • (evennia.objects.objects.ExitCommand attribute) +
  • +
  • (evennia.server.profiling.dummyrunner.CmdDummyRunnerEchoResponse attribute)
  • (evennia.typeclasses.models.TypedObject attribute)
  • @@ -1345,6 +1347,8 @@
  • (evennia.contrib.tutorial_world.rooms.DarkCmdSet method)
  • (evennia.contrib.tutorial_world.rooms.TutorialRoomCmdSet method) +
  • +
  • (evennia.server.profiling.dummyrunner.DummyRunnerCmdSet method)
  • (evennia.utils.eveditor.EvEditorCmdSet method)
  • @@ -2084,6 +2088,8 @@
  • c_logout() (in module evennia.server.profiling.dummyrunner_settings)
  • c_looks() (in module evennia.server.profiling.dummyrunner_settings) +
  • +
  • c_measure_lag() (in module evennia.server.profiling.dummyrunner_settings)
  • c_moves() (in module evennia.server.profiling.dummyrunner_settings)
  • @@ -2511,6 +2517,8 @@
  • (class in evennia.contrib.clothing)
  • +
  • CmdDummyRunnerEchoResponse (class in evennia.server.profiling.dummyrunner) +
  • CmdEast (class in evennia.contrib.tutorial_world.rooms)
  • CmdEditorBase (class in evennia.utils.eveditor) @@ -2608,11 +2616,11 @@
  • CmdLock (class in evennia.commands.default.building)
  • CmdLook (class in evennia.commands.default.general) -
  • -
  • CmdLookBridge (class in evennia.contrib.tutorial_world.rooms)
  • - - +
  • initialize_for_combat() (evennia.contrib.turnbattle.tb_basic.TBBasicTurnHandler method) @@ -7977,6 +7993,8 @@
  • (evennia.server.portal.amp.MsgServer2Portal attribute)
  • (evennia.server.portal.amp.MsgStatus attribute) +
  • +
  • (evennia.server.profiling.dummyrunner.CmdDummyRunnerEchoResponse attribute)
  • (evennia.utils.eveditor.CmdEditorBase attribute)
  • @@ -8607,6 +8625,8 @@
  • (evennia.contrib.unixcommand.UnixCommand attribute)
  • (evennia.objects.objects.ExitCommand attribute) +
  • +
  • (evennia.server.profiling.dummyrunner.CmdDummyRunnerEchoResponse attribute)
  • (evennia.utils.eveditor.CmdEditorBase attribute)
  • @@ -9105,6 +9125,8 @@
  • (evennia.server.portal.grapevine.RestartingWebsocketServerFactory attribute)
  • (evennia.server.portal.irc.IRCBotFactory attribute) +
  • +
  • (evennia.server.profiling.dummyrunner.DummyFactory attribute)
  • maxDiff (evennia.commands.default.tests.TestHelp attribute) @@ -9940,6 +9962,8 @@
  • (evennia.server.portal.ssh.TerminalSessionTransport_getPeer attribute)
  • (evennia.server.portal.telnet.TelnetServerFactory attribute) +
  • +
  • (evennia.server.profiling.dummyrunner.DummyFactory attribute)
  • (evennia.server.webserver.Website attribute)
  • @@ -10462,6 +10486,8 @@
  • (evennia.scripts.scripts.Store attribute)
  • (evennia.server.models.ServerConfig attribute) +
  • +
  • (evennia.server.profiling.dummyrunner.DummyRunnerCmdSet attribute)
  • (evennia.server.profiling.memplot.Memplot attribute)
  • @@ -10987,6 +11013,8 @@
  • replace_data() (evennia.utils.evtable.EvCell method)
  • replace_timeslots() (evennia.contrib.extended_room.ExtendedRoom method) +
  • +
  • report() (evennia.server.profiling.dummyrunner.DummyClient method)
  • requestAvatarId() (evennia.server.portal.ssh.AccountDBPasswordChecker method)
  • @@ -11752,6 +11780,8 @@
  • (evennia.contrib.unixcommand.UnixCommand attribute)
  • (evennia.objects.objects.ExitCommand attribute) +
  • +
  • (evennia.server.profiling.dummyrunner.CmdDummyRunnerEchoResponse attribute)
  • (evennia.utils.eveditor.CmdEditorBase attribute)
  • diff --git a/docs/0.9.5/objects.inv b/docs/0.9.5/objects.inv index 840a4134d3ba2fa64ec195fe1b77af7a5beab5ad..8fbf4df5843bf508a1d9506eefd9630def4f4cc7 100644 GIT binary patch delta 68793 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 pfZ*GKLEav~$VTGdEIXaW1Dujw3 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 is considered an advanced topic mainly of interest to server developers.

    Simple timer testsΒΆ

    -

    Python’s timeit module is very good for testing small things. For example, in order to test if it -is faster to use a for loop or a list comprehension you could use the following code:

    +

    Python’s timeit module is very good for testing small things. For example, in +order to test if it is faster to use a for loop or a list comprehension you +could use the following code:

    1
     2
     3
    @@ -75,87 +78,251 @@ is faster to use a 
        <<<  5.358283996582031
     
    -

    The setup keyword is used to set up things that should not be included in the time measurement, -like a = [] in the first call.

    -

    By default the timeit function will re-run the given test 1000000 times and returns the total -time to do so (so not the average per test). A hint is to not use this default for testing -something that includes database writes - for that you may want to use a lower number of repeats -(say 100 or 1000) using the number=100 keyword.

    +

    The setup keyword is used to set up things that should not be included in the +time measurement, like a = [] in the first call.

    +

    By default the timeit function will re-run the given test 1000000 times and +returns the total time to do so (so not the average per test). A hint is to +not use this default for testing something that includes database writes - for +that you may want to use a lower number of repeats (say 100 or 1000) using the +number=100 keyword.

    Using cProfileΒΆ

    -

    Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done -with pypy at this point). Due to the way Evennia’s processes are handled, there is no point in -using the normal way to start the profiler (python -m cProfile evennia.py). Instead you start the -profiler through the launcher:

    +

    Python comes with its own profiler, named cProfile (this is for cPython, no +tests have been done with pypy at this point). Due to the way Evennia’s +processes are handled, there is no point in using the normal way to start the +profiler (python -m cProfile evennia.py). Instead you start the profiler +through the launcher:

    evennia --profiler start
     
    -

    This will start Evennia with the Server component running (in daemon mode) under cProfile. You could -instead try --profile with the portal argument to profile the Portal (you would then need to +

    This will start Evennia with the Server component running (in daemon mode) under +cProfile. You could instead try --profile with the portal argument to +profile the Portal (you would then need to start the Server separately).

    -

    Please note that while the profiler is running, your process will use a lot more memory than usual. -Memory usage is even likely to climb over time. So don’t leave it running perpetually but monitor it -carefully (for example using the top command on Linux or the Task Manager’s memory display on -Windows).

    -

    Once you have run the server for a while, you need to stop it so the profiler can give its report. -Do not kill the program from your task manager or by sending it a kill signal - this will most -likely also mess with the profiler. Instead either use evennia.py stop or (which may be even -better), use @shutdown from inside the game.

    -

    Once the server has fully shut down (this may be a lot slower than usual) you will find that -profiler has created a new file mygame/server/logs/server.prof.

    -
    +

    Please note that while the profiler is running, your process will use a lot more +memory than usual. Memory usage is even likely to climb over time. So don’t +leave it running perpetually but monitor it carefully (for example using the +top command on Linux or the Task Manager’s memory display on Windows).

    +

    Once you have run the server for a while, you need to stop it so the profiler +can give its report. Do not kill the program from your task manager or by +sending it a kill signal - this will most likely also mess with the profiler. +Instead either use evennia.py stop or (which may be even better), use +@shutdown from inside the game.

    +

    Once the server has fully shut down (this may be a lot slower than usual) you +will find that profiler has created a new file mygame/server/logs/server.prof.

    -

    Analyzing the profileΒΆ

    -

    The server.prof file is a binary file. There are many ways to analyze and display its contents, -all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works).

    -

    We recommend the -Runsnake visualizer to see the processor usage -of different processes in a graphical form. For more detailed listing of usage time, you can use -KCachegrind. To make KCachegrind work with -Python profiles you also need the wrapper script -pyprof2calltree. You can get pyprof2calltree via -pip whereas KCacheGrind is something you need to get via your package manager or their homepage.

    -

    How to analyze and interpret profiling data is not a trivial issue and depends on what you are -profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing -list if you need help and be ready to be able to supply your server.prof file for comparison, -along with the exact conditions under which it was obtained.

    +

    Analyzing the profileΒΆ

    +

    The server.prof file is a binary file. There are many ways to analyze and +display its contents, all of which has only been tested in Linux (If you are a +Windows/Mac user, let us know what works).

    +

    You can look at the contents of the profile file with Python’s in-built pstats +module in the evennia shell (it’s recommended you install ipython with pip install ipython in your virtualenv first, for prettier output):

    +
    evennia shell
    +
    +
    +

    Then in the shell

    +
    1
    +2
    +3
    +4
    +5
    import pstats
    +from pstats import SortKey
    +
    +p = pstats.Stats('server/log/server.prof')
    +p.strip_dirs().sort_stats(-1).print_stats()
    +
    +
    +

    See the +Python profiling documentation +for more information.

    +

    You can also visualize the data in various ways.

    +
      +
    • Runsnake visualizes the profile to +give a good overview. Install with pip install runsnakerun. Note that this +may require a C compiler and be quite slow to install.

    • +
    • For more detailed listing of usage time, you can use +KCachegrind. To make +KCachegrind work with Python profiles you also need the wrapper script +pyprof2calltree. You can get +pyprof2calltree via pip whereas KCacheGrind is something you need to get +via your package manager or their homepage.

    • +
    +

    How to analyze and interpret profiling data is not a trivial issue and depends +on what you are profiling for. Evennia being an asynchronous server can also +confuse profiling. Ask on the mailing list if you need help and be ready to be +able to supply your server.prof file for comparison, along with the exact +conditions under which it was obtained.

    +

    The DummyrunnerΒΆ

    -

    It is difficult to test β€œactual” game performance without having players in your game. For this -reason Evennia comes with the Dummyrunner system. The Dummyrunner is a stress-testing system: a -separate program that logs into your game with simulated players (aka β€œbots” or β€œdummies”). Once -connected these dummies will semi-randomly perform various tasks from a list of possible actions. -Use Ctrl-C to stop the Dummyrunner.

    -
    -

    Warning: You should not run the Dummyrunner on a production database. It will spawn many objects -and also needs to run with general permissions.

    -
    -

    To launch the Dummyrunner, first start your server normally (with or without profiling, as above). -Then start a new terminal/console window and active your virtualenv there too. In the new terminal, -try to connect 10 dummy players:

    -
    evennia --dummyrunner 10
    +

    It is difficult to test β€œactual” game performance without having players in your +game. For this reason Evennia comes with the Dummyrunner system. The +Dummyrunner is a stress-testing system: a separate program that logs into your +game with simulated players (aka β€œbots” or β€œdummies”). Once connected, these +dummies will semi-randomly perform various tasks from a list of possible +actions. Use Ctrl-C to stop the Dummyrunner.

    +
    +

    Warning

    +

    You should not run the Dummyrunner on a production database. It +will spawn many objects and also needs to run with general permissions.

    +
    +

    This is the recommended process for using the dummy runner:

    +
      +
    1. Stop your server completely with evennia stop.

    2. +
    3. At the end of your mygame/server/conf.settings.py file, add the line

      +
       from evennia.server.profiling.settings_mixin import *
       
      -

      The first time you do this you will most likely get a warning from Dummyrunner. It will tell you to -copy an import string to the end of your settings file. Quit the Dummyrunner (Ctrl-C) and follow -the instructions. Restart Evennia and try evennia --dummyrunner 10 again. Make sure to remove that -extra settings line when running a public server.

      -

      The actions perform by the dummies is controlled by a settings file. The default Dummyrunner -settings file is evennia/server/server/profiling/dummyrunner_settings.py but you shouldn’t modify -this directly. Rather create/copy the default file to mygame/server/conf/ and modify it there. To -make sure to use your file over the default, add the following line to your settings file:

      -
      + -
      1
      DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py"
      +

      This will override your settings and disable Evennia’s rate limiters and +DoS-protections, which would otherwise block mass-connecting clients from +one IP. Notably, it will also change to a different (faster) password hasher.

      + +
    4. (recommended): Build a new database. If you use default Sqlite3 and want to +keep your existing database, just rename mygame/server/evennia.db3 to +mygame/server/evennia.db3_backup and run evennia migrate and evennia start to create a new superuser as usual.

    5. +
    6. (recommended) Log into the game as your superuser. This is just so you +can manually check response. If you kept an old database, you will not +be able to connect with an existing user since the password hasher changed!

    7. +
    8. Start the dummyrunner with 10 dummy users from the terminal with

      +
       evennia --dummyrunner 10
      +
      +
      +

      Use Ctrl-C (or Cmd-C) to stop it.

      +
    9. + +

      If you want to see what the dummies are actually doing you can run with a single +dummy:

      +
      evennia --dummyrunner 1
      +
      +
      +

      The inputs/outputs from the dummy will then be printed. By default the runner +uses the β€˜looker’ profile, which just logs in and sends the β€˜look’ command +over and over. To change the settings, copy the file +evennia/server/profiling/dummyrunner_settings.py to your mygame/server/conf/ +directory, then add this line to your settings file to use it in the new +location:

      +
      DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py"
      +
      +
      +

      The dummyrunner settings file is a python code module in its own right - it +defines the actions available to the dummies. These are just tuples of command +strings (like β€œlook here”) for the dummy to send to the server along with a +probability of them happening. The dummyrunner looks for a global variable +ACTIONS, a list of tuples, where the first two elements define the +commands for logging in/out of the server.

      +

      Below is a simplified minimal setup (the default settings file adds a lot more +functionality and info):

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      +26
      +27
      +28
      +29
      +30
      +31
      +32
      +33
      +34
      +35
      +36
      +37
      +38
      +39
      +40
      # minimal dummyrunner setup file
      +
      +# Time between each dummyrunner "tick", in seconds. Each dummy will be called
      +# with this frequency.
      +TIMESTEP = 1
      +
      +# 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.5
      +
      +# Chance of a currently unlogged-in dummy performing its login action every
      +# tick. This emulates not all accounts logging in at exactly the same time.
      +CHANCE_OF_LOGIN = 0.01
      +
      +# Which telnet port to connect to. If set to None, uses the first default
      +# telnet port of the running server.
      +TELNET_PORT = None
      +
      +# actions
      +
      +def c_login(client):
      +    name = f"Character-{client.gid}"
      +    pwd = f"23fwsf23sdfw23wef23"
      +    return (
      +        f"create {name} {pwd}"
      +        f"connect {name} {pwd}"
      +    )
      +
      +def c_logout(client):
      +    return ("quit", )
      +
      +def c_look(client):
      +    return ("look here", "look me")
      +
      +# this is read by dummyrunner.
      +ACTIONS = (
      +    c_login,
      +    c_logout,
      +    (1.0, c_look)   # (probability, command-generator)
      +)
       
      -
      -

      Hint: Don’t start with too many dummies. The Dummyrunner defaults to taxing the server much more -intensely than an equal number of human players. A good dummy number to start with is 10-100.

      -
      -

      Once you have the dummyrunner running, stop it with Ctrl-C.

      -

      Generally, the dummyrunner system makes for a decent test of general performance; but it is of -course hard to actually mimic human user behavior. For this, actual real-game testing is required.

      +

      At the bottom of the default file are a few default profiles you can test out +by just setting the PROFILE variable to one of the options.

      +
      +

      Dummyrunner hintsΒΆ

      +
        +
      • Don’t start with too many dummies. The Dummyrunner taxes the server much more +than β€˜real’ users tend to do. Start with 10-100 to begin with.

      • +
      • Stress-testing can be fun, but also consider what a β€˜realistic’ number of +users would be for your game.

      • +
      • Note in the dummyrunner output how many commands/s are being sent to the +server by all dummies. This is usually a lot higher than what you’d +realistically expect to see from the same number of users.

      • +
      • The default settings sets up a β€˜lag’ measure to measaure the round-about +message time. It updates with an average every 30 seconds. It can be worth to +have this running for a small number of dummies in one terminal before adding +more by starting another dummyrunner in another terminal - the first one will +act as a measure of how lag changes with different loads. Also verify the +lag-times by entering commands manually in-game.

      • +
      • Check the CPU usage of your server using top/htop (linux). In-game, use the +server command.

      • +
      • You can run the server with --profiler start to test it with dummies. Note +that the profiler will itself affect server performance, especially memory +consumption.

      • +
      • Generally, the dummyrunner system makes for a decent test of general +performance; but it is of course hard to actually mimic human user behavior. +For this, actual real-game testing is required.

      • +
      +
      @@ -184,9 +351,14 @@ course hard to actually mimic human user behavior. For this, actual real-game te
    10. Profiling
    11. diff --git a/docs/1.0-dev/Components/Channels.html b/docs/1.0-dev/Components/Channels.html index 666339a447..43bcf1e640 100644 --- a/docs/1.0-dev/Components/Channels.html +++ b/docs/1.0-dev/Components/Channels.html @@ -119,7 +119,7 @@ bar Hello again!

      And even remove the default one if they don’t want to use it

      channel/unalias public
      -public Hello
      +public Hello    (gives a command-not-found error now)
       

      But you can also use your alias with the channel command:

      diff --git a/docs/1.0-dev/_modules/evennia/server/portal/portalsessionhandler.html b/docs/1.0-dev/_modules/evennia/server/portal/portalsessionhandler.html index a4047de2d7..bc53fd2443 100644 --- a/docs/1.0-dev/_modules/evennia/server/portal/portalsessionhandler.html +++ b/docs/1.0-dev/_modules/evennia/server/portal/portalsessionhandler.html @@ -79,7 +79,8 @@ # Portal-SessionHandler class # ------------------------------------------------------------- -DOS_PROTECTION_MSG = _("{servername} DoS protection is active. You are queued to connect in {num} seconds ...") +DOS_PROTECTION_MSG = _("{servername} DoS protection is active." + "You are queued to connect in {num} seconds ...")
      [docs]class PortalSessionHandler(SessionHandler): diff --git a/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner.html b/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner.html index e300494bc7..bfef7e1c8f 100644 --- a/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner.html +++ b/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner.html @@ -82,8 +82,16 @@ from twisted.internet import reactor, protocol from twisted.internet.task import LoopingCall -from django.conf import settings -from evennia.utils import mod_import, time_format +import django +django.setup() +import evennia # noqa +evennia._init() + +from django.conf import settings # noqa +from evennia.utils import mod_import, time_format # noqa +from evennia.commands.command import Command # noqa +from evennia.commands.cmdset import CmdSet # noqa +from evennia.utils.ansi import strip_ansi # noqa # Load the dummyrunner settings module @@ -93,8 +101,10 @@ "Error: Dummyrunner could not find settings file at %s" % settings.DUMMYRUNNER_SETTINGS_MODULE ) +IDMAPPER_CACHE_MAXSIZE = settings.IDMAPPER_CACHE_MAXSIZE DATESTRING = "%Y%m%d%H%M%S" +CLIENTS = [] # Settings @@ -113,18 +123,37 @@ # Port to use, if not specified on command line TELNET_PORT = DUMMYRUNNER_SETTINGS.TELNET_PORT or settings.TELNET_PORTS[0] # -NLOGGED_IN = 0 +NCONNECTED = 0 # client has received a connection +NLOGIN_SCREEN = 0 # client has seen the login screen (server responded) +NLOGGING_IN = 0 # client starting login procedure +NLOGGED_IN = 0 # client has authenticated and logged in - -# Messages +# time when all clients have logged_in +TIME_ALL_LOGIN = 0 +# actions since all logged in +TOTAL_ACTIONS = 0 +TOTAL_LAG_MEASURES = 0 +# lag per 30s for all logged in +TOTAL_LAG = 0 +TOTAL_LAG_IN = 0 +TOTAL_LAG_OUT = 0 INFO_STARTING = """ - Dummyrunner starting using {N} dummy account(s). If you don't see + Dummyrunner starting using {nclients} dummy account(s). If you don't see any connection messages, make sure that the Evennia server is running. - Use Ctrl-C to stop/disconnect clients. + TELNET_PORT = {port} + IDMAPPER_CACHE_MAXSIZE = {idmapper_cache_size} MB + TIMESTEP = {timestep} (rate {rate}/s) + CHANCE_OF_LOGIN = {chance_of_login}% per time step + CHANCE_OF_ACTION = {chance_of_action}% per time step + -> avg rate (per client, after login): {avg_rate} cmds/s + -> total avg rate (after login): {avg_rate_total} cmds/s + + Use Ctrl-C (or Cmd-C) to stop/disconnect all clients. + """ ERROR_NO_MIXIN = """ @@ -139,6 +168,7 @@ to test all commands - change PASSWORD_HASHERS to use a faster (but less safe) algorithm when creating large numbers of accounts at the same time + - set LOGIN_THROTTLE/CREATION_THROTTLE=None to disable it If you don't want to use the custom settings of the mixin for some reason, you can change their values manually after the import, or @@ -210,6 +240,39 @@ """ + +
      [docs]class CmdDummyRunnerEchoResponse(Command): + """ + Dummyrunner command measuring the round-about response time + from sending to receiving a result. + + Usage: + dummyrunner_echo_response <timestamp> + + Responds with + dummyrunner_echo_response:<timestamp>,<current_time> + + The dummyrunner will send this and then compare the send time + with the receive time on both ends. + + """ + key = "dummyrunner_echo_response" + +
      [docs] def func(self): + # returns (dummy_client_timestamp,current_time) + self.msg(f"dummyrunner_echo_response:{self.args},{time.time()}") + if self.caller.account.is_superuser: + print(f"cmddummyrunner lag in: {time.time() - float(self.args)}s")
      + + +
      [docs]class DummyRunnerCmdSet(CmdSet): + """ + Dummyrunner injected cmdset. + + """ +
      [docs] def at_cmdset_creation(self): + self.add(CmdDummyRunnerEchoResponse())
      + # ------------------------------------------------------------ # Helper functions # ------------------------------------------------------------ @@ -223,12 +286,12 @@ Makes unique ids. Returns: - count (int): A globally unique counter. + str: A globally unique id. """ global ICOUNT ICOUNT += 1 - return str(ICOUNT)
      + return str("{:03d}".format(ICOUNT)) GCOUNT = 0 @@ -244,7 +307,7 @@ """ global GCOUNT GCOUNT += 1 - return "%s-%s" % (time.strftime(DATESTRING), GCOUNT) + return "%s_%s" % (time.strftime(DATESTRING), GCOUNT)
      [docs]def makeiter(obj): @@ -264,7 +327,6 @@ # Client classes # ------------------------------------------------------------ -
      [docs]class DummyClient(telnet.StatefulTelnetProtocol): """ Handles connection to a running Evennia server, @@ -273,29 +335,80 @@ """ +
      [docs] def report(self, text, clientkey): + pad = " " * (25 - len(text)) + tim = round(time.time() - self.connection_timestamp) + print(f"{text} {clientkey}{pad}\t" + f"conn: {NCONNECTED} -> " + f"welcome screen: {NLOGIN_SCREEN} -> " + f"authing: {NLOGGING_IN} -> " + f"loggedin/tot: {NLOGGED_IN}/{NCLIENTS} (after {tim}s)")
      +
      [docs] def connectionMade(self): """ Called when connection is first established. """ + global NCONNECTED # public properties self.cid = idcounter() - self.key = "Dummy-%s" % self.cid - self.gid = "%s-%s" % (time.strftime(DATESTRING), self.cid) + self.key = f"Dummy-{self.cid}" + self.gid = f"{time.strftime(DATESTRING)}_{self.cid}" self.istep = 0 self.exits = [] # exit names created self.objs = [] # obj names created + self.connection_timestamp = time.time() + self.connection_attempt = 0 + self.action_started = 0 self._connected = False self._loggedin = False self._logging_out = False + self._ready = False self._report = "" self._cmdlist = [] # already stepping in a cmd definition self._login = self.factory.actions[0] self._logout = self.factory.actions[1] self._actions = self.factory.actions[2:] - reactor.addSystemEventTrigger("before", "shutdown", self.logout)
      + reactor.addSystemEventTrigger("before", "shutdown", self.logout) + + NCONNECTED += 1 + self.report("-> connected", self.key) + + reactor.callLater(30, self._retry_welcome_screen)
      + + def _retry_welcome_screen(self): + if not self._connected and not self._ready: + # we have connected but not received anything for 30s. + # (unclear why this would be - overload?) + # try sending a look to get something to start with + self.report("?? retrying welcome screen", self.key) + self.sendLine(bytes("look", 'utf-8')) + # make sure to check again later + reactor.callLater(30, self._retry_welcome_screen) + + def _print_statistics(self): + global TIME_ALL_LOGIN, TOTAL_ACTIONS + global TOTAL_LAG, TOTAL_LAG_MEASURES, TOTAL_LAG_IN, TOTAL_LAG_OUT + + tim = time.time() - TIME_ALL_LOGIN + avgrate = round(TOTAL_ACTIONS / tim) + lag = TOTAL_LAG / (TOTAL_LAG_MEASURES or 1) + lag_in = TOTAL_LAG_IN / (TOTAL_LAG_MEASURES or 1) + lag_out = TOTAL_LAG_OUT / (TOTAL_LAG_MEASURES or 1) + + TOTAL_ACTIONS = 0 + TOTAL_LAG = 0 + TOTAL_LAG_IN = 0 + TOTAL_LAG_OUT = 0 + TOTAL_LAG_MEASURES = 0 + TIME_ALL_LOGIN = time.time() + + print(f".. running 30s average: ~{avgrate} actions/s " + f"lag: {lag:.2}s (in: {lag_in:.2}s, out: {lag_out:.2}s)") + + reactor.callLater(30, self._print_statistics)
      [docs] def dataReceived(self, data): """ @@ -306,15 +419,67 @@ data (str): Incoming data. """ - if not self._connected and not data.startswith(chr(255)): - # wait until we actually get text back (not just telnet - # negotiation) - self._connected = True - # start client tick - d = LoopingCall(self.step) - # dissipate exact step by up to +/- 0.5 second - timestep = TIMESTEP + (-0.5 + (random.random() * 1.0)) - d.start(timestep, now=True).addErrback(self.error)
      + global NLOGIN_SCREEN, NLOGGED_IN, NLOGGING_IN, NCONNECTED + global TOTAL_ACTIONS, TIME_ALL_LOGIN + global TOTAL_LAG, TOTAL_LAG_MEASURES, TOTAL_LAG_IN, TOTAL_LAG_OUT + + if not data.startswith(b"\xff"): + # regular text, not a telnet command + + if NCLIENTS == 1: + print("dummy-client sees:", str(data, "utf-8")) + + if not self._connected: + # waiting for connection + # wait until we actually get text back (not just telnet + # negotiation) + # start client tick + d = LoopingCall(self.step) + df = max(abs(TIMESTEP * 0.001), min(TIMESTEP/10, 0.5)) + # dither next attempt with random time + timestep = TIMESTEP + (-df + (random.random() * df)) + d.start(timestep, now=True).addErrback(self.error) + self.connection_attempt += 1 + + self._connected = True + NLOGIN_SCREEN += 1 + NCONNECTED -= 1 + self.report("<- server sent login screen", self.key) + + elif self._loggedin: + if not self._ready: + # logged in, ready to run + NLOGGED_IN += 1 + NLOGGING_IN -= 1 + self._ready = True + self.report("== logged in", self.key) + if NLOGGED_IN == NCLIENTS and not TIME_ALL_LOGIN: + # all are logged in! We can start collecting statistics + print(".. All clients connected and logged in!") + TIME_ALL_LOGIN = time.time() + reactor.callLater(30, self._print_statistics) + + elif TIME_ALL_LOGIN: + TOTAL_ACTIONS += 1 + + try: + data = strip_ansi(str(data, "utf-8").strip()) + if data.startswith("dummyrunner_echo_response:"): + # handle special lag-measuring command. This returns + # dummyrunner_echo_response:<starttime>,<midpointtime> + now = time.time() + _, data = data.split(":", 1) + start_time, mid_time = (float(part) for part in data.split(",", 1)) + lag_in = mid_time - start_time + lag_out = now - mid_time + total_lag = now - start_time # full round-about time + + TOTAL_LAG += total_lag + TOTAL_LAG_IN += lag_in + TOTAL_LAG_OUT += lag_out + TOTAL_LAG_MEASURES += 1 + except Exception: + pass
      [docs] def connectionLost(self, reason): """ @@ -325,7 +490,7 @@ """ if not self._logging_out: - print("client %s(%s) lost connection (%s)" % (self.key, self.cid, reason))
      + self.report("XX lost connection", self.key)
      [docs] def error(self, err): """ @@ -352,9 +517,9 @@ """ self._logging_out = True - cmd = self._logout(self) - print("client %s(%s) logout (%s actions)" % (self.key, self.cid, self.istep)) - self.sendLine(cmd)
      + cmd = self._logout(self)[0] + self.report(f"-> logout/disconnect ({self.istep} actions)", self.key) + self.sendLine(bytes(cmd, 'utf-8'))
      [docs] def step(self): """ @@ -363,7 +528,7 @@ all "intelligence" of the dummy client. """ - global NLOGGED_IN + global NLOGGING_IN, NLOGIN_SCREEN rand = random.random() @@ -371,11 +536,13 @@ # no commands ready. Load some. if not self._loggedin: - if rand < CHANCE_OF_LOGIN: + if rand < CHANCE_OF_LOGIN or NLOGGING_IN < 10: + # lower rate of logins, but not below 1 / s # get the login commands self._cmdlist = list(makeiter(self._login(self))) - NLOGGED_IN += 1 # this is for book-keeping - print("connecting client %s (%i/%i)..." % (self.key, NLOGGED_IN, NCLIENTS)) + NLOGGING_IN += 1 # this is for book-keeping + NLOGIN_SCREEN -= 1 + self.report("-> create/login", self.key) self._loggedin = True else: # no login yet, so cmdlist not yet set @@ -389,12 +556,26 @@ # at this point we always have a list of commands if rand < CHANCE_OF_ACTION: # send to the game - self.sendLine(str(self._cmdlist.pop(0))) - self.istep += 1
      + cmd = str(self._cmdlist.pop(0)) + + if cmd.startswith("dummyrunner_echo_response"): + # we need to set the timer element as close to + # the send as possible + cmd = cmd.format(timestamp=time.time()) + + self.sendLine(bytes(cmd, 'utf-8')) + self.action_started = time.time() + self.istep += 1 + + if NCLIENTS == 1: + print(f"dummy-client sent: {cmd}") -
      [docs]class DummyFactory(protocol.ClientFactory): +
      [docs]class DummyFactory(protocol.ReconnectingClientFactory): protocol = DummyClient + initialDelay = 1 + maxDelay = 1 + noisy = False
      [docs] def __init__(self, actions): "Setup the factory base (shared by all clients)" @@ -439,7 +620,7 @@ # setting up all clients (they are automatically started) factory = DummyFactory(actions) for i in range(NCLIENTS): - reactor.connectTCP("localhost", TELNET_PORT, factory) + reactor.connectTCP("127.0.0.1", TELNET_PORT, factory) # start reactor reactor.run()
      @@ -464,12 +645,23 @@ ) args = parser.parse_args() + nclients = int(args.nclients[0]) - print(INFO_STARTING.format(N=args.nclients[0])) + print(INFO_STARTING.format( + nclients=nclients, + port=TELNET_PORT, + idmapper_cache_size=IDMAPPER_CACHE_MAXSIZE, + timestep=TIMESTEP, + rate=1/TIMESTEP, + chance_of_login=CHANCE_OF_LOGIN * 100, + chance_of_action=CHANCE_OF_ACTION * 100, + avg_rate=(1 / TIMESTEP) * CHANCE_OF_ACTION, + avg_rate_total=(1 / TIMESTEP) * CHANCE_OF_ACTION * nclients + )) # run the dummyrunner - t0 = time.time() - start_all_dummy_clients(nclients=args.nclients[0]) + TIME_START = t0 = time.time() + start_all_dummy_clients(nclients=nclients) ttot = time.time() - t0 # output runtime diff --git a/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner_settings.html b/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner_settings.html index 29eb95e525..c2a378c91e 100644 --- a/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner_settings.html +++ b/docs/1.0-dev/_modules/evennia/server/profiling/dummyrunner_settings.html @@ -48,9 +48,9 @@ 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 +- TIMESTEP - time in seconds between each 'tick'. 1 is a good start. +- CHANCE_OF_ACTION - chance 0-1 of action happening. Default is 0.5. +- CHANCE_OF_LOGIN - chance 0-1 of login happening. 0.01 is a good number. - TELNET_PORT - port to use, defaults to settings.TELNET_PORT - ACTIONS - see below @@ -58,23 +58,25 @@ ```python (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 account. 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, +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. + +The PROFILE variable define pre-made ACTION tuples for convenience. + +Each function should return an iterable of one or more command-call +strings (like "look here"), so each can group multiple command 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. +reference to the dummy client currently performing the action. The client object has the following relevant properties and methods: @@ -97,11 +99,15 @@ ---- """ +import random +import string + # Dummy runner settings # Time between each dummyrunner "tick", in seconds. Each dummy # will be called with this frequency. -TIMESTEP = 2 +TIMESTEP = 1 +# TIMESTEP = 0.025 # 40/s # Chance of a dummy actually performing an action on a given tick. # This spreads out usage randomly, like it would be in reality. @@ -110,7 +116,7 @@ # Chance of a currently unlogged-in dummy performing its login # action every tick. This emulates not all accounts logging in # at exactly the same time. -CHANCE_OF_LOGIN = 1.0 +CHANCE_OF_LOGIN = 0.01 # Which telnet port to connect to. If set to None, uses the first # default telnet port of the running server. @@ -121,9 +127,10 @@ # some convenient templates -DUMMY_NAME = "Dummy-%s" -DUMMY_PWD = "password-%s" -START_ROOM = "testing_room_start_%s" +DUMMY_NAME = "Dummy_{gid}" +DUMMY_PWD = (''.join(random.choice(string.ascii_letters + string.digits) + for _ in range(20)) + "-{gid}") +START_ROOM = "testing_room_start_{gid}" ROOM_TEMPLATE = "testing_room_%s" EXIT_TEMPLATE = "exit_%s" OBJ_TEMPLATE = "testing_obj_%s" @@ -136,26 +143,27 @@ # login/logout -
      [docs]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 + cname = DUMMY_NAME.format(gid=client.gid) + cpwd = DUMMY_PWD.format(gid=client.gid) + room_name = START_ROOM.format(gid=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]) + # we assign the dummyrunner cmdsert to ourselves so # we can use special commands + add_cmdset = ( + "py from evennia.server.profiling.dummyrunner import DummyRunnerCmdSet;" + "self.cmdset.add(DummyRunnerCmdSet, persistent=False)" + ) + # create character, log in, then immediately dig a new location and + # teleport it (to keep the login room clean) cmds = ( - "create %s %s" % (cname, cpwd), - "connect %s %s" % (cname, cpwd), - "@dig %s" % START_ROOM % client.gid, - "@teleport %s" % START_ROOM % client.gid, - "@dig %s = %s, %s" % (roomname, exitname1, exitname2), + f"create {cname} {cpwd}", + f"connect {cname} {cpwd}", + f"dig {room_name}", + f"teleport {room_name}", + add_cmdset, ) return cmds
      @@ -164,14 +172,16 @@ "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)) + cmds = ( + f"create {cname} {cpwd}", + f"connect {cname} {cpwd}" + ) return cmds
      [docs]def c_logout(client): "logouts of the game" - return "@quit"
      + return ("quit",)
      # random commands @@ -183,7 +193,7 @@ if not cmds: cmds = ["look %s" % exi for exi in client.exits] if not cmds: - cmds = "look" + cmds = ("look",) return cmds @@ -193,7 +203,7 @@ if not cmds: cmds = ["examine %s" % exi for exi in client.exits] if not cmds: - cmds = "examine me" + cmds = ("examine me",) return cmds @@ -205,7 +215,7 @@
      [docs]def c_help(client): "reads help files" - cmds = ("help", "help @teleport", "help look", "help @tunnel", "help @dig") + cmds = ("help", "dummyrunner_echo_response",) return cmds
      @@ -215,7 +225,7 @@ exitname1 = EXIT_TEMPLATE % client.counter() exitname2 = EXIT_TEMPLATE % client.counter() client.exits.extend([exitname1, exitname2]) - return "@dig/tel %s = %s, %s" % (roomname, exitname1, exitname2) + return ("@dig/tel %s = %s, %s" % (roomname, exitname1, exitname2),)
      [docs]def c_creates_obj(client): @@ -242,9 +252,7 @@
      [docs]def c_socialize(client): "socializechats on channel" cmds = ( - "ooc Hello!", - "ooc Testing ...", - "ooc Testing ... times 2", + "pub Hello!", "say Yo!", "emote stands looking around.", ) @@ -254,84 +262,120 @@
      [docs]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
      + return ("look",) if not cmds else cmds
      [docs]def c_moves_n(client): "move through north exit if available" - return "north"
      + return ("north",)
      [docs]def c_moves_s(client): "move through south exit if available" - return "south"
      + 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. +
      [docs]def c_measure_lag(client): + """ + Special dummyrunner command, injected in c_login. It measures + response time. Including this in the ACTION tuple will give more + dummyrunner output about just how fast commands are being processed. + + The dummyrunner will treat this special and inject the + {timestamp} just before sending. + + """ + return ("dummyrunner_echo_response {timestamp}",)
      + + +# Action profile (required) + +# Some pre-made profiles to test. To make your own, just assign a tuple to ACTIONS. # +# idler - does nothing after logging in +# looker - just looks around +# normal_player - moves around, reads help, looks around (digs rarely) (spammy) +# normal_builder - digs now and then, examines, creates objects, moves +# heavy_builder - digs and creates a lot, moves and examines +# socializing_builder - builds a lot, creates help entries, moves, chat (spammy) +# only_digger - extreme builder that only digs room after room + +PROFILE = "looker" -# "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 account" 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)) -# "inactive account" definition -# ACTIONS = (c_login_nodig, -# c_logout, -# (1.0, c_idles)) -# "normal account" definition -ACTIONS = (c_login, c_logout, (0.01, c_digs), (0.39, c_looks), (0.2, c_help), (0.4, c_moves)) -# walking tester. This requires a pre-made -# "loop" of multiple rooms that ties back -# to limbo (using @tunnel and @open) -# 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)) +if PROFILE == 'idler': + ACTIONS = ( + c_login, + c_logout, + (0.9, c_idles), + (0.1, c_measure_lag), + ) +elif PROFILE == 'looker': + ACTIONS = ( + c_login, + c_logout, + (0.8, c_looks), + (0.2, c_measure_lag) + ) +elif PROFILE == 'normal_player': + ACTIONS = ( + c_login, + c_logout, + (0.01, c_digs), + (0.29, c_looks), + (0.2, c_help), + (0.3, c_moves), + (0.05, c_socialize), + (0.1, c_measure_lag) + ) +elif PROFILE == 'normal_builder': + 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.2, c_moves) + (0.1, c_measure_lag) + ) +elif PROFILE == 'heavy_builder': + ACTIONS = ( + c_login, + c_logout, + (0.1, c_looks), + (0.1, c_examines), + (0.2, c_help), + (0.1, c_digs), + (0.1, c_creates_obj), + (0.2, c_moves), + (0.1, c_measure_lag) + ) +elif PROFILE == 'socializing_builder': + ACTIONS = ( + c_login, + c_logout, + (0.1, c_socialize), + (0.1, c_looks), + (0.1, c_help), + (0.1, c_creates_obj), + (0.2, c_digs), + (0.3, c_moves), + (0.1, c_measure_lag) + ) +elif PROFILE == 'only_digger': + ACTIONS = ( + c_login, + c_logout, + (0.9, c_digs), + (0.1, c_measure_lag) + ) + +else: + print("No dummyrunner ACTION profile defined.") + import sys + sys.exit()
      diff --git a/docs/1.0-dev/_modules/evennia/server/throttle.html b/docs/1.0-dev/_modules/evennia/server/throttle.html index d479b22b54..78dcb1c38f 100644 --- a/docs/1.0-dev/_modules/evennia/server/throttle.html +++ b/docs/1.0-dev/_modules/evennia/server/throttle.html @@ -68,7 +68,8 @@ Keyword Args: name (str): Name of this throttle. - limit (int): Max number of failures before imposing limiter + limit (int): Max number of failures before imposing limiter. If `None`, + the throttle is disabled. timeout (int): number of timeout seconds after max number of tries has been reached. cache_size (int): Max number of attempts to record per IP within a @@ -239,6 +240,10 @@ False otherwise. """ + if self.limit is None: + # throttle is disabled + return False + now = time.time() ip = str(ip) diff --git a/docs/1.0-dev/_sources/Coding/Profiling.md.txt b/docs/1.0-dev/_sources/Coding/Profiling.md.txt index d1800e1831..f7e70f1da1 100644 --- a/docs/1.0-dev/_sources/Coding/Profiling.md.txt +++ b/docs/1.0-dev/_sources/Coding/Profiling.md.txt @@ -4,24 +4,27 @@ ## Introduction -Sometimes it can be useful to try to determine just how efficient a particular piece of code is, or -to figure out if one could speed up things more than they are. There are many ways to test the -performance of Python and the running server. +Sometimes it can be useful to try to determine just how efficient a particular +piece of code is, or to figure out if one could speed up things more than they +are. There are many ways to test the performance of Python and the running +server. -Before digging into this section, remember Donald Knuth's [words of -wisdom](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize): +Before digging into this section, remember Donald Knuth's +[words of wisdom](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize): > *[...]about 97% of the time: Premature optimization is the root of all evil*. -That is, don't start to try to optimize your code until you have actually identified a need to do -so. This means your code must actually be working before you start to consider optimization. -Optimization will also often make your code more complex and harder to read. Consider readability -and maintainability and you may find that a small gain in speed is just not worth it. +That is, don't start to try to optimize your code until you have actually +identified a need to do so. This means your code must actually be working before +you start to consider optimization. Optimization will also often make your code +more complex and harder to read. Consider readability and maintainability and +you may find that a small gain in speed is just not worth it. ## Simple timer tests -Python's `timeit` module is very good for testing small things. For example, in order to test if it -is faster to use a `for` loop or a list comprehension you could use the following code: +Python's `timeit` module is very good for testing small things. For example, in +order to test if it is faster to use a `for` loop or a list comprehension you +could use the following code: ```python import timeit @@ -33,93 +36,218 @@ is faster to use a `for` loop or a list comprehension you could use the followin <<< 5.358283996582031 ``` -The `setup` keyword is used to set up things that should not be included in the time measurement, -like `a = []` in the first call. +The `setup` keyword is used to set up things that should not be included in the +time measurement, like `a = []` in the first call. -By default the `timeit` function will re-run the given test 1000000 times and returns the *total -time* to do so (so *not* the average per test). A hint is to not use this default for testing -something that includes database writes - for that you may want to use a lower number of repeats -(say 100 or 1000) using the `number=100` keyword. +By default the `timeit` function will re-run the given test 1000000 times and +returns the *total time* to do so (so *not* the average per test). A hint is to +not use this default for testing something that includes database writes - for +that you may want to use a lower number of repeats (say 100 or 1000) using the +`number=100` keyword. ## Using cProfile -Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done -with `pypy` at this point). Due to the way Evennia's processes are handled, there is no point in -using the normal way to start the profiler (`python -m cProfile evennia.py`). Instead you start the -profiler through the launcher: +Python comes with its own profiler, named cProfile (this is for cPython, no +tests have been done with `pypy` at this point). Due to the way Evennia's +processes are handled, there is no point in using the normal way to start the +profiler (`python -m cProfile evennia.py`). Instead you start the profiler +through the launcher: evennia --profiler start -This will start Evennia with the Server component running (in daemon mode) under cProfile. You could -instead try `--profile` with the `portal` argument to profile the Portal (you would then need to +This will start Evennia with the Server component running (in daemon mode) under +cProfile. You could instead try `--profile` with the `portal` argument to +profile the Portal (you would then need to [start the Server separately](../Setup/Start-Stop-Reload)). -Please note that while the profiler is running, your process will use a lot more memory than usual. -Memory usage is even likely to climb over time. So don't leave it running perpetually but monitor it -carefully (for example using the `top` command on Linux or the Task Manager's memory display on -Windows). +Please note that while the profiler is running, your process will use a lot more +memory than usual. Memory usage is even likely to climb over time. So don't +leave it running perpetually but monitor it carefully (for example using the +`top` command on Linux or the Task Manager's memory display on Windows). -Once you have run the server for a while, you need to stop it so the profiler can give its report. -Do *not* kill the program from your task manager or by sending it a kill signal - this will most -likely also mess with the profiler. Instead either use `evennia.py stop` or (which may be even -better), use `@shutdown` from inside the game. +Once you have run the server for a while, you need to stop it so the profiler +can give its report. Do *not* kill the program from your task manager or by +sending it a kill signal - this will most likely also mess with the profiler. +Instead either use `evennia.py stop` or (which may be even better), use +`@shutdown` from inside the game. -Once the server has fully shut down (this may be a lot slower than usual) you will find that -profiler has created a new file `mygame/server/logs/server.prof`. +Once the server has fully shut down (this may be a lot slower than usual) you +will find that profiler has created a new file `mygame/server/logs/server.prof`. -## Analyzing the profile +### Analyzing the profile -The `server.prof` file is a binary file. There are many ways to analyze and display its contents, -all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works). +The `server.prof` file is a binary file. There are many ways to analyze and +display its contents, all of which has only been tested in Linux (If you are a +Windows/Mac user, let us know what works). -We recommend the -[Runsnake](http://www.vrplumber.com/programming/runsnakerun/) visualizer to see the processor usage -of different processes in a graphical form. For more detailed listing of usage time, you can use -[KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html). To make KCachegrind work with -Python profiles you also need the wrapper script -[pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/). You can get pyprof2calltree via -`pip` whereas KCacheGrind is something you need to get via your package manager or their homepage. +You can look at the contents of the profile file with Python's in-built `pstats` +module in the evennia shell (it's recommended you install `ipython` with `pip +install ipython` in your virtualenv first, for prettier output): -How to analyze and interpret profiling data is not a trivial issue and depends on what you are -profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing -list if you need help and be ready to be able to supply your `server.prof` file for comparison, -along with the exact conditions under which it was obtained. + evennia shell + +Then in the shell + +```python +import pstats +from pstats import SortKey + +p = pstats.Stats('server/log/server.prof') +p.strip_dirs().sort_stats(-1).print_stats() + +``` + +See the +[Python profiling documentation](https://docs.python.org/3/library/profile.html#instant-user-s-manual) +for more information. + +You can also visualize the data in various ways. +- [Runsnake](https://pypi.org/project/RunSnakeRun/) visualizes the profile to + give a good overview. Install with `pip install runsnakerun`. Note that this + may require a C compiler and be quite slow to install. +- For more detailed listing of usage time, you can use + [KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html). To make + KCachegrind work with Python profiles you also need the wrapper script + [pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/). You can get + `pyprof2calltree` via `pip` whereas KCacheGrind is something you need to get + via your package manager or their homepage. + +How to analyze and interpret profiling data is not a trivial issue and depends +on what you are profiling for. Evennia being an asynchronous server can also +confuse profiling. Ask on the mailing list if you need help and be ready to be +able to supply your `server.prof` file for comparison, along with the exact +conditions under which it was obtained. ## The Dummyrunner -It is difficult to test "actual" game performance without having players in your game. For this -reason Evennia comes with the *Dummyrunner* system. The Dummyrunner is a stress-testing system: a -separate program that logs into your game with simulated players (aka "bots" or "dummies"). Once -connected these dummies will semi-randomly perform various tasks from a list of possible actions. -Use `Ctrl-C` to stop the Dummyrunner. +It is difficult to test "actual" game performance without having players in your +game. For this reason Evennia comes with the *Dummyrunner* system. The +Dummyrunner is a stress-testing system: a separate program that logs into your +game with simulated players (aka "bots" or "dummies"). Once connected, these +dummies will semi-randomly perform various tasks from a list of possible +actions. Use `Ctrl-C` to stop the Dummyrunner. -> Warning: You should not run the Dummyrunner on a production database. It will spawn many objects -and also needs to run with general permissions. +```warning:: -To launch the Dummyrunner, first start your server normally (with or without profiling, as above). -Then start a new terminal/console window and active your virtualenv there too. In the new terminal, -try to connect 10 dummy players: + You should not run the Dummyrunner on a production database. It + will spawn many objects and also needs to run with general permissions. - evennia --dummyrunner 10 - -The first time you do this you will most likely get a warning from Dummyrunner. It will tell you to -copy an import string to the end of your settings file. Quit the Dummyrunner (`Ctrl-C`) and follow -the instructions. Restart Evennia and try `evennia --dummyrunner 10` again. Make sure to remove that -extra settings line when running a public server. - -The actions perform by the dummies is controlled by a settings file. The default Dummyrunner -settings file is `evennia/server/server/profiling/dummyrunner_settings.py` but you shouldn't modify -this directly. Rather create/copy the default file to `mygame/server/conf/` and modify it there. To -make sure to use your file over the default, add the following line to your settings file: - -```python -DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py" +This is the recommended process for using the dummy runner: ``` -> Hint: Don't start with too many dummies. The Dummyrunner defaults to taxing the server much more -intensely than an equal number of human players. A good dummy number to start with is 10-100. +1. Stop your server completely with `evennia stop`. +1. At _the end_ of your `mygame/server/conf.settings.py` file, add the line -Once you have the dummyrunner running, stop it with `Ctrl-C`. + from evennia.server.profiling.settings_mixin import * + + This will override your settings and disable Evennia's rate limiters and + DoS-protections, which would otherwise block mass-connecting clients from + one IP. Notably, it will also change to a different (faster) password hasher. +1. (recommended): Build a new database. If you use default Sqlite3 and want to + keep your existing database, just rename `mygame/server/evennia.db3` to + `mygame/server/evennia.db3_backup` and run `evennia migrate` and `evennia + start` to create a new superuser as usual. +1. (recommended) Log into the game as your superuser. This is just so you + can manually check response. If you kept an old database, you will _not_ + be able to connect with an _existing_ user since the password hasher changed! +1. Start the dummyrunner with 10 dummy users from the terminal with + + evennia --dummyrunner 10 + + Use `Ctrl-C` (or `Cmd-C`) to stop it. + +If you want to see what the dummies are actually doing you can run with a single +dummy: + + evennia --dummyrunner 1 + +The inputs/outputs from the dummy will then be printed. By default the runner +uses the 'looker' profile, which just logs in and sends the 'look' command +over and over. To change the settings, copy the file +`evennia/server/profiling/dummyrunner_settings.py` to your `mygame/server/conf/` +directory, then add this line to your settings file to use it in the new +location: + + DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py" + +The dummyrunner settings file is a python code module in its own right - it +defines the actions available to the dummies. These are just tuples of command +strings (like "look here") for the dummy to send to the server along with a +probability of them happening. The dummyrunner looks for a global variable +`ACTIONS`, a list of tuples, where the first two elements define the +commands for logging in/out of the server. + +Below is a simplified minimal setup (the default settings file adds a lot more +functionality and info): + +```python +# minimal dummyrunner setup file + +# Time between each dummyrunner "tick", in seconds. Each dummy will be called +# with this frequency. +TIMESTEP = 1 + +# 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.5 + +# Chance of a currently unlogged-in dummy performing its login action every +# tick. This emulates not all accounts logging in at exactly the same time. +CHANCE_OF_LOGIN = 0.01 + +# Which telnet port to connect to. If set to None, uses the first default +# telnet port of the running server. +TELNET_PORT = None + +# actions + +def c_login(client): + name = f"Character-{client.gid}" + pwd = f"23fwsf23sdfw23wef23" + return ( + f"create {name} {pwd}" + f"connect {name} {pwd}" + ) + +def c_logout(client): + return ("quit", ) + +def c_look(client): + return ("look here", "look me") + +# this is read by dummyrunner. +ACTIONS = ( + c_login, + c_logout, + (1.0, c_look) # (probability, command-generator) +) + +``` + +At the bottom of the default file are a few default profiles you can test out +by just setting the `PROFILE` variable to one of the options. + +### Dummyrunner hints + +- Don't start with too many dummies. The Dummyrunner taxes the server much more + than 'real' users tend to do. Start with 10-100 to begin with. +- Stress-testing can be fun, but also consider what a 'realistic' number of + users would be for your game. +- Note in the dummyrunner output how many commands/s are being sent to the + server by all dummies. This is usually a lot higher than what you'd + realistically expect to see from the same number of users. +- The default settings sets up a 'lag' measure to measaure the round-about + message time. It updates with an average every 30 seconds. It can be worth to + have this running for a small number of dummies in one terminal before adding + more by starting another dummyrunner in another terminal - the first one will + act as a measure of how lag changes with different loads. Also verify the + lag-times by entering commands manually in-game. +- Check the CPU usage of your server using `top/htop` (linux). In-game, use the + `server` command. +- You can run the server with `--profiler start` to test it with dummies. Note + that the profiler will itself affect server performance, especially memory + consumption. +- Generally, the dummyrunner system makes for a decent test of general + performance; but it is of course hard to actually mimic human user behavior. + For this, actual real-game testing is required. -Generally, the dummyrunner system makes for a decent test of general performance; but it is of -course hard to actually mimic human user behavior. For this, actual real-game testing is required. \ No newline at end of file diff --git a/docs/1.0-dev/_sources/Components/Channels.md.txt b/docs/1.0-dev/_sources/Components/Channels.md.txt index 5998880201..f7463b7bcf 100644 --- a/docs/1.0-dev/_sources/Components/Channels.md.txt +++ b/docs/1.0-dev/_sources/Components/Channels.md.txt @@ -85,7 +85,7 @@ You can now just do And even remove the default one if they don't want to use it channel/unalias public - public Hello + public Hello (gives a command-not-found error now) But you can also use your alias with the `channel` command: diff --git a/docs/1.0-dev/_sources/toc.md.txt b/docs/1.0-dev/_sources/toc.md.txt index 00050cc0c7..21ddd67512 100644 --- a/docs/1.0-dev/_sources/toc.md.txt +++ b/docs/1.0-dev/_sources/toc.md.txt @@ -1,5 +1,5 @@ # Toc -- [API root](api/evennia-api.rst) + - [Coding/Coding Introduction](Coding/Coding-Introduction) - [Coding/Coding Overview](Coding/Coding-Overview) - [Coding/Continuous Integration](Coding/Continuous-Integration) diff --git a/docs/1.0-dev/api/evennia.commands.default.account.html b/docs/1.0-dev/api/evennia.commands.default.account.html index 90cbc54b66..e5e39f21f7 100644 --- a/docs/1.0-dev/api/evennia.commands.default.account.html +++ b/docs/1.0-dev/api/evennia.commands.default.account.html @@ -71,7 +71,7 @@ method. Otherwise all text will be returned to all connected sessions.

      -aliases = ['l', 'ls']ΒΆ
      +aliases = ['ls', 'l']ΒΆ
      @@ -102,7 +102,7 @@ method. Otherwise all text will be returned to all connected sessions.

      -search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look while out-of-character\n\n Usage:\n look\n\n Look in the ooc state.\n '}ΒΆ
      +search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look while out-of-character\n\n Usage:\n look\n\n Look in the ooc state.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.commands.default.admin.html b/docs/1.0-dev/api/evennia.commands.default.admin.html index 880c887c5c..789a1fb57e 100644 --- a/docs/1.0-dev/api/evennia.commands.default.admin.html +++ b/docs/1.0-dev/api/evennia.commands.default.admin.html @@ -255,7 +255,7 @@ to accounts respectively.

      -aliases = ['pemit', 'remit']ΒΆ
      +aliases = ['remit', 'pemit']ΒΆ
      @@ -286,7 +286,7 @@ to accounts respectively.

      -search_index_entry = {'aliases': 'pemit remit', 'category': 'admin', 'key': 'emit', 'tags': '', 'text': '\n admin command for emitting message to multiple objects\n\n Usage:\n emit[/switches] [<obj>, <obj>, ... =] <message>\n remit [<obj>, <obj>, ... =] <message>\n pemit [<obj>, <obj>, ... =] <message>\n\n Switches:\n room - limit emits to rooms only (default)\n accounts - limit emits to accounts only\n contents - send to the contents of matched objects too\n\n Emits a message to the selected objects or to\n your immediate surroundings. If the object is a room,\n send to its contents. remit and pemit are just\n limited forms of emit, for sending to rooms and\n to accounts respectively.\n '}ΒΆ
      +search_index_entry = {'aliases': 'remit pemit', 'category': 'admin', 'key': 'emit', 'tags': '', 'text': '\n admin command for emitting message to multiple objects\n\n Usage:\n emit[/switches] [<obj>, <obj>, ... =] <message>\n remit [<obj>, <obj>, ... =] <message>\n pemit [<obj>, <obj>, ... =] <message>\n\n Switches:\n room - limit emits to rooms only (default)\n accounts - limit emits to accounts only\n contents - send to the contents of matched objects too\n\n Emits a message to the selected objects or to\n your immediate surroundings. If the object is a room,\n send to its contents. remit and pemit are just\n limited forms of emit, for sending to rooms and\n to accounts respectively.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.commands.default.batchprocess.html b/docs/1.0-dev/api/evennia.commands.default.batchprocess.html index 75251220fa..f167793b40 100644 --- a/docs/1.0-dev/api/evennia.commands.default.batchprocess.html +++ b/docs/1.0-dev/api/evennia.commands.default.batchprocess.html @@ -76,7 +76,7 @@ skipping, reloading etc.

      -aliases = ['batchcmd', 'batchcommand']ΒΆ
      +aliases = ['batchcommand', 'batchcmd']ΒΆ
      @@ -107,7 +107,7 @@ skipping, reloading etc.

      -search_index_entry = {'aliases': 'batchcmd batchcommand', 'category': 'building', 'key': 'batchcommands', 'tags': '', 'text': '\n build from batch-command file\n\n Usage:\n batchcommands[/interactive] <python.path.to.file>\n\n Switch:\n interactive - this mode will offer more control when\n executing the batch file, like stepping,\n skipping, reloading etc.\n\n Runs batches of commands from a batch-cmd text file (*.ev).\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'batchcommand batchcmd', 'category': 'building', 'key': 'batchcommands', 'tags': '', 'text': '\n build from batch-command file\n\n Usage:\n batchcommands[/interactive] <python.path.to.file>\n\n Switch:\n interactive - this mode will offer more control when\n executing the batch file, like stepping,\n skipping, reloading etc.\n\n Runs batches of commands from a batch-cmd text file (*.ev).\n\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.commands.default.building.html b/docs/1.0-dev/api/evennia.commands.default.building.html index 43e975d9ac..116307d218 100644 --- a/docs/1.0-dev/api/evennia.commands.default.building.html +++ b/docs/1.0-dev/api/evennia.commands.default.building.html @@ -530,7 +530,7 @@ You can specify the /force switch to bypass this confirmation.

      -aliases = ['delete', 'del']ΒΆ
      +aliases = ['del', 'delete']ΒΆ
      @@ -571,7 +571,7 @@ You can specify the /force switch to bypass this confirmation.

      -search_index_entry = {'aliases': 'delete del', 'category': 'building', 'key': 'destroy', 'tags': '', 'text': '\n permanently delete objects\n\n Usage:\n destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]\n\n Switches:\n override - The destroy command will usually avoid accidentally\n destroying account objects. This switch overrides this safety.\n force - destroy without confirmation.\n Examples:\n destroy house, roof, door, 44-78\n destroy 5-10, flower, 45\n destroy/force north\n\n Destroys one or many objects. If dbrefs are used, a range to delete can be\n given, e.g. 4-10. Also the end points will be deleted. This command\n displays a confirmation before destroying, to make sure of your choice.\n You can specify the /force switch to bypass this confirmation.\n '}ΒΆ
      +search_index_entry = {'aliases': 'del delete', 'category': 'building', 'key': 'destroy', 'tags': '', 'text': '\n permanently delete objects\n\n Usage:\n destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]\n\n Switches:\n override - The destroy command will usually avoid accidentally\n destroying account objects. This switch overrides this safety.\n force - destroy without confirmation.\n Examples:\n destroy house, roof, door, 44-78\n destroy 5-10, flower, 45\n destroy/force north\n\n Destroys one or many objects. If dbrefs are used, a range to delete can be\n given, e.g. 4-10. Also the end points will be deleted. This command\n displays a confirmation before destroying, to make sure of your choice.\n You can specify the /force switch to bypass this confirmation.\n '}ΒΆ
      @@ -1269,7 +1269,7 @@ server settings.

      -aliases = ['update', 'type', 'swap', 'parent']ΒΆ
      +aliases = ['update', 'parent', 'swap', 'type']ΒΆ
      @@ -1300,7 +1300,7 @@ server settings.

      -search_index_entry = {'aliases': 'update type swap parent', 'category': 'building', 'key': 'typeclass', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object.\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}ΒΆ
      +search_index_entry = {'aliases': 'update parent swap type', 'category': 'building', 'key': 'typeclass', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object.\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}ΒΆ
      @@ -1584,7 +1584,7 @@ one is given.

      -aliases = ['search', 'locate']ΒΆ
      +aliases = ['locate', 'search']ΒΆ
      @@ -1615,7 +1615,7 @@ one is given.

      -search_index_entry = {'aliases': 'search locate', 'category': 'building', 'key': 'find', 'tags': '', 'text': '\n search the database for objects\n\n Usage:\n find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]]\n locate - this is a shorthand for using the /loc switch.\n\n Switches:\n room - only look for rooms (location=None)\n exit - only look for exits (destination!=None)\n char - only look for characters (BASE_CHARACTER_TYPECLASS)\n exact - only exact matches are returned.\n loc - display object location if exists and match has one result\n startswith - search for names starting with the string, rather than containing\n\n Searches the database for an object of a particular name or exact #dbref.\n Use *accountname to search for an account. The switches allows for\n limiting object matches to certain game entities. Dbrefmin and dbrefmax\n limits matches to within the given dbrefs range, or above/below if only\n one is given.\n '}ΒΆ
      +search_index_entry = {'aliases': 'locate search', 'category': 'building', 'key': 'find', 'tags': '', 'text': '\n search the database for objects\n\n Usage:\n find[/switches] <name or dbref or *account> [= dbrefmin[-dbrefmax]]\n locate - this is a shorthand for using the /loc switch.\n\n Switches:\n room - only look for rooms (location=None)\n exit - only look for exits (destination!=None)\n char - only look for characters (BASE_CHARACTER_TYPECLASS)\n exact - only exact matches are returned.\n loc - display object location if exists and match has one result\n startswith - search for names starting with the string, rather than containing\n\n Searches the database for an object of a particular name or exact #dbref.\n Use *accountname to search for an account. The switches allows for\n limiting object matches to certain game entities. Dbrefmin and dbrefmax\n limits matches to within the given dbrefs range, or above/below if only\n one is given.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.commands.default.comms.html b/docs/1.0-dev/api/evennia.commands.default.comms.html index 4efb57379b..8330e6f4de 100644 --- a/docs/1.0-dev/api/evennia.commands.default.comms.html +++ b/docs/1.0-dev/api/evennia.commands.default.comms.html @@ -194,7 +194,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.

      -aliases = ['channels', 'chan']ΒΆ
      +aliases = ['chan', 'channels']ΒΆ
      @@ -720,7 +720,7 @@ don’t actually sub to yet.

      -search_index_entry = {'aliases': 'channels chan', 'category': 'comms', 'key': 'channel', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}ΒΆ
      +search_index_entry = {'aliases': 'chan channels', 'category': 'comms', 'key': 'channel', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}ΒΆ
      @@ -802,7 +802,7 @@ for that channel.

      -aliases = ['delchanalias', 'delaliaschan']ΒΆ
      +aliases = ['delaliaschan', 'delchanalias']ΒΆ
      @@ -833,7 +833,7 @@ for that channel.

      -search_index_entry = {'aliases': 'delchanalias delaliaschan', 'category': 'comms', 'key': 'delcom', 'tags': '', 'text': "\n remove a channel alias and/or unsubscribe from channel\n\n Usage:\n delcom <alias or channel>\n delcom/all <channel>\n\n If the full channel name is given, unsubscribe from the\n channel. If an alias is given, remove the alias but don't\n unsubscribe. If the 'all' switch is used, remove all aliases\n for that channel.\n "}ΒΆ
      +search_index_entry = {'aliases': 'delaliaschan delchanalias', 'category': 'comms', 'key': 'delcom', 'tags': '', 'text': "\n remove a channel alias and/or unsubscribe from channel\n\n Usage:\n delcom <alias or channel>\n delcom/all <channel>\n\n If the full channel name is given, unsubscribe from the\n channel. If an alias is given, remove the alias but don't\n unsubscribe. If the 'all' switch is used, remove all aliases\n for that channel.\n "}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.commands.default.general.html b/docs/1.0-dev/api/evennia.commands.default.general.html index f05b74c9a2..d567d47c6a 100644 --- a/docs/1.0-dev/api/evennia.commands.default.general.html +++ b/docs/1.0-dev/api/evennia.commands.default.general.html @@ -113,7 +113,7 @@ look *<account&g
      -aliases = ['l', 'ls']ΒΆ
      +aliases = ['ls', 'l']ΒΆ
      @@ -144,7 +144,7 @@ look *<account&g
      -search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look at location or object\n\n Usage:\n look\n look <obj>\n look *<account>\n\n Observes your location or objects in your vicinity.\n '}ΒΆ
      +search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look at location or object\n\n Usage:\n look\n look <obj>\n look *<account>\n\n Observes your location or objects in your vicinity.\n '}ΒΆ
      @@ -536,7 +536,7 @@ placing it in their inventory.

      -aliases = ['"', "'"]ΒΆ
      +aliases = ["'", '"']ΒΆ
      @@ -562,7 +562,7 @@ placing it in their inventory.

      -search_index_entry = {'aliases': '" \'', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n speak as your character\n\n Usage:\n say <message>\n\n Talk to those in your current location.\n '}ΒΆ
      +search_index_entry = {'aliases': '\' "', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n speak as your character\n\n Usage:\n say <message>\n\n Talk to those in your current location.\n '}ΒΆ
      @@ -642,7 +642,7 @@ automatically begin with your name.

      -aliases = ['emote', ':']ΒΆ
      +aliases = [':', 'emote']ΒΆ
      @@ -678,7 +678,7 @@ space.

      -search_index_entry = {'aliases': 'emote :', 'category': 'general', 'key': 'pose', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}ΒΆ
      +search_index_entry = {'aliases': ': emote', 'category': 'general', 'key': 'pose', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}ΒΆ
      @@ -701,7 +701,7 @@ which permission groups you are a member of.

      -aliases = ['groups', 'hierarchy']ΒΆ
      +aliases = ['hierarchy', 'groups']ΒΆ
      @@ -732,7 +732,7 @@ which permission groups you are a member of.

      -search_index_entry = {'aliases': 'groups hierarchy', 'category': 'general', 'key': 'access', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}ΒΆ
      +search_index_entry = {'aliases': 'hierarchy groups', 'category': 'general', 'key': 'access', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.commands.default.system.html b/docs/1.0-dev/api/evennia.commands.default.system.html index 10c541df63..6983aece5f 100644 --- a/docs/1.0-dev/api/evennia.commands.default.system.html +++ b/docs/1.0-dev/api/evennia.commands.default.system.html @@ -386,7 +386,7 @@ given, <nr> defaults to 10.

      -aliases = ['db', 'listobjs', 'listobjects', 'stats']ΒΆ
      +aliases = ['db', 'listobjects', 'listobjs', 'stats']ΒΆ
      @@ -412,7 +412,7 @@ given, <nr> defaults to 10.

      -search_index_entry = {'aliases': 'db listobjs listobjects stats', 'category': 'system', 'key': 'objects', 'tags': '', 'text': '\n statistics on objects in the database\n\n Usage:\n objects [<nr>]\n\n Gives statictics on objects in database as well as\n a list of <nr> latest objects in database. If not\n given, <nr> defaults to 10.\n '}ΒΆ
      +search_index_entry = {'aliases': 'db listobjects listobjs stats', 'category': 'system', 'key': 'objects', 'tags': '', 'text': '\n statistics on objects in the database\n\n Usage:\n objects [<nr>]\n\n Gives statictics on objects in database as well as\n a list of <nr> latest objects in database. If not\n given, <nr> defaults to 10.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.commands.default.unloggedin.html b/docs/1.0-dev/api/evennia.commands.default.unloggedin.html index e1cdfa2aa0..d99d661292 100644 --- a/docs/1.0-dev/api/evennia.commands.default.unloggedin.html +++ b/docs/1.0-dev/api/evennia.commands.default.unloggedin.html @@ -60,7 +60,7 @@ connect β€œaccount name” β€œpass word”

      -aliases = ['conn', 'co', 'con']ΒΆ
      +aliases = ['conn', 'con', 'co']ΒΆ
      @@ -95,7 +95,7 @@ there is no object yet before the account has logged in)

      -search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}ΒΆ
      +search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}ΒΆ
      @@ -119,7 +119,7 @@ create β€œaccount name” β€œpass word”

      -aliases = ['cre', 'cr']ΒΆ
      +aliases = ['cr', 'cre']ΒΆ
      @@ -150,7 +150,7 @@ create β€œaccount name” β€œpass word”

      -search_index_entry = {'aliases': 'cre cr', 'category': 'general', 'key': 'create', 'tags': '', 'text': '\n create a new account account\n\n Usage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\n This creates a new account account.\n\n If you have spaces in your name, enclose it in double quotes.\n '}ΒΆ
      +search_index_entry = {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'tags': '', 'text': '\n create a new account account\n\n Usage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\n This creates a new account account.\n\n If you have spaces in your name, enclose it in double quotes.\n '}ΒΆ
      @@ -224,7 +224,7 @@ All it does is display the connect screen.

      -aliases = ['l', 'look']ΒΆ
      +aliases = ['look', 'l']ΒΆ
      @@ -250,7 +250,7 @@ All it does is display the connect screen.

      -search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}ΒΆ
      +search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.chargen.html b/docs/1.0-dev/api/evennia.contrib.chargen.html index 77d7eba5e8..6512241a3f 100644 --- a/docs/1.0-dev/api/evennia.contrib.chargen.html +++ b/docs/1.0-dev/api/evennia.contrib.chargen.html @@ -78,7 +78,7 @@ at them with this command.

      -aliases = ['l', 'ls']ΒΆ
      +aliases = ['ls', 'l']ΒΆ
      @@ -110,7 +110,7 @@ that is checked by the @ic command directly.

      -search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n ooc look\n\n Usage:\n look\n look <character>\n\n This is an OOC version of the look command. Since an Account doesn\'t\n have an in-game existence, there is no concept of location or\n "self".\n\n If any characters are available for you to control, you may look\n at them with this command.\n '}ΒΆ
      +search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n ooc look\n\n Usage:\n look\n look <character>\n\n This is an OOC version of the look command. Since an Account doesn\'t\n have an in-game existence, there is no concept of location or\n "self".\n\n If any characters are available for you to control, you may look\n at them with this command.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.email_login.html b/docs/1.0-dev/api/evennia.contrib.email_login.html index 73c570bda1..fc5b4bd822 100644 --- a/docs/1.0-dev/api/evennia.contrib.email_login.html +++ b/docs/1.0-dev/api/evennia.contrib.email_login.html @@ -75,7 +75,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE.

      -aliases = ['conn', 'co', 'con']ΒΆ
      +aliases = ['conn', 'con', 'co']ΒΆ
      @@ -105,7 +105,7 @@ there is no object yet before the account has logged in)

      -search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}ΒΆ
      +search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}ΒΆ
      @@ -127,7 +127,7 @@ there is no object yet before the account has logged in)

      -aliases = ['cre', 'cr']ΒΆ
      +aliases = ['cr', 'cre']ΒΆ
      @@ -163,7 +163,7 @@ name enclosed in quotes:

      -search_index_entry = {'aliases': 'cre cr', 'category': 'general', 'key': 'create', 'tags': '', 'text': '\n Create a new account.\n\n Usage (at login screen):\n create "accountname" <email> <password>\n\n This creates a new account account.\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'tags': '', 'text': '\n Create a new account.\n\n Usage (at login screen):\n create "accountname" <email> <password>\n\n This creates a new account account.\n\n '}ΒΆ
      @@ -227,7 +227,7 @@ All it does is display the connect screen.

      -aliases = ['l', 'look']ΒΆ
      +aliases = ['look', 'l']ΒΆ
      @@ -253,7 +253,7 @@ All it does is display the connect screen.

      -search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}ΒΆ
      +search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.evscaperoom.commands.html b/docs/1.0-dev/api/evennia.contrib.evscaperoom.commands.html index 18d105f033..b50b3949e4 100644 --- a/docs/1.0-dev/api/evennia.contrib.evscaperoom.commands.html +++ b/docs/1.0-dev/api/evennia.contrib.evscaperoom.commands.html @@ -148,7 +148,7 @@ the operation will be general or on the room.

      -aliases = ['q', 'chicken out', 'abort', 'quit']ΒΆ
      +aliases = ['quit', 'q', 'chicken out', 'abort']ΒΆ
      @@ -172,7 +172,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'q chicken out abort quit', 'category': 'evscaperoom', 'key': 'give up', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'quit q chicken out abort', 'category': 'evscaperoom', 'key': 'give up', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}ΒΆ
      @@ -193,7 +193,7 @@ set in self.parse())

      -aliases = ['l', 'ls']ΒΆ
      +aliases = ['ls', 'l']ΒΆ
      @@ -227,7 +227,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'l ls', 'category': 'evscaperoom', 'key': 'look', 'tags': '', 'text': '\n Look at the room, an object or the currently focused object\n\n Usage:\n look [obj]\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'ls l', 'category': 'evscaperoom', 'key': 'look', 'tags': '', 'text': '\n Look at the room, an object or the currently focused object\n\n Usage:\n look [obj]\n\n '}ΒΆ
      @@ -308,7 +308,7 @@ shout

      -aliases = ['shout', 'whisper', ';']ΒΆ
      +aliases = [';', 'whisper', 'shout']ΒΆ
      @@ -337,7 +337,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'shout whisper ;', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n Perform an communication action.\n\n Usage:\n say <text>\n whisper\n shout\n\n '}ΒΆ
      +search_index_entry = {'aliases': '; whisper shout', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n Perform an communication action.\n\n Usage:\n say <text>\n whisper\n shout\n\n '}ΒΆ
      @@ -365,7 +365,7 @@ emote /me points to /box and /lever.

      -aliases = [':', 'pose']ΒΆ
      +aliases = ['pose', ':']ΒΆ
      @@ -404,7 +404,7 @@ set in self.parse())

      -search_index_entry = {'aliases': ': pose', 'category': 'general', 'key': 'emote', 'tags': '', 'text': '\n Perform a free-form emote. Use /me to\n include yourself in the emote and /name\n to include other objects or characters.\n Use "..." to enact speech.\n\n Usage:\n emote <emote>\n :<emote\n\n Example:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'pose :', 'category': 'general', 'key': 'emote', 'tags': '', 'text': '\n Perform a free-form emote. Use /me to\n include yourself in the emote and /name\n to include other objects or characters.\n Use "..." to enact speech.\n\n Usage:\n emote <emote>\n :<emote\n\n Example:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n '}ΒΆ
      @@ -427,7 +427,7 @@ looks and what actions is available.

      -aliases = ['ex', 'unfocus', 'e', 'examine']ΒΆ
      +aliases = ['unfocus', 'e', 'examine', 'ex']ΒΆ
      @@ -456,7 +456,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'ex unfocus e examine', 'category': 'evscaperoom', 'key': 'focus', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'unfocus e examine ex', 'category': 'evscaperoom', 'key': 'focus', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}ΒΆ
      @@ -518,7 +518,7 @@ set in self.parse())

      -aliases = ['i', 'inventory', 'give', 'inv']ΒΆ
      +aliases = ['inv', 'i', 'give', 'inventory']ΒΆ
      @@ -542,7 +542,7 @@ set in self.parse())

      -search_index_entry = {'aliases': 'i inventory give inv', 'category': 'evscaperoom', 'key': 'get', 'tags': '', 'text': '\n Use focus / examine instead.\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'inv i give inventory', 'category': 'evscaperoom', 'key': 'get', 'tags': '', 'text': '\n Use focus / examine instead.\n\n '}ΒΆ
      @@ -563,7 +563,7 @@ set in self.parse())

      -aliases = ['@open', '@dig']ΒΆ
      +aliases = ['@dig', '@open']ΒΆ
      @@ -586,7 +586,7 @@ to all the variables defined therein.

      -search_index_entry = {'aliases': '@open @dig', 'category': 'general', 'key': 'open', 'tags': '', 'text': '\n Interact with an object in focus.\n\n Usage:\n <action> [arg]\n\n '}ΒΆ
      +search_index_entry = {'aliases': '@dig @open', 'category': 'general', 'key': 'open', 'tags': '', 'text': '\n Interact with an object in focus.\n\n Usage:\n <action> [arg]\n\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.extended_room.html b/docs/1.0-dev/api/evennia.contrib.extended_room.html index 0b426da4f1..403ff156e1 100644 --- a/docs/1.0-dev/api/evennia.contrib.extended_room.html +++ b/docs/1.0-dev/api/evennia.contrib.extended_room.html @@ -277,7 +277,7 @@ look *<account&g
      -aliases = ['l', 'ls']ΒΆ
      +aliases = ['ls', 'l']ΒΆ
      @@ -297,7 +297,7 @@ look *<account&g
      -search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look\n\n Usage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects in your vicinity.\n '}ΒΆ
      +search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'tags': '', 'text': '\n look\n\n Usage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects in your vicinity.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.ingame_python.commands.html b/docs/1.0-dev/api/evennia.contrib.ingame_python.commands.html index 45775a0973..6e39f41dc3 100644 --- a/docs/1.0-dev/api/evennia.contrib.ingame_python.commands.html +++ b/docs/1.0-dev/api/evennia.contrib.ingame_python.commands.html @@ -53,7 +53,7 @@
      -aliases = ['@callbacks', '@callback', '@calls']ΒΆ
      +aliases = ['@callback', '@calls', '@callbacks']ΒΆ
      @@ -134,7 +134,7 @@ on user permission.

      -search_index_entry = {'aliases': '@callbacks @callback @calls', 'category': 'building', 'key': '@call', 'tags': '', 'text': '\n Command to edit callbacks.\n '}ΒΆ
      +search_index_entry = {'aliases': '@callback @calls @callbacks', 'category': 'building', 'key': '@call', 'tags': '', 'text': '\n Command to edit callbacks.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.rpsystem.html b/docs/1.0-dev/api/evennia.contrib.rpsystem.html index 7f70e7a2f9..9d81fbca5a 100644 --- a/docs/1.0-dev/api/evennia.contrib.rpsystem.html +++ b/docs/1.0-dev/api/evennia.contrib.rpsystem.html @@ -637,7 +637,7 @@ a different language.

      -aliases = ['"', "'"]ΒΆ
      +aliases = ["'", '"']ΒΆ
      @@ -663,7 +663,7 @@ a different language.

      -search_index_entry = {'aliases': '" \'', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n speak as your character\n\n Usage:\n say <message>\n\n Talk to those in your current location.\n '}ΒΆ
      +search_index_entry = {'aliases': '\' "', 'category': 'general', 'key': 'say', 'tags': '', 'text': '\n speak as your character\n\n Usage:\n say <message>\n\n Talk to those in your current location.\n '}ΒΆ
      @@ -802,7 +802,7 @@ Using the command without arguments will list all current recogs.

      -aliases = ['forget', 'recognize']ΒΆ
      +aliases = ['recognize', 'forget']ΒΆ
      @@ -829,7 +829,7 @@ Using the command without arguments will list all current recogs.

      -search_index_entry = {'aliases': 'forget recognize', 'category': 'general', 'key': 'recog', 'tags': '', 'text': '\n Recognize another person in the same room.\n\n Usage:\n recog\n recog sdesc as alias\n forget alias\n\n Example:\n recog tall man as Griatch\n forget griatch\n\n This will assign a personal alias for a person, or forget said alias.\n Using the command without arguments will list all current recogs.\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'recognize forget', 'category': 'general', 'key': 'recog', 'tags': '', 'text': '\n Recognize another person in the same room.\n\n Usage:\n recog\n recog sdesc as alias\n forget alias\n\n Example:\n recog tall man as Griatch\n forget griatch\n\n This will assign a personal alias for a person, or forget said alias.\n Using the command without arguments will list all current recogs.\n\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.tutorial_examples.red_button.html b/docs/1.0-dev/api/evennia.contrib.tutorial_examples.red_button.html index 5a8185a0dc..36efc0ac45 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorial_examples.red_button.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorial_examples.red_button.html @@ -433,7 +433,7 @@ be mutually exclusive.

      -aliases = ['examine', 'ex', 'listen', 'l', 'feel', 'get']ΒΆ
      +aliases = ['ex', 'get', 'examine', 'feel', 'listen', 'l']ΒΆ
      @@ -459,7 +459,7 @@ be mutually exclusive.

      -search_index_entry = {'aliases': 'examine ex listen l feel get', 'category': 'general', 'key': 'look', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}ΒΆ
      +search_index_entry = {'aliases': 'ex get examine feel listen l', 'category': 'general', 'key': 'look', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.tutorial_world.objects.html b/docs/1.0-dev/api/evennia.contrib.tutorial_world.objects.html index ed5893ac5d..f728095dfb 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorial_world.objects.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorial_world.objects.html @@ -362,7 +362,7 @@ of the object. We overload it with our own version.

      -aliases = ['burn', 'light']ΒΆ
      +aliases = ['light', 'burn']ΒΆ
      @@ -389,7 +389,7 @@ to sit on a β€œlightable” object, we operate only on self.obj.

      -search_index_entry = {'aliases': 'burn light', 'category': 'tutorialworld', 'key': 'on', 'tags': '', 'text': '\n Creates light where there was none. Something to burn.\n '}ΒΆ
      +search_index_entry = {'aliases': 'light burn', 'category': 'tutorialworld', 'key': 'on', 'tags': '', 'text': '\n Creates light where there was none. Something to burn.\n '}ΒΆ
      @@ -493,7 +493,7 @@ shift green root up/down

      -aliases = ['shiftroot', 'move', 'push', 'pull']ΒΆ
      +aliases = ['shiftroot', 'push', 'pull', 'move']ΒΆ
      @@ -529,7 +529,7 @@ yellow/green - horizontal roots

      -search_index_entry = {'aliases': 'shiftroot move push pull', 'category': 'tutorialworld', 'key': 'shift', 'tags': '', 'text': '\n Shifts roots around.\n\n Usage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'shiftroot push pull move', 'category': 'tutorialworld', 'key': 'shift', 'tags': '', 'text': '\n Shifts roots around.\n\n Usage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n '}ΒΆ
      @@ -546,7 +546,7 @@ yellow/green - horizontal roots

      -aliases = ['press button', 'button', 'push button']ΒΆ
      +aliases = ['button', 'press button', 'push button']ΒΆ
      @@ -572,7 +572,7 @@ yellow/green - horizontal roots

      -search_index_entry = {'aliases': 'press button button push button', 'category': 'tutorialworld', 'key': 'press', 'tags': '', 'text': '\n Presses a button.\n '}ΒΆ
      +search_index_entry = {'aliases': 'button press button push button', 'category': 'tutorialworld', 'key': 'press', 'tags': '', 'text': '\n Presses a button.\n '}ΒΆ
      @@ -716,7 +716,7 @@ parry - forgoes your attack but will make you harder to hit on next

      -aliases = ['stab', 'thrust', 'kill', 'pierce', 'parry', 'defend', 'chop', 'hit', 'bash', 'fight', 'slash']ΒΆ
      +aliases = ['chop', 'defend', 'thrust', 'stab', 'kill', 'hit', 'slash', 'bash', 'parry', 'fight', 'pierce']ΒΆ
      @@ -742,7 +742,7 @@ parry - forgoes your attack but will make you harder to hit on next

      -search_index_entry = {'aliases': 'stab thrust kill pierce parry defend chop hit bash fight slash', 'category': 'tutorialworld', 'key': 'attack', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'chop defend thrust stab kill hit slash bash parry fight pierce', 'category': 'tutorialworld', 'key': 'attack', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.contrib.tutorial_world.rooms.html b/docs/1.0-dev/api/evennia.contrib.tutorial_world.rooms.html index 6fe257dc6c..3f852d4169 100644 --- a/docs/1.0-dev/api/evennia.contrib.tutorial_world.rooms.html +++ b/docs/1.0-dev/api/evennia.contrib.tutorial_world.rooms.html @@ -185,7 +185,7 @@ code except for adding in the details.

      -aliases = ['l', 'ls']ΒΆ
      +aliases = ['ls', 'l']ΒΆ
      @@ -200,7 +200,7 @@ code except for adding in the details.

      -search_index_entry = {'aliases': 'l ls', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n looks at the room and on details\n\n Usage:\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects\n in your vicinity.\n\n Tutorial: This is a child of the default Look command, that also\n allows us to look at "details" in the room. These details are\n things to examine and offers some extra description without\n actually having to be actual database objects. It uses the\n return_detail() hook on TutorialRooms for this.\n '}ΒΆ
      +search_index_entry = {'aliases': 'ls l', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n looks at the room and on details\n\n Usage:\n look <obj>\n look <room detail>\n look *<account>\n\n Observes your location, details at your location or objects\n in your vicinity.\n\n Tutorial: This is a child of the default Look command, that also\n allows us to look at "details" in the room. These details are\n things to examine and offers some extra description without\n actually having to be actual database objects. It uses the\n return_detail() hook on TutorialRooms for this.\n '}ΒΆ
      @@ -866,7 +866,7 @@ to find something.

      -aliases = ['search', 'fiddle', 'feel around', 'l', 'feel']ΒΆ
      +aliases = ['feel around', 'search', 'fiddle', 'feel', 'l']ΒΆ
      @@ -894,7 +894,7 @@ random chance of eventually finding a light source.

      -search_index_entry = {'aliases': 'search fiddle feel around l feel', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}ΒΆ
      +search_index_entry = {'aliases': 'feel around search fiddle feel l', 'category': 'tutorialworld', 'key': 'look', 'tags': '', 'text': '\n Look around in darkness\n\n Usage:\n look\n\n Look around in the darkness, trying\n to find something.\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.server.profiling.dummyrunner.html b/docs/1.0-dev/api/evennia.server.profiling.dummyrunner.html index 16fa56afd0..7de452833a 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.dummyrunner.html +++ b/docs/1.0-dev/api/evennia.server.profiling.dummyrunner.html @@ -68,13 +68,83 @@ change which actions by adding a path to

      in your settings. See utils.dummyrunner_actions.py for instructions on how to define this module.

      +
      +
      +class evennia.server.profiling.dummyrunner.CmdDummyRunnerEchoResponse(**kwargs)[source]ΒΆ
      +

      Bases: evennia.commands.command.Command

      +

      Dummyrunner command measuring the round-about response time +from sending to receiving a result.

      +
      +
      Usage:

      dummyrunner_echo_response <timestamp>

      +
      +
      Responds with

      dummyrunner_echo_response:<timestamp>,<current_time>

      +
      +
      +

      The dummyrunner will send this and then compare the send time +with the receive time on both ends.

      +
      +
      +key = 'dummyrunner_echo_response'ΒΆ
      +
      + +
      +
      +func()[source]ΒΆ
      +

      This is the actual executing part of the command. It is +called directly after self.parse(). See the docstring of this +module for which object properties are available (beyond those +set in self.parse())

      +
      + +
      +
      +aliases = []ΒΆ
      +
      + +
      +
      +help_category = 'general'ΒΆ
      +
      + +
      +
      +lock_storage = 'cmd:all();'ΒΆ
      +
      + +
      +
      +search_index_entry = {'aliases': '', 'category': 'general', 'key': 'dummyrunner_echo_response', 'tags': '', 'text': '\n Dummyrunner command measuring the round-about response time\n from sending to receiving a result.\n\n Usage:\n dummyrunner_echo_response <timestamp>\n\n Responds with\n dummyrunner_echo_response:<timestamp>,<current_time>\n\n The dummyrunner will send this and then compare the send time\n with the receive time on both ends.\n\n '}ΒΆ
      +
      + +
      + +
      +
      +class evennia.server.profiling.dummyrunner.DummyRunnerCmdSet(cmdsetobj=None, key=None)[source]ΒΆ
      +

      Bases: evennia.commands.cmdset.CmdSet

      +

      Dummyrunner injected cmdset.

      +
      +
      +at_cmdset_creation()[source]ΒΆ
      +

      Hook method - this should be overloaded in the inheriting +class, and should take care of populating the cmdset by use of +self.add().

      +
      + +
      +
      +path = 'evennia.server.profiling.dummyrunner.DummyRunnerCmdSet'ΒΆ
      +
      + +
      +
      evennia.server.profiling.dummyrunner.idcounter()[source]ΒΆ

      Makes unique ids.

      Returns
      -

      count (int) – A globally unique counter.

      +

      str – A globally unique id.

      @@ -111,6 +181,11 @@ for instructions on how to define this module.

      Handles connection to a running Evennia server, mimicking a real account by sending commands on a timer.

      +
      +
      +report(text, clientkey)[source]ΒΆ
      +
      +
      connectionMade()[source]ΒΆ
      @@ -181,13 +256,28 @@ all β€œintelligence” of the dummy client.

      class evennia.server.profiling.dummyrunner.DummyFactory(actions)[source]ΒΆ
      -

      Bases: twisted.internet.protocol.ClientFactory

      +

      Bases: twisted.internet.protocol.ReconnectingClientFactory

      protocolΒΆ

      alias of DummyClient

      +
      +
      +initialDelay = 1ΒΆ
      +
      + +
      +
      +maxDelay = 1ΒΆ
      +
      + +
      +
      +noisy = FalseΒΆ
      +
      +
      __init__(actions)[source]ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.server.profiling.dummyrunner_settings.html b/docs/1.0-dev/api/evennia.server.profiling.dummyrunner_settings.html index fa7102babc..08f3c077ec 100644 --- a/docs/1.0-dev/api/evennia.server.profiling.dummyrunner_settings.html +++ b/docs/1.0-dev/api/evennia.server.profiling.dummyrunner_settings.html @@ -45,9 +45,9 @@ the actions available to dummy accounts.

      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

      • +
      • TIMESTEP - time in seconds between each β€˜tick’. 1 is a good start.

      • +
      • CHANCE_OF_ACTION - chance 0-1 of action happening. Default is 0.5.

      • +
      • CHANCE_OF_LOGIN - chance 0-1 of login happening. 0.01 is a good number.

      • TELNET_PORT - port to use, defaults to settings.TELNET_PORT

      • ACTIONS - see below

      @@ -59,16 +59,15 @@ the actions available to dummy accounts.

      chance of occurring given by CHANCE_OF_LOGIN. This function is usually responsible for logging in the account. 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, +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.

      +so if will still work also if the given chances don’t add up to 1).

      +

      The PROFILE variable define pre-made ACTION tuples for convenience.

      +

      Each function should return an iterable of one or more command-call +strings (like β€œlook here”), so each can group multiple command 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.

      +reference to the dummy client currently performing the action.

      The client object has the following relevant properties and methods:

      • key - an optional client key. This is only used for dummyrunner output. @@ -171,6 +170,16 @@ commands (such as creating an account and logging in).

        move through south exit if available

      +
      +
      +evennia.server.profiling.dummyrunner_settings.c_measure_lag(client)[source]ΒΆ
      +

      Special dummyrunner command, injected in c_login. It measures +response time. Including this in the ACTION tuple will give more +dummyrunner output about just how fast commands are being processed.

      +

      The dummyrunner will treat this special and inject the +{timestamp} just before sending.

      +
      + diff --git a/docs/1.0-dev/api/evennia.server.throttle.html b/docs/1.0-dev/api/evennia.server.throttle.html index ceae4f7884..47befc5558 100644 --- a/docs/1.0-dev/api/evennia.server.throttle.html +++ b/docs/1.0-dev/api/evennia.server.throttle.html @@ -64,7 +64,8 @@ caches for automatic key eviction and persistence configurability.

      Keyword Arguments
      • name (str) – Name of this throttle.

      • -
      • limit (int) – Max number of failures before imposing limiter

      • +
      • limit (int) – Max number of failures before imposing limiter. If None, +the throttle is disabled.

      • timeout (int) – number of timeout seconds after max number of tries has been reached.

      • cache_size (int) – Max number of attempts to record per IP within a diff --git a/docs/1.0-dev/api/evennia.utils.eveditor.html b/docs/1.0-dev/api/evennia.utils.eveditor.html index 87d944c824..7f1298dd9a 100644 --- a/docs/1.0-dev/api/evennia.utils.eveditor.html +++ b/docs/1.0-dev/api/evennia.utils.eveditor.html @@ -275,7 +275,7 @@ indentation.

        -aliases = [':A', ':f', ':S', ':p', ':UU', '::', ':fd', ':q!', ':fi', ':q', ':uu', ':<', ':w', ':::', ':I', ':i', ':x', ':s', ':wq', ':h', ':>', ':', ':r', ':DD', ':y', ':!', ':dw', ':dd', ':j', ':=', ':u', ':echo']ΒΆ
        +aliases = [':dd', ':j', ':wq', ':fd', ':dw', ':I', ':r', ':u', ':h', ':', ':i', ':q', ':<', ':=', ':S', ':p', ':y', ':w', '::', ':A', ':fi', ':x', ':!', ':>', ':s', ':q!', ':echo', ':UU', ':f', ':uu', ':::', ':DD']ΒΆ
        @@ -303,7 +303,7 @@ efficient presentation.

        -search_index_entry = {'aliases': ':A :f :S :p :UU :: :fd :q! :fi :q :uu :< :w ::: :I :i :x :s :wq :h :> : :r :DD :y :! :dw :dd :j := :u :echo', 'category': 'general', 'key': ':editor_command_group', 'tags': '', 'text': '\n Commands for the editor\n '}ΒΆ
        +search_index_entry = {'aliases': ':dd :j :wq :fd :dw :I :r :u :h : :i :q :< := :S :p :y :w :: :A :fi :x :! :> :s :q! :echo :UU :f :uu ::: :DD', 'category': 'general', 'key': ':editor_command_group', 'tags': '', 'text': '\n Commands for the editor\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.utils.evmenu.html b/docs/1.0-dev/api/evennia.utils.evmenu.html index cf93aa27a3..220f622e20 100644 --- a/docs/1.0-dev/api/evennia.utils.evmenu.html +++ b/docs/1.0-dev/api/evennia.utils.evmenu.html @@ -941,7 +941,7 @@ single question.

      -aliases = ['n', 'yes', 'y', 'no', '__nomatch_command', 'abort', 'a']ΒΆ
      +aliases = ['y', 'a', 'no', 'n', 'abort', '__nomatch_command', 'yes']ΒΆ
      @@ -967,7 +967,7 @@ single question.

      -search_index_entry = {'aliases': 'n yes y no __nomatch_command abort a', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}ΒΆ
      +search_index_entry = {'aliases': 'y a no n abort __nomatch_command yes', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}ΒΆ
      diff --git a/docs/1.0-dev/api/evennia.utils.evmore.html b/docs/1.0-dev/api/evennia.utils.evmore.html index 461dc2380f..1d10af0e91 100644 --- a/docs/1.0-dev/api/evennia.utils.evmore.html +++ b/docs/1.0-dev/api/evennia.utils.evmore.html @@ -76,7 +76,7 @@ the caller.msg() construct every time the page is updated.

      -aliases = ['q', 'n', 'end', 'back', 'b', 'top', 't', 'e', 'abort', 'a', 'quit', 'next']ΒΆ
      +aliases = ['b', 'e', 'end', 'top', 'back', 'quit', 'a', 'q', 'n', 'abort', 't', 'next']ΒΆ
      @@ -102,7 +102,7 @@ the caller.msg() construct every time the page is updated.

      -search_index_entry = {'aliases': 'q n end back b top t e abort a quit next', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Manipulate the text paging\n '}ΒΆ
      +search_index_entry = {'aliases': 'b e end top back quit a q n abort t next', 'category': 'general', 'key': '__noinput_command', 'tags': '', 'text': '\n Manipulate the text paging\n '}ΒΆ
      diff --git a/docs/1.0-dev/genindex.html b/docs/1.0-dev/genindex.html index 43383cf08d..e2b88b370a 100644 --- a/docs/1.0-dev/genindex.html +++ b/docs/1.0-dev/genindex.html @@ -1112,6 +1112,8 @@
    12. (evennia.help.models.HelpEntry attribute)
    13. (evennia.objects.objects.ExitCommand attribute) +
    14. +
    15. (evennia.server.profiling.dummyrunner.CmdDummyRunnerEchoResponse attribute)
    16. (evennia.typeclasses.models.TypedObject attribute)
    17. @@ -1572,6 +1574,8 @@
    18. (evennia.contrib.tutorial_world.rooms.DarkCmdSet method)
    19. (evennia.contrib.tutorial_world.rooms.TutorialRoomCmdSet method) +
    20. +
    21. (evennia.server.profiling.dummyrunner.DummyRunnerCmdSet method)
    22. (evennia.utils.eveditor.EvEditorCmdSet method)
    23. @@ -2543,6 +2547,8 @@
    24. c_logout() (in module evennia.server.profiling.dummyrunner_settings)
    25. c_looks() (in module evennia.server.profiling.dummyrunner_settings) +
    26. +
    27. c_measure_lag() (in module evennia.server.profiling.dummyrunner_settings)
    28. c_moves() (in module evennia.server.profiling.dummyrunner_settings)
    29. @@ -3072,6 +3078,8 @@
    30. (class in evennia.contrib.clothing)
    31. +
    32. CmdDummyRunnerEchoResponse (class in evennia.server.profiling.dummyrunner) +
    33. CmdEast (class in evennia.contrib.tutorial_world.rooms)
    34. CmdEditorBase (class in evennia.utils.eveditor) @@ -3192,14 +3200,14 @@
    35. CmdLock (class in evennia.commands.default.building)
    36. +
    37. initialize_for_combat() (evennia.contrib.turnbattle.tb_basic.TBBasicTurnHandler method) @@ -9032,10 +9048,10 @@
    38. (evennia.contrib.turnbattle.tb_range.TBRangeTurnHandler method)
    39. -
    40. initialize_nick_templates() (in module evennia.typeclasses.attributes) -

    IntroductionΒΆ

    -

    Sometimes it can be useful to try to determine just how efficient a particular piece of code is, or -to figure out if one could speed up things more than they are. There are many ways to test the -performance of Python and the running server.

    -

    Before digging into this section, remember Donald Knuth’s words of -wisdom:

    +

    Sometimes it can be useful to try to determine just how efficient a particular +piece of code is, or to figure out if one could speed up things more than they +are. There are many ways to test the performance of Python and the running +server.

    +

    Before digging into this section, remember Donald Knuth’s +words of wisdom:

    […]about 97% of the time: Premature optimization is the root of all evil.

    -

    That is, don’t start to try to optimize your code until you have actually identified a need to do -so. This means your code must actually be working before you start to consider optimization. -Optimization will also often make your code more complex and harder to read. Consider readability -and maintainability and you may find that a small gain in speed is just not worth it.

    +

    That is, don’t start to try to optimize your code until you have actually +identified a need to do so. This means your code must actually be working before +you start to consider optimization. Optimization will also often make your code +more complex and harder to read. Consider readability and maintainability and +you may find that a small gain in speed is just not worth it.