From f18afae6ae2a4bb5ed4aa6e72bcf4ef4d38681b1 Mon Sep 17 00:00:00 2001 From: duysqubix Date: Tue, 23 Mar 2021 04:32:21 +0000 Subject: [PATCH 01/71] need to add twistd location to environment --- bin/unix/evennia.service | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/unix/evennia.service b/bin/unix/evennia.service index a312bb8b4e..5b636f202b 100644 --- a/bin/unix/evennia.service +++ b/bin/unix/evennia.service @@ -26,6 +26,11 @@ User=your-user # ExecStart=/your/path/to/pyenv/bin/python /your/path/to/evennia/bin/unix/evennia ipstart --gamedir /your/path/to/mygame +# +# Service needs to know the path to twistd binary +# Replace /your/path/to/venv/bin with whatever is appropriate +Environment="PATH=/your/path/to/pyenv/bin:$PATH" + # restart on all failures, wait 3 seconds before doing so. Restart=on-failure RestartSec=3 From 464b13d3b7cb99a0dc1414c74d184ee7663c0020 Mon Sep 17 00:00:00 2001 From: fariparedes Date: Fri, 26 Mar 2021 21:34:03 -0400 Subject: [PATCH 02/71] Proposed fix --- evennia/accounts/accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index 4cb1255dfa..0d8a9dd09a 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -1500,7 +1500,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): ) ) result.append("\n\n |whelp|n - more commands") - result.append("\n |wooc |n - talk on public channel") + result.append("\n |wpublic |n - talk on public channel") charmax = _MAX_NR_CHARACTERS From e8d8227c87dc77d71e85290bdbe1a7739efad6c5 Mon Sep 17 00:00:00 2001 From: fariparedes Date: Fri, 26 Mar 2021 21:42:15 -0400 Subject: [PATCH 03/71] Proposed fix --- evennia/objects/objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 746d02a47a..660dc4de01 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -628,7 +628,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): logger.log_trace() kwargs["options"] = options try: - if not self.at_msg_receive(text=text, **kwargs): + if not self.at_msg_receive(text=text, from_obj=from_obj, **kwargs): # if at_msg_receive returns false, we abort message to this object return except Exception: @@ -1443,7 +1443,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): } ) - location.msg_contents(string, exclude=(self,), mapping=mapping) + location.msg_contents(string, exclude=(self,), from_obj=self, mapping=mapping) def announce_move_to(self, source_location, msg=None, mapping=None, **kwargs): """ @@ -1505,7 +1505,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): } ) - destination.msg_contents(string, exclude=(self,), mapping=mapping) + destination.msg_contents(string, exclude=(self,), from_obj=self, mapping=mapping) def at_after_move(self, source_location, **kwargs): """ From 3d102740a985f97362b920d8aafac179a91fb004 Mon Sep 17 00:00:00 2001 From: RealKinetix Date: Mon, 29 Mar 2021 22:34:55 -0700 Subject: [PATCH 04/71] Maintenance time calculations should be done in minutes, not seconds. Should resolve #2336 --- evennia/server/portal/portal.py | 2 +- evennia/server/server.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/evennia/server/portal/portal.py b/evennia/server/portal/portal.py index d1af41a143..f3e29fb9a0 100644 --- a/evennia/server/portal/portal.py +++ b/evennia/server/portal/portal.py @@ -119,7 +119,7 @@ def _portal_maintenance(): _MAINTENANCE_COUNT += 1 - if _MAINTENANCE_COUNT % (3600 * 7) == 0: + if _MAINTENANCE_COUNT % (60 * 7) == 0: # drop database connection every 7 hrs to avoid default timeouts on MySQL # (see https://github.com/evennia/evennia/issues/1376) connection.close() diff --git a/evennia/server/server.py b/evennia/server/server.py index 4d09d8fdb6..fb112fe5cb 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -140,16 +140,16 @@ def _server_maintenance(): _GAMETIME_MODULE.SERVER_RUNTIME_LAST_UPDATED = now ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.SERVER_RUNTIME) - if _MAINTENANCE_COUNT % 300 == 0: + if _MAINTENANCE_COUNT % 5 == 0: # check cache size every 5 minutes _FLUSH_CACHE(_IDMAPPER_CACHE_MAXSIZE) - if _MAINTENANCE_COUNT % 3600 == 0: + if _MAINTENANCE_COUNT % 60 == 0: # validate scripts every hour evennia.ScriptDB.objects.validate() - if _MAINTENANCE_COUNT % 3700 == 0: + if _MAINTENANCE_COUNT % 61 == 0: # validate channels off-sync with scripts evennia.CHANNEL_HANDLER.update() - if _MAINTENANCE_COUNT % (3600 * 7) == 0: + if _MAINTENANCE_COUNT % (60 * 7) == 0: # drop database connection every 7 hrs to avoid default timeouts on MySQL # (see https://github.com/evennia/evennia/issues/1376) connection.close() From f088ba4ba21b130b472dd140a72b76900c0dfb8e Mon Sep 17 00:00:00 2001 From: RealKinetix Date: Tue, 6 Apr 2021 17:25:08 -0700 Subject: [PATCH 05/71] Fixed related server test in the test suite with appropriate timing trigger. --- evennia/server/tests/test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/server/tests/test_server.py b/evennia/server/tests/test_server.py index 33a9341cae..0d4a217ed7 100644 --- a/evennia/server/tests/test_server.py +++ b/evennia/server/tests/test_server.py @@ -84,7 +84,7 @@ class TestServer(TestCase): _FLUSH_CACHE=DEFAULT, connection=DEFAULT, _IDMAPPER_CACHE_MAXSIZE=1000, - _MAINTENANCE_COUNT=3700 - 1, + _MAINTENANCE_COUNT=62 - 1, _LAST_SERVER_TIME_SNAPSHOT=0, ServerConfig=DEFAULT, ) as mocks: From 24ed366b326d1de0ac59d880e20b46dd0bea5901 Mon Sep 17 00:00:00 2001 From: RealKinetix Date: Tue, 6 Apr 2021 17:43:49 -0700 Subject: [PATCH 06/71] Unsure how the last commit had an old edit, but this should fix tests now. --- evennia/server/tests/test_server.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evennia/server/tests/test_server.py b/evennia/server/tests/test_server.py index 0d4a217ed7..a7933cd6ff 100644 --- a/evennia/server/tests/test_server.py +++ b/evennia/server/tests/test_server.py @@ -47,7 +47,7 @@ class TestServer(TestCase): _FLUSH_CACHE=DEFAULT, connection=DEFAULT, _IDMAPPER_CACHE_MAXSIZE=1000, - _MAINTENANCE_COUNT=600 - 1, + _MAINTENANCE_COUNT=5 - 1, ServerConfig=DEFAULT, ) as mocks: mocks["connection"].close = MagicMock() @@ -65,7 +65,7 @@ class TestServer(TestCase): _FLUSH_CACHE=DEFAULT, connection=DEFAULT, _IDMAPPER_CACHE_MAXSIZE=1000, - _MAINTENANCE_COUNT=3600 - 1, + _MAINTENANCE_COUNT=60 - 1, _LAST_SERVER_TIME_SNAPSHOT=0, ServerConfig=DEFAULT, ) as mocks: @@ -84,7 +84,7 @@ class TestServer(TestCase): _FLUSH_CACHE=DEFAULT, connection=DEFAULT, _IDMAPPER_CACHE_MAXSIZE=1000, - _MAINTENANCE_COUNT=62 - 1, + _MAINTENANCE_COUNT=61 - 1, _LAST_SERVER_TIME_SNAPSHOT=0, ServerConfig=DEFAULT, ) as mocks: @@ -102,7 +102,7 @@ class TestServer(TestCase): _FLUSH_CACHE=DEFAULT, connection=DEFAULT, _IDMAPPER_CACHE_MAXSIZE=1000, - _MAINTENANCE_COUNT=(3600 * 7) - 1, + _MAINTENANCE_COUNT=(60 * 7) - 1, _LAST_SERVER_TIME_SNAPSHOT=0, ServerConfig=DEFAULT, ) as mocks: From be195474d32dec50eb440b072315773c8957e24e Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Wed, 7 Apr 2021 17:02:22 -0400 Subject: [PATCH 07/71] .gitignore Atom's remote sync settings file Atom's remote sync settings file It contains clear text passwords. As well as settings that are user specific. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6641f347d9..79d177f411 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ twistd.bat # never commit docs/build docs/build + +# Atom's remote sync settings file (it contains clear text password) +.remote-sync.json From 6d7fe60ad52453eb2dd56282bee71a1562b170be Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Wed, 7 Apr 2021 17:36:54 -0400 Subject: [PATCH 08/71] Revert ".gitignore Atom's remote sync settings file" This reverts commit be195474d32dec50eb440b072315773c8957e24e. --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 79d177f411..6641f347d9 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,3 @@ twistd.bat # never commit docs/build docs/build - -# Atom's remote sync settings file (it contains clear text password) -.remote-sync.json From 7d2a5161b293d8dff1ff3ea0ecf5e722e0b7f10a Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Wed, 7 Apr 2021 18:00:02 -0400 Subject: [PATCH 09/71] TaskHandler Return task_id if persistent TaskHandler Return task_id if persistent unit tests passed 645 ran --- evennia/scripts/taskhandler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 6f5dccc4f9..9506addef4 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -149,6 +149,8 @@ class TaskHandler(object): callback = self.do_task args = [task_id] kwargs = {} + deferLater(reactor, timedelay, callback, *args, **kwargs) + return task_id return deferLater(reactor, timedelay, callback, *args, **kwargs) From 58e99685cd10925b451b55f3d80c58b61ab16084 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Tue, 13 Apr 2021 17:01:55 -0400 Subject: [PATCH 10/71] taskhandler verified do_task causes errors if called manually (early) Any usage of taskhandler's method outside of the deferred instance calling it results in errors. Referencing: It's easier to access these tasks (should it be necessary) using `evennia.scripts.taskhandler.TASK_HANDLER` I can see it is intended to be used this way. More importantly usage of the global reactor would require usage of reactorbuilder API which is recomended for building reactors only. Commiting notes before switching to twisted's documented methods for making and testing deferrals. In short I need to get an instance of reactor's callLater. Creating and working with that call later will allow me to test taskhandler and make it function as intended. Usage of utils.delay will not change. --- evennia/scripts/taskhandler.py | 27 +++++++++++++++++--- evennia/utils/tests/test_utils.py | 42 ++++++++++++++++++++++++++++++- evennia/utils/utils.py | 15 +++++++++-- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 9506addef4..1fd8a211f1 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -19,8 +19,11 @@ class TaskHandler(object): A light singleton wrapper allowing to access permanent tasks. When `utils.delay` is called, the task handler is used to create - the task. If `utils.delay` is called with `persistent=True`, the - task handler stores the new task and saves. + the task. + If `utils.delay` is called with `persistent=True`. The + task handler creates the task. It saves the delay time, callback, + arguments and kwa to the database. Each of these variables are + serialized to do this. It's easier to access these tasks (should it be necessary) using `evennia.scripts.taskhandler.TASK_HANDLER`, which contains one @@ -101,9 +104,27 @@ class TaskHandler(object): any (any): any additional positional arguments to send to the callback Keyword Args: - persistent (bool, optional): persist the task (store it). + persistent (bool, optional): persist the task (stores it). + Add will return the task's id for use with the global TASK_HANDLER. any (any): additional keyword arguments to send to the callback + Returns: + twisted.internet.defer.Deferred instance of the deferred task + task_id (int), the task's id intended for use with this class. + + Notes: + This method has two return types. + An instance of twisted's Deferred class is standard. + If the persistent kwarg is truthy instead a task ID will be returned. + This task id can be used with task handler's do_task and remove methods. + + If the persistent kwarg is truthy: + The callback, args and values for kwarg will be serialized. Type + and attribute errors during the serialization will be logged, + but will not throw exceptions. + Do not use memory references in the callback function or arguments. + As those memory references will no longer acurately point to + the variable desired. """ persistent = kwargs.get("persistent", False) if persistent: diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 67625bbfbb..b1a97c984d 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -6,13 +6,15 @@ TODO: Not nearly all utilities are covered yet. """ import os.path - import mock + from django.test import TestCase from datetime import datetime +from twisted.internet import task, reactor from evennia.utils.ansi import ANSIString from evennia.utils import utils +from evennia.utils.test_resources import EvenniaTest class TestIsIter(TestCase): @@ -292,3 +294,41 @@ class LatinifyTest(TestCase): byte_str = utils.to_bytes(self.example_str) result = utils.latinify(byte_str) self.assertEqual(result, self.expected_output) + + +_TASK_HANDLER = None + + +def dummy_func(obj): + """ + Used in TestDelay. + + A function that: + can be serialized + uses no memory references + uses evennia objects + """ + # get a reference of object + from evennia.objects.models import ObjectDB + obj = ObjectDB.objects.object_search(obj) + obj = obj[0] + # make changes to object + obj.ndb.dummy_var = 'dummy_func ran' + + +class TestDelay(EvenniaTest): + """ + Test utils.delay. + """ + + def test_delay(self): + # get a reference of TASK_HANDLER + global _TASK_HANDLER + if _TASK_HANDLER is None: + from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER + self.char1.ndb.dummy_var = False + # test a persistent deferral + task_id = utils.delay(1, dummy_func, self.char1.dbref, persistent=True) + _TASK_HANDLER.do_task(task_id) # run the deferred task + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.char1.ndb.dummy_var = False diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index b0abe2b9c7..105f9474a6 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1022,7 +1022,7 @@ _TASK_HANDLER = None def delay(timedelay, callback, *args, **kwargs): """ - Delay the return of a value. + Delay the calling of a callback (function). Args: timedelay (int or float): The delay in seconds @@ -1040,15 +1040,26 @@ def delay(timedelay, callback, *args, **kwargs): commandhandler callback chain, the callback chain can be defined directly in the command body and don't need to be specified here. + Reference twisted.internet.defer.Deferred + if persistent kwarg is truthy: + task_id (int): the task's id intended for use with + evennia.scripts.taskhandler.TASK_HANDLER's do_task and remove methods. Note: The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will be called for persistent or non-persistent tasks. If persistent is set to True, the callback, its arguments - and other keyword arguments will be saved in the database, + and other keyword arguments will be saved (serialized) in the database, assuming they can be. The callback will be executed even after a server restart/reload, taking into account the specified delay (and server down time). + Keep in mind that persistent tasks arguments and callback should not + use memory references. + If persistent is set to True the delay function will return an int + which is the task's id itended for use with TASK_HANDLER's do_task + and remove methods. + + All task's whose time delays have passed will be called on server startup. """ global _TASK_HANDLER From 251a70275bbd98a3e157cbb4c025597a4bb24ac9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 13 Apr 2021 23:57:09 +0200 Subject: [PATCH 11/71] Update the evscaperoom README --- evennia/contrib/evscaperoom/README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/evscaperoom/README.md b/evennia/contrib/evscaperoom/README.md index 8cf8930e74..113897b409 100644 --- a/evennia/contrib/evscaperoom/README.md +++ b/evennia/contrib/evscaperoom/README.md @@ -4,9 +4,15 @@ Evennia contrib - Griatch 2019 This 'Evennia escaperoom game engine' was created for the MUD Coders Guild game Jam, April 14-May 15 2019. The theme for the jam was "One Room". This contains the -utilities and base classes and an empty example room. The code for the full -in-production game 'Evscaperoom' is found at https://github.com/Griatch/evscaperoom -and you can play the full game (for now) at `http://experimental.evennia.com`. +utilities and base classes and an empty example room. + +The original code for the contest is found at https://github.com/Griatch/evscaperoom +but the version on the public Evennia demo is more updated, so if you really +want the latest bug fixes etc you should rather look at +https://github.com/evennia/evdemo/tree/master/evdemo/evscaperoom instead. +A copy of the full game can also be played on the Evennia demo server at +https://demo.evennia.com - just connect to the server and write `evscaperoom` +in the first room to start! # Introduction From 0a5d0d0fe86bf543a91c2e077f6c109164374638 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Wed, 14 Apr 2021 12:14:04 -0400 Subject: [PATCH 12/71] Task_Handler unit test, after completion time only Unit test for task handler tasks that complete after delay time. Required creating local reference for a clock. All evennia unit tests pass with `evennia test evennia`. All of my projects unit tests passed. They have a very heavy usage of reactor. Verified delays working with project manually Moving forward with creating method to call callbacks early. --- evennia/scripts/taskhandler.py | 12 +++++++++--- evennia/utils/tests/test_utils.py | 10 ++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 1fd8a211f1..11ac144a94 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -29,11 +29,17 @@ class TaskHandler(object): `evennia.scripts.taskhandler.TASK_HANDLER`, which contains one instance of this class, and use its `add` and `remove` methods. + Dev notes: + deferLater creates an instance of IDelayedCall using reactor.callLater. + deferLater uses the cancel method on the IDelayedCall instance to create + the defer instance it returns. + """ def __init__(self): self.tasks = {} self.to_save = {} + self.clock = reactor def load(self): """Load from the ServerConfig. @@ -170,10 +176,10 @@ class TaskHandler(object): callback = self.do_task args = [task_id] kwargs = {} - deferLater(reactor, timedelay, callback, *args, **kwargs) + deferLater(self.clock, timedelay, callback, *args, **kwargs) return task_id - return deferLater(reactor, timedelay, callback, *args, **kwargs) + return deferLater(self.clock, timedelay, callback, *args, **kwargs) def remove(self, task_id): """Remove a persistent task without executing it. @@ -219,7 +225,7 @@ class TaskHandler(object): now = datetime.now() for task_id, (date, callbac, args, kwargs) in self.tasks.items(): seconds = max(0, (date - now).total_seconds()) - deferLater(reactor, seconds, self.do_task, task_id) + deferLater(self.clock, seconds, self.do_task, task_id) # Create the soft singleton diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index b1a97c984d..ea23601fce 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -326,9 +326,15 @@ class TestDelay(EvenniaTest): global _TASK_HANDLER if _TASK_HANDLER is None: from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER + _TASK_HANDLER.clock = task.Clock() self.char1.ndb.dummy_var = False - # test a persistent deferral + # test a persistent deferral, that completes after delay time task_id = utils.delay(1, dummy_func, self.char1.dbref, persistent=True) - _TASK_HANDLER.do_task(task_id) # run the deferred task + _TASK_HANDLER.clock.advance(1) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.char1.ndb.dummy_var = False + # test a non persisten deferral, that completes after delay time. + deferal_inst = utils.delay(1, dummy_func, self.char1.dbref) + _TASK_HANDLER.clock.advance(1) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False From 68cd21d48e91317b2ed1664fdac7e680df8a0391 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Fri, 16 Apr 2021 10:55:24 -0400 Subject: [PATCH 13/71] taskhandler suppress cancel error Added an errback to handle errors within task handler's deferred instance. Without this instances of deferred cause a traceback when a deferred is canceled without errback or callback having been called. This traceback does not end execution, and ultimately would only show to main console. Reference cancel: https://github.com/twisted/twisted/blob/trunk/src/twisted/internet/defer.py All evennia unit tests pass. --- evennia/scripts/taskhandler.py | 16 +++++++++++++++- evennia/utils/tests/test_utils.py | 8 +++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 11ac144a94..3273e27e8a 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -6,6 +6,7 @@ from datetime import datetime, timedelta from twisted.internet import reactor from twisted.internet.task import deferLater +from twisted.internet.defer import CancelledError as DefCancelledError from evennia.server.models import ServerConfig from evennia.utils.logger import log_err from evennia.utils.dbserialize import dbserialize, dbunserialize @@ -13,6 +14,17 @@ from evennia.utils.dbserialize import dbserialize, dbunserialize TASK_HANDLER = None +def handle_error(*args, **kwargs): + """ + Handle errors withing deferred objects. + """ + for arg in args: + # suppress cancel errors + if arg.type == DefCancelledError: + continue + raise arg + + class TaskHandler(object): """ @@ -179,7 +191,9 @@ class TaskHandler(object): deferLater(self.clock, timedelay, callback, *args, **kwargs) return task_id - return deferLater(self.clock, timedelay, callback, *args, **kwargs) + d = deferLater(self.clock, timedelay, callback, *args, **kwargs) + d.addErrback(handle_error) + return d def remove(self, task_id): """Remove a persistent task without executing it. diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index ea23601fce..bebea149ce 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -10,7 +10,7 @@ import mock from django.test import TestCase from datetime import datetime -from twisted.internet import task, reactor +from twisted.internet import task from evennia.utils.ansi import ANSIString from evennia.utils import utils @@ -338,3 +338,9 @@ class TestDelay(EvenniaTest): _TASK_HANDLER.clock.advance(1) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False + # test canceling a deferral. + deferal_inst = utils.delay(1, dummy_func, self.char1.dbref) + deferal_inst.cancel() + _TASK_HANDLER.clock.advance(1) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, False) + self.char1.ndb.dummy_var = False From 9f49eba5bdf5b06afa89724074fcd61c2ff152f1 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sun, 18 Apr 2021 00:43:09 -0400 Subject: [PATCH 14/71] task handler, updated to only return task id Updated task handler to only return task id. updated code within evennia that relied on the deferral directly. Including unit test for one. all unit tests pass. Test server functional after restarting, no issues found would telnet web client. (delay was used in the telnet module in the portal folder. I needed to commit this before continuing forward. There is already a high line count change. --- evennia/contrib/slow_exit.py | 5 +- evennia/contrib/tests.py | 6 -- evennia/contrib/tutorial_world/objects.py | 7 +- evennia/scripts/taskhandler.py | 85 +++++++++++++++++------ evennia/server/portal/telnet.py | 5 +- evennia/utils/tests/test_utils.py | 19 +++-- 6 files changed, 86 insertions(+), 41 deletions(-) diff --git a/evennia/contrib/slow_exit.py b/evennia/contrib/slow_exit.py index 107d274b8c..3b060f7471 100644 --- a/evennia/contrib/slow_exit.py +++ b/evennia/contrib/slow_exit.py @@ -36,6 +36,7 @@ TickerHandler might be better. """ from evennia import DefaultExit, utils, Command +from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER MOVE_DELAY = {"stroll": 6, "walk": 4, "run": 2, "sprint": 1} @@ -70,11 +71,11 @@ class SlowExit(DefaultExit): traversing_object.msg("You start moving %s at a %s." % (self.key, move_speed)) # create a delayed movement - deferred = utils.delay(move_delay, move_callback) + task_id = utils.delay(move_delay, move_callback) # we store the deferred on the character, this will allow us # to abort the movement. We must use an ndb here since # deferreds cannot be pickled. - traversing_object.ndb.currently_moving = deferred + traversing_object.ndb.currently_moving = _TASK_HANDLER.get_deferred(task_id) # diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index a4e50a29f7..7b69d07cc9 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1152,13 +1152,7 @@ from evennia.contrib import slow_exit slow_exit.MOVE_DELAY = {"stroll": 0, "walk": 0, "run": 0, "sprint": 0} -def _cancellable_mockdelay(time, callback, *args, **kwargs): - callback(*args, **kwargs) - return Mock() - - class TestSlowExit(CommandTest): - @patch("evennia.utils.delay", _cancellable_mockdelay) def test_exit(self): exi = create_object( slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2 diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index ca4147a715..759d1f1dc0 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -24,6 +24,7 @@ import random from evennia import DefaultObject, DefaultExit, Command, CmdSet from evennia.utils import search, delay, dedent from evennia.prototypes.spawner import spawn +from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER # ------------------------------------------------------------- # @@ -389,7 +390,8 @@ class LightSource(TutorialObject): # start the burn timer. When it runs out, self._burnout # will be called. We store the deferred so it can be # killed in unittesting. - self.deferred = delay(60 * 3, self._burnout) + task_id = delay(60 * 3, self._burnout) + self.deferred = _TASK_HANDLER.get_deferred(task_id) return True @@ -687,7 +689,8 @@ class CrumblingWall(TutorialObject, DefaultExit): self.db.exit_open = True # start a 45 second timer before closing again. We store the deferred so it can be # killed in unittesting. - self.deferred = delay(45, self.reset) + task_id = delay(45, self.reset) + self.deferred = _TASK_HANDLER.get_deferred(task_id) return True def _translate_position(self, root, ipos): diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 3273e27e8a..b865a74185 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -44,7 +44,6 @@ class TaskHandler(object): Dev notes: deferLater creates an instance of IDelayedCall using reactor.callLater. deferLater uses the cancel method on the IDelayedCall instance to create - the defer instance it returns. """ @@ -79,16 +78,18 @@ class TaskHandler(object): continue callback = getattr(obj, method) - self.tasks[task_id] = (date, callback, args, kwargs) + self.tasks[task_id] = date, callback, args, kwargs, True, None if to_save: self.save() def save(self): """Save the tasks in ServerConfig.""" - for task_id, (date, callback, args, kwargs) in self.tasks.items(): + for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): if task_id in self.to_save: continue + if not persistent: + continue if getattr(callback, "__self__", None): # `callback` is an instance method @@ -127,8 +128,8 @@ class TaskHandler(object): any (any): additional keyword arguments to send to the callback Returns: - twisted.internet.defer.Deferred instance of the deferred task task_id (int), the task's id intended for use with this class. + False, if the task has completed before addition finishes. Notes: This method has two return types. @@ -144,19 +145,23 @@ class TaskHandler(object): As those memory references will no longer acurately point to the variable desired. """ + # set the completion time + # Only used on persistent tasks after a restart + now = datetime.now() + delta = timedelta(seconds=timedelay) + comp_time = now + delta + # get an open task id + used_ids = list(self.tasks.keys()) + task_id = 1 + while task_id in used_ids: + task_id += 1 + + # record the task to the tasks dictionary persistent = kwargs.get("persistent", False) if persistent: del kwargs["persistent"] - now = datetime.now() - delta = timedelta(seconds=timedelay) - - # Choose a free task_id safe_args = [] safe_kwargs = {} - used_ids = list(self.tasks.keys()) - task_id = 1 - while task_id in used_ids: - task_id += 1 # Check that args and kwargs contain picklable information for arg in args: @@ -183,17 +188,28 @@ class TaskHandler(object): else: safe_kwargs[key] = value - self.tasks[task_id] = (now + delta, callback, safe_args, safe_kwargs) + self.tasks[task_id] = (comp_time, callback, safe_args, safe_kwargs, True, None) self.save() - callback = self.do_task - args = [task_id] - kwargs = {} - deferLater(self.clock, timedelay, callback, *args, **kwargs) - return task_id + else: # this is a non-persitent task + self.tasks[task_id] = (comp_time, callback, args, kwargs, True, None) + # defer the task + callback = self.do_task + args = [task_id] + kwargs = {} d = deferLater(self.clock, timedelay, callback, *args, **kwargs) d.addErrback(handle_error) - return d + + # some tasks may complete before the deferal can be added + if task_id in self.tasks: + task = self.tasks.get(task_id) + task = list(task) + task[4] = persistent + task[5] = d + self.tasks[task_id] = task + else: # the task already completed + return False + return task_id def remove(self, task_id): """Remove a persistent task without executing it. @@ -206,7 +222,8 @@ class TaskHandler(object): in the TaskHandler. """ - del self.tasks[task_id] + if task_id in self.tasks: + del self.tasks[task_id] if task_id in self.to_save: del self.to_save[task_id] @@ -222,13 +239,30 @@ class TaskHandler(object): This will also remove it from the list of current tasks. """ - date, callback, args, kwargs = self.tasks.pop(task_id) + date, callback, args, kwargs, persistent, d = self.tasks.pop(task_id) + if task_id in self.to_save: del self.to_save[task_id] self.save() callback(*args, **kwargs) + def get_deferred(self, task_id): + """ + Return the instance of the deferred the task id is using. + + Args: + task_id (int): a valid task ID. + + Returns: + An instance of a deferral or False if there is no task with the id. + None is returned if there is no deferral affiliated with this id. + """ + if task_id in self.tasks: + return self.tasks[task_id][5] + else: + return False + def create_delays(self): """Create the delayed tasks for the persistent tasks. @@ -237,9 +271,14 @@ class TaskHandler(object): """ now = datetime.now() - for task_id, (date, callbac, args, kwargs) in self.tasks.items(): + for task_id, (date, callbac, args, kwargs, _, _) in self.tasks.items(): + self.tasks[task_id] = date, callbac, args, kwargs, True, None seconds = max(0, (date - now).total_seconds()) - deferLater(self.clock, seconds, self.do_task, task_id) + d = deferLater(self.clock, seconds, self.do_task, task_id) + d.addErrback(handle_error) + # some tasks may complete before the deferal can be added + if self.tasks.get(task_id, False): + self.tasks[task_id] = date, callbac, args, kwargs, True, d # Create the soft singleton diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 821e96aa24..42facf7e65 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -31,6 +31,7 @@ from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP from evennia.server.portal.mxp import Mxp, mxp_parse from evennia.utils import ansi from evennia.utils.utils import to_bytes +from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER _RE_N = re.compile(r"\|n$") _RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) @@ -127,8 +128,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): from evennia.utils.utils import delay - # timeout the handshakes in case the client doesn't reply at all - self._handshake_delay = delay(2, callback=self.handshake_done, timeout=True) + task_id = delay(2, callback=self.handshake_done, timeout=True) + self._handshake_delay = _TASK_HANDLER.get_deferred(task_id) # TCP/IP keepalive watches for dead links self.transport.setTcpKeepAlive(1) diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index bebea149ce..ae4d49e90f 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -323,24 +323,31 @@ class TestDelay(EvenniaTest): def test_delay(self): # get a reference of TASK_HANDLER + timedelay = 5 global _TASK_HANDLER if _TASK_HANDLER is None: from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER _TASK_HANDLER.clock = task.Clock() self.char1.ndb.dummy_var = False # test a persistent deferral, that completes after delay time - task_id = utils.delay(1, dummy_func, self.char1.dbref, persistent=True) - _TASK_HANDLER.clock.advance(1) # make time pass + task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test a non persisten deferral, that completes after delay time. - deferal_inst = utils.delay(1, dummy_func, self.char1.dbref) - _TASK_HANDLER.clock.advance(1) # make time pass + utils.delay(timedelay, dummy_func, self.char1.dbref) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.char1.ndb.dummy_var = False + # test a non persisten deferral, with a short timedelay + utils.delay(.1, dummy_func, self.char1.dbref) + _TASK_HANDLER.clock.advance(.1) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test canceling a deferral. - deferal_inst = utils.delay(1, dummy_func, self.char1.dbref) + task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) + deferal_inst = _TASK_HANDLER.get_deferred(task_id) deferal_inst.cancel() - _TASK_HANDLER.clock.advance(1) # make time pass + _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) self.char1.ndb.dummy_var = False From b17e47319f891d21fb8381a9c1704a59f4bf92ca Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sun, 18 Apr 2021 08:51:18 -0400 Subject: [PATCH 15/71] TaskHandler.remove() made functional TaskHandler.remove method now functions. Previous it would have removed the task from the TaskHandler.tasks dictionary, but never canceled the task. Making the "remove a persistent task without executing it" incorrect. Previous there was no method to get a persistent tasks's deferral instance, which was likely why TaskHandler.remove was not used within the module. Added unit tests to test TaskHandler.remove --- evennia/scripts/taskhandler.py | 27 ++++++++++++++++++++------- evennia/utils/tests/test_utils.py | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index b865a74185..2eb8623670 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -212,22 +212,35 @@ class TaskHandler(object): return task_id def remove(self, task_id): - """Remove a persistent task without executing it. + """ + Remove a task without executing it. + Deletes the instance of the task's deferral. Args: task_id (int): an existing task ID. - Note: - A non-persistent task doesn't have a task_id, it is not stored - in the TaskHandler. + Returns: + True (bool), if the removal completed successfully. + None, if there was a raise error """ + d = None + # delete the task from the tasks dictionary if task_id in self.tasks: - del self.tasks[task_id] + # if the task has not been run, cancel it + d = self.get_deferred(task_id) + if d: # it is remotely possible for a task to not have a deferral + if not d.called: + d.cancel() + del self.tasks[task_id] # delete the task from the tasks dictionary + # remove the task from the persistent dictionary and ServerConfig if task_id in self.to_save: del self.to_save[task_id] - - self.save() + self.save() # remove from ServerConfig.objects + # delete the instance of the deferral + if d: + del d + return True def do_task(self, task_id): """Execute the task (call its callback). diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index ae4d49e90f..6d9ae97d04 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -351,3 +351,17 @@ class TestDelay(EvenniaTest): _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) self.char1.ndb.dummy_var = False + # test removing an active task + task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) + success = _TASK_HANDLER.remove(task_id) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, False) + self.char1.ndb.dummy_var = False + # test removing a canceled active task + task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) + deferal_inst = _TASK_HANDLER.get_deferred(task_id) + deferal_inst.cancel() + success = _TASK_HANDLER.remove(task_id) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, False) + self.char1.ndb.dummy_var = False From 5209a31d350e1c3f36767269569d57d1c36305aa Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sun, 18 Apr 2021 09:50:54 -0400 Subject: [PATCH 16/71] TaskHandler cancel and exists method's created Created task handler methods to cancel a task and check if a task exists. Modified unit tests to use these and test methods. unit test test_delay passes. --- evennia/scripts/taskhandler.py | 60 +++++++++++++++++++++++++++---- evennia/utils/tests/test_utils.py | 10 ++++-- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 2eb8623670..d00b4cd91e 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -211,6 +211,56 @@ class TaskHandler(object): return False return task_id + def exists(self, task_id): + """ + Test if a task exists. + + Args: + task_id (int): an existing task ID. + + Returns: + True (bool): if the task exists. + False (bool): if the task does not exist. + + Note: + Most task handler methods check for existence for you. + """ + if task_id in self.tasks: + return True + else: + return False + + + def cancel(self, task_id): + """ + Stop a task from automatically executing. + This will not remove the task. + + Args: + task_id (int): an existing task ID. + + Returns: + True (bool): if the removal completed successfully. + False (bool): if the task: + does not exist, + has already run, + does not have a deferral instance created for the task. + None, if there was a raised exception + """ + if task_id in self.tasks: + # if the task has not been run, cancel it + d = self.get_deferred(task_id) + if d: # it is remotely possible for a task to not have a deferral + if d.called: + return False + else: # the callback has not been called yet. + d.cancel() + return True + else: # this task has no deferral + return False + else: + return False + def remove(self, task_id): """ Remove a task without executing it. @@ -220,18 +270,16 @@ class TaskHandler(object): task_id (int): an existing task ID. Returns: - True (bool), if the removal completed successfully. - None, if there was a raise error + True (bool): if the removal completed successfully or if the a + task with the id does not exist. + None, if there was a raised exception """ d = None # delete the task from the tasks dictionary if task_id in self.tasks: # if the task has not been run, cancel it - d = self.get_deferred(task_id) - if d: # it is remotely possible for a task to not have a deferral - if not d.called: - d.cancel() + self.cancel(task_id) del self.tasks[task_id] # delete the task from the tasks dictionary # remove the task from the persistent dictionary and ServerConfig if task_id in self.to_save: diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 6d9ae97d04..60e85e34a7 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -345,9 +345,11 @@ class TestDelay(EvenniaTest): self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test canceling a deferral. + # after this the task_id 1 remains used by this canceled but unused task task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) - deferal_inst = _TASK_HANDLER.get_deferred(task_id) - deferal_inst.cancel() + success = _TASK_HANDLER.cancel(task_id) + self.assertTrue(success) + self.assertTrue(_TASK_HANDLER.exists(task_id)) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) self.char1.ndb.dummy_var = False @@ -356,6 +358,7 @@ class TestDelay(EvenniaTest): success = _TASK_HANDLER.remove(task_id) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) + self.assertFalse(_TASK_HANDLER.exists(task_id)) self.char1.ndb.dummy_var = False # test removing a canceled active task task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) @@ -364,4 +367,7 @@ class TestDelay(EvenniaTest): success = _TASK_HANDLER.remove(task_id) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) + self.assertFalse(_TASK_HANDLER.exists(task_id)) self.char1.ndb.dummy_var = False + # restart condictions + # cancel a diferall directly, without calling task handler's cancel From 21a1c4395ecc557bcf4fbf0eaaedeefa9b1d400a Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sun, 18 Apr 2021 09:59:45 -0400 Subject: [PATCH 17/71] TaskHandler.active method created TaskHandler.active method created to check if a task is currently active. test_delay unit test passes. --- evennia/scripts/taskhandler.py | 26 ++++++++++++++++++++++++++ evennia/utils/tests/test_utils.py | 9 +++++++++ 2 files changed, 35 insertions(+) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index d00b4cd91e..6c648b3360 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -230,6 +230,32 @@ class TaskHandler(object): else: return False + def active(self, task_id): + """ + Check if a task is active (has not been called yet). + + Args: + task_id (int): an existing task ID. + + Returns: + True (bool): If a task is active (has not been called yet). + False (bool): if the task + is not active (has already been called), + does not exist + """ + if task_id in self.tasks: + # if the task has not been run, cancel it + d = self.get_deferred(task_id) + if d: # it is remotely possible for a task to not have a deferral + if d.called: + return False + else: # the callback has not been called yet. + return True + else: # this task has no deferral, and could not have been called + return True + else: + return False + def cancel(self, task_id): """ diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 60e85e34a7..c5bf20d032 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -331,23 +331,28 @@ class TestDelay(EvenniaTest): self.char1.ndb.dummy_var = False # test a persistent deferral, that completes after delay time task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + self.assertTrue(_TASK_HANDLER.active(task_id)) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test a non persisten deferral, that completes after delay time. utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(_TASK_HANDLER.active(task_id)) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test a non persisten deferral, with a short timedelay utils.delay(.1, dummy_func, self.char1.dbref) + self.assertTrue(_TASK_HANDLER.active(task_id)) _TASK_HANDLER.clock.advance(.1) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test canceling a deferral. # after this the task_id 1 remains used by this canceled but unused task task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(_TASK_HANDLER.active(task_id)) success = _TASK_HANDLER.cancel(task_id) + self.assertFalse(_TASK_HANDLER.active(task_id)) self.assertTrue(success) self.assertTrue(_TASK_HANDLER.exists(task_id)) _TASK_HANDLER.clock.advance(timedelay) # make time pass @@ -355,15 +360,19 @@ class TestDelay(EvenniaTest): self.char1.ndb.dummy_var = False # test removing an active task task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(_TASK_HANDLER.active(task_id)) success = _TASK_HANDLER.remove(task_id) + self.assertFalse(_TASK_HANDLER.active(task_id)) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) self.assertFalse(_TASK_HANDLER.exists(task_id)) self.char1.ndb.dummy_var = False # test removing a canceled active task task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(_TASK_HANDLER.active(task_id)) deferal_inst = _TASK_HANDLER.get_deferred(task_id) deferal_inst.cancel() + self.assertFalse(_TASK_HANDLER.active(task_id)) success = _TASK_HANDLER.remove(task_id) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) From e24dbc322ba9824c2eb1f8eb307f351cfc4169df Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sun, 18 Apr 2021 11:01:45 -0400 Subject: [PATCH 18/71] TaskHandler.do_task is now state aware & can return callback's return TaskHandler.do_task is now state aware and can be called manually. It can now return the callbacks returns. added unit tests to verify early callback is functional. Both persistent and non-persistent tasks. All evennia unit tests pass. --- evennia/scripts/taskhandler.py | 40 ++++++++++++++++++++++--------- evennia/utils/tests/test_utils.py | 21 +++++++++++++++- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 6c648b3360..eceb61a1cf 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -256,7 +256,6 @@ class TaskHandler(object): else: return False - def cancel(self, task_id): """ Stop a task from automatically executing. @@ -298,7 +297,7 @@ class TaskHandler(object): Returns: True (bool): if the removal completed successfully or if the a task with the id does not exist. - None, if there was a raised exception + None: if there was a raised exception """ d = None @@ -317,22 +316,41 @@ class TaskHandler(object): return True def do_task(self, task_id): - """Execute the task (call its callback). + """ + Execute the task (call its callback). + If calling before timedelay cancel the deferral affliated to this task. + Remove the task from the dictionary of current tasks on a successful + callback. Args: task_id (int): a valid task ID. + Returns: + False (bool): if the: + task no longer exists, + has no affliated instance of deferral + The return of the callback passed on task creation. + This makes it possible for the callback to also return False + None: if there was a raised exception + Note: - This will also remove it from the list of current tasks. + On a successful call the task will be removed from the dictionary + of current tasks. """ - date, callback, args, kwargs, persistent, d = self.tasks.pop(task_id) - - if task_id in self.to_save: - del self.to_save[task_id] - - self.save() - callback(*args, **kwargs) + callback_return = False + if task_id in self.tasks: + date, callback, args, kwargs, persistent, d = self.tasks.get(task_id) + else: # the task does not exist + return False + if d: # it is remotely possible for a task to not have a deferral + if not d.called: # the task has not been called yet + d.cancel() # cancel the automated callback + else: # this task has no deferral, and should not be called + return False + callback_return = callback(*args, **kwargs) + self.remove(task_id) + return callback_return def get_deferred(self, task_id): """ diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index c5bf20d032..8de5ef1900 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -314,6 +314,7 @@ def dummy_func(obj): obj = obj[0] # make changes to object obj.ndb.dummy_var = 'dummy_func ran' + return True class TestDelay(EvenniaTest): @@ -335,12 +336,30 @@ class TestDelay(EvenniaTest): _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False + # test a persistent deferral, that completes on a manual call + task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + self.assertTrue(_TASK_HANDLER.active(task_id)) + result = _TASK_HANDLER.do_task(task_id) + self.assertTrue(result) + self.assertFalse(_TASK_HANDLER.exists(task_id)) + _TASK_HANDLER.clock.advance(timedelay) # make time pass, important keep + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.char1.ndb.dummy_var = False # test a non persisten deferral, that completes after delay time. utils.delay(timedelay, dummy_func, self.char1.dbref) self.assertTrue(_TASK_HANDLER.active(task_id)) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False + # test a non-persistent deferral, that completes on a manual call + task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(_TASK_HANDLER.active(task_id)) + result = _TASK_HANDLER.do_task(task_id) + self.assertTrue(result) + self.assertFalse(_TASK_HANDLER.exists(task_id)) + _TASK_HANDLER.clock.advance(timedelay) # make time pass, important keep + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.char1.ndb.dummy_var = False # test a non persisten deferral, with a short timedelay utils.delay(.1, dummy_func, self.char1.dbref) self.assertTrue(_TASK_HANDLER.active(task_id)) @@ -367,7 +386,7 @@ class TestDelay(EvenniaTest): self.assertEqual(self.char1.ndb.dummy_var, False) self.assertFalse(_TASK_HANDLER.exists(task_id)) self.char1.ndb.dummy_var = False - # test removing a canceled active task + # test removing a canceled task task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) self.assertTrue(_TASK_HANDLER.active(task_id)) deferal_inst = _TASK_HANDLER.get_deferred(task_id) From e65724e2a7c0cf865f99912f6117d1c00ba11951 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sun, 18 Apr 2021 18:25:39 -0400 Subject: [PATCH 19/71] taskhandler.Task created Created an object to represent a task. This allows for the return of TASK_HANDLER.add or utils.delay to be an object that has callable methods. It has been created to mock the most common methods and attributes of a twisted deferred object. Changed test_utils.test_delay for new usage. Returned previously changed modules slow_exit, tutorial_world.objects and portal.telnet to their previous states. As the return of utils.delay can be used as if it were a deferred. All evennia unit tests pass --- evennia/contrib/slow_exit.py | 5 +- evennia/contrib/tests.py | 6 + evennia/contrib/tutorial_world/objects.py | 7 +- evennia/scripts/taskhandler.py | 149 +++++++++++++++++++++- evennia/server/portal/telnet.py | 5 +- evennia/utils/tests/test_utils.py | 76 ++++++----- 6 files changed, 206 insertions(+), 42 deletions(-) diff --git a/evennia/contrib/slow_exit.py b/evennia/contrib/slow_exit.py index 3b060f7471..7f7f2cc249 100644 --- a/evennia/contrib/slow_exit.py +++ b/evennia/contrib/slow_exit.py @@ -36,7 +36,6 @@ TickerHandler might be better. """ from evennia import DefaultExit, utils, Command -from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER MOVE_DELAY = {"stroll": 6, "walk": 4, "run": 2, "sprint": 1} @@ -71,11 +70,11 @@ class SlowExit(DefaultExit): traversing_object.msg("You start moving %s at a %s." % (self.key, move_speed)) # create a delayed movement - task_id = utils.delay(move_delay, move_callback) + t = utils.delay(move_delay, move_callback) # we store the deferred on the character, this will allow us # to abort the movement. We must use an ndb here since # deferreds cannot be pickled. - traversing_object.ndb.currently_moving = _TASK_HANDLER.get_deferred(task_id) + traversing_object.ndb.currently_moving = t # diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 7b69d07cc9..a4e50a29f7 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -1152,7 +1152,13 @@ from evennia.contrib import slow_exit slow_exit.MOVE_DELAY = {"stroll": 0, "walk": 0, "run": 0, "sprint": 0} +def _cancellable_mockdelay(time, callback, *args, **kwargs): + callback(*args, **kwargs) + return Mock() + + class TestSlowExit(CommandTest): + @patch("evennia.utils.delay", _cancellable_mockdelay) def test_exit(self): exi = create_object( slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2 diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index 759d1f1dc0..ca4147a715 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -24,7 +24,6 @@ import random from evennia import DefaultObject, DefaultExit, Command, CmdSet from evennia.utils import search, delay, dedent from evennia.prototypes.spawner import spawn -from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER # ------------------------------------------------------------- # @@ -390,8 +389,7 @@ class LightSource(TutorialObject): # start the burn timer. When it runs out, self._burnout # will be called. We store the deferred so it can be # killed in unittesting. - task_id = delay(60 * 3, self._burnout) - self.deferred = _TASK_HANDLER.get_deferred(task_id) + self.deferred = delay(60 * 3, self._burnout) return True @@ -689,8 +687,7 @@ class CrumblingWall(TutorialObject, DefaultExit): self.db.exit_open = True # start a 45 second timer before closing again. We store the deferred so it can be # killed in unittesting. - task_id = delay(45, self.reset) - self.deferred = _TASK_HANDLER.get_deferred(task_id) + self.deferred = delay(45, self.reset) return True def _translate_position(self, root, ipos): diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index eceb61a1cf..9ccb0ea307 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -209,7 +209,7 @@ class TaskHandler(object): self.tasks[task_id] = task else: # the task already completed return False - return task_id + return Task(task_id) def exists(self, task_id): """ @@ -388,3 +388,150 @@ class TaskHandler(object): # Create the soft singleton TASK_HANDLER = TaskHandler() + + +class Task: + """ + A light + """ + def __init__(self, task_id): + self.task_id = task_id + + def get_deferred(self): + """ + Return the instance of the deferred the task id is using. + + Returns: + An instance of a deferral or False if there is no task with the id. + None is returned if there is no deferral affiliated with this id. + """ + return TASK_HANDLER.get_deferred(self.task_id) + + def pause(self): + """ + Pause the callback of a task. + To resume use Task.unpause + """ + d = TASK_HANDLER.get_deferred(self.task_id) + if d: + d.pause() + + def unpause(self): + """ + Process all callbacks made since pause() was called. + """ + d = TASK_HANDLER.get_deferred(self.task_id) + if d: + d.unpause() + + @property + def paused(self): + """ + A task attribute to check if the deferral of a task has been paused. + + This exists to mock usage of a twisted deferred object. + + This will return None if the deferred object for the task does not + exist or if the task no longer exists. + """ + d = TASK_HANDLER.get_deferred(self.task_id) + if d: + return d.paused + else: + return None + + def do_task(self): + """ + Execute the task (call its callback). + If calling before timedelay cancel the deferral affliated to this task. + Remove the task from the dictionary of current tasks on a successful + callback. + + Returns: + False (bool): if the: + task no longer exists, + has no affliated instance of deferral + The return of the callback passed on task creation. + This makes it possible for the callback to also return False + None: if there was a raised exception + + Note: + On a successful call the task will be removed from the dictionary + of current tasks. + + """ + return TASK_HANDLER.do_task(self.task_id) + + def remove(self): + """ + Remove a task without executing it. + Deletes the instance of the task's deferral. + + Returns: + True (bool): if the removal completed successfully or if the a + task with the id does not exist. + None: if there was a raised exception + + """ + return TASK_HANDLER.remove(self.task_id) + + def cancel(self): + """ + Stop a task from automatically executing. + This will not remove the task. + + Returns: + True (bool): if the removal completed successfully. + False (bool): if the task: + does not exist, + has already run, + does not have a deferral instance created for the task. + None, if there was a raised exception + """ + return TASK_HANDLER.cancel(self.task_id) + + def active(self): + """ + Check if a task is active (has not been called yet). + + Returns: + True (bool): If a task is active (has not been called yet). + False (bool): if the task + is not active (has already been called), + does not exist + """ + return TASK_HANDLER.active(self.task_id) + + @property + def called(self): + """ + A task attribute to check if the deferral of a task has been called. + + This exists to mock usage of a twisted deferred object. + It will not set to false if Task.call has been called. + + """ + return not TASK_HANDLER.active(self.task_id) + + def exists(self): + """ + Test if a task exists. + + Returns: + True (bool): if the task exists. + False (bool): if the task does not exist. + + Note: + Most task handler methods check for existence for you. + """ + return TASK_HANDLER.exists(self.task_id) + + def get_id(self): + """ + Returns the global id for this task. For use with + `evennia.scripts.taskhandler.TASK_HANDLER`. + + Returns: + task_id (int): global task id for this task. + """ + return self.task_id diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 42facf7e65..821e96aa24 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -31,7 +31,6 @@ from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP from evennia.server.portal.mxp import Mxp, mxp_parse from evennia.utils import ansi from evennia.utils.utils import to_bytes -from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER _RE_N = re.compile(r"\|n$") _RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) @@ -128,8 +127,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): from evennia.utils.utils import delay - task_id = delay(2, callback=self.handshake_done, timeout=True) - self._handshake_delay = _TASK_HANDLER.get_deferred(task_id) + # timeout the handshakes in case the client doesn't reply at all + self._handshake_delay = delay(2, callback=self.handshake_done, timeout=True) # TCP/IP keepalive watches for dead links self.transport.setTcpKeepAlive(1) diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 8de5ef1900..9bf252d0a8 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -331,71 +331,87 @@ class TestDelay(EvenniaTest): _TASK_HANDLER.clock = task.Clock() self.char1.ndb.dummy_var = False # test a persistent deferral, that completes after delay time - task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - self.assertTrue(_TASK_HANDLER.active(task_id)) + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + self.assertTrue(_TASK_HANDLER.active(t.get_id())) # test Task.get_id + self.assertTrue(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertTrue(t.called) # test Task.called property self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test a persistent deferral, that completes on a manual call - task_id = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - self.assertTrue(_TASK_HANDLER.active(task_id)) - result = _TASK_HANDLER.do_task(task_id) + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + self.assertTrue(t.active()) + result = t.do_task() self.assertTrue(result) - self.assertFalse(_TASK_HANDLER.exists(task_id)) + self.assertFalse(t.exists()) _TASK_HANDLER.clock.advance(timedelay) # make time pass, important keep self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test a non persisten deferral, that completes after delay time. - utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(_TASK_HANDLER.active(task_id)) + t = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test a non-persistent deferral, that completes on a manual call - task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(_TASK_HANDLER.active(task_id)) - result = _TASK_HANDLER.do_task(task_id) + t = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(t.active()) + result = t.do_task() self.assertTrue(result) - self.assertFalse(_TASK_HANDLER.exists(task_id)) + self.assertFalse(t.exists()) _TASK_HANDLER.clock.advance(timedelay) # make time pass, important keep self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test a non persisten deferral, with a short timedelay - utils.delay(.1, dummy_func, self.char1.dbref) - self.assertTrue(_TASK_HANDLER.active(task_id)) + t = utils.delay(.1, dummy_func, self.char1.dbref) + self.assertTrue(t.active()) _TASK_HANDLER.clock.advance(.1) # make time pass self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # test canceling a deferral. # after this the task_id 1 remains used by this canceled but unused task - task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(_TASK_HANDLER.active(task_id)) - success = _TASK_HANDLER.cancel(task_id) - self.assertFalse(_TASK_HANDLER.active(task_id)) + t = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(t.active()) + success = t.cancel() + self.assertFalse(t.active()) self.assertTrue(success) - self.assertTrue(_TASK_HANDLER.exists(task_id)) + self.assertTrue(t.exists()) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) self.char1.ndb.dummy_var = False # test removing an active task - task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(_TASK_HANDLER.active(task_id)) - success = _TASK_HANDLER.remove(task_id) - self.assertFalse(_TASK_HANDLER.active(task_id)) + t = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(t.active()) + success = t.remove() + self.assertFalse(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) - self.assertFalse(_TASK_HANDLER.exists(task_id)) + self.assertFalse(t.exists()) self.char1.ndb.dummy_var = False # test removing a canceled task - task_id = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(_TASK_HANDLER.active(task_id)) - deferal_inst = _TASK_HANDLER.get_deferred(task_id) + t = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(t.active()) + deferal_inst = t.get_deferred() deferal_inst.cancel() - self.assertFalse(_TASK_HANDLER.active(task_id)) - success = _TASK_HANDLER.remove(task_id) + self.assertFalse(t.active()) + success = t.remove() _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertEqual(self.char1.ndb.dummy_var, False) - self.assertFalse(_TASK_HANDLER.exists(task_id)) + self.assertFalse(t.exists()) + self.char1.ndb.dummy_var = False + # test pause, paused and unpause + t = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(t.active()) + t.pause() + self.assertTrue(t.paused) + t.unpause() + self.assertFalse(t.paused) + self.assertEqual(self.char1.ndb.dummy_var, False) + t.pause() + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, False) + t.unpause() + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False # restart condictions # cancel a diferall directly, without calling task handler's cancel From a61577b8f307a394bba2a3ba3efda1fe69aebf98 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Mon, 19 Apr 2021 09:37:19 -0400 Subject: [PATCH 20/71] task handler automatic stale task cleanup Task handler will automatically remove uncalled but canceled from task handler. By default this will not occur until a canceled task has been uncalled for 60 second after the time it should have been called. To adjust this time use TASK_HANDLER.stale_timeout. If stale_timeout is 0 stale tasks will not be automatically removed. This is not done on a timer. I is done as new tasks are added or the load method is called. Added unit tests to test automatic removal. Including when it should not automatically removed. Both when it is too soon, or when the stale_timeout attribute is set to 0. --- evennia/scripts/taskhandler.py | 82 ++++++++++++++++++++++++------- evennia/utils/tests/test_utils.py | 53 +++++++++++++++++++- 2 files changed, 116 insertions(+), 19 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 9ccb0ea307..214d1e3f8a 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -32,18 +32,13 @@ class TaskHandler(object): When `utils.delay` is called, the task handler is used to create the task. - If `utils.delay` is called with `persistent=True`. The - task handler creates the task. It saves the delay time, callback, - arguments and kwa to the database. Each of these variables are - serialized to do this. - It's easier to access these tasks (should it be necessary) using - `evennia.scripts.taskhandler.TASK_HANDLER`, which contains one - instance of this class, and use its `add` and `remove` methods. - - Dev notes: - deferLater creates an instance of IDelayedCall using reactor.callLater. - deferLater uses the cancel method on the IDelayedCall instance to create + Task handler will automatically remove uncalled but canceled from task + handler. By default this will not occur until a canceled task + has been uncalled for 60 second after the time it should have been called. + To adjust this time use TASK_HANDLER.stale_timeout. If stale_timeout is 0 + stale tasks will not be automatically removed. + This is not done on a timer. I is done as new tasks are added or the load method is called. """ @@ -51,6 +46,9 @@ class TaskHandler(object): self.tasks = {} self.to_save = {} self.clock = reactor + # number of seconds before an uncalled canceled task is removed from TaskHandler + self.stale_timeout = 60 + self._now = False # used in unit testing to manually set now time def load(self): """Load from the ServerConfig. @@ -80,11 +78,37 @@ class TaskHandler(object): callback = getattr(obj, method) self.tasks[task_id] = date, callback, args, kwargs, True, None + if self.stale_timeout > 0: # cleanup stale tasks. + self.clean_stale_tasks() if to_save: self.save() + def clean_stale_tasks(self): + """ + remove uncalled but canceled from task handler. + + By default this will not occur until a canceled task + has been uncalled for 60 second after the time it should have been called. + To adjust this time use TASK_HANDLER.stale_timeout. + """ + clean_ids = [] + for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): + if not self.active(task_id): + stale_date = date + timedelta(seconds=self.stale_timeout) + # if a now time is provided use it (intended for unit testing) + now = self._now if self._now else datetime.now() + # the task was canceled more than stale_timeout seconds ago + if now > stale_date: + clean_ids.append(task_id) + for task_id in clean_ids: + self.remove(task_id) + return True + def save(self): - """Save the tasks in ServerConfig.""" + """ + Save the tasks in ServerConfig. + """ + for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): if task_id in self.to_save: continue @@ -209,6 +233,8 @@ class TaskHandler(object): self.tasks[task_id] = task else: # the task already completed return False + if self.stale_timeout > 0: + self.clean_stale_tasks() return Task(task_id) def exists(self, task_id): @@ -392,10 +418,28 @@ TASK_HANDLER = TaskHandler() class Task: """ - A light + A object to represent a single TaskHandler task. + + Instance Attributes: + task_id (int): the global id for this task + deferred (deferred): a reference to this task's deferred + Propert Attributes: + paused (bool): check if the deferral of a task has been paused. + called(self): A task attribute to check if the deferral of a task has been called. + + Methods: + pause(): Pause the callback of a task. + unpause(): Process all callbacks made since pause() was called. + do_task(): Execute the task (call its callback). + remove(): Remove a task without executing it. + cancel(): Stop a task from automatically executing. + active(): Check if a task is active (has not been called yet). + exists(): Check if a task exists. + get_id(): Returns the global id for this task. For use with """ def __init__(self, task_id): self.task_id = task_id + self.deferred = TASK_HANDLER.get_deferred(task_id) def get_deferred(self): """ @@ -412,7 +456,7 @@ class Task: Pause the callback of a task. To resume use Task.unpause """ - d = TASK_HANDLER.get_deferred(self.task_id) + d = self.deferred if d: d.pause() @@ -420,7 +464,7 @@ class Task: """ Process all callbacks made since pause() was called. """ - d = TASK_HANDLER.get_deferred(self.task_id) + d = self.deferred if d: d.unpause() @@ -434,7 +478,7 @@ class Task: This will return None if the deferred object for the task does not exist or if the task no longer exists. """ - d = TASK_HANDLER.get_deferred(self.task_id) + d = self.deferred if d: return d.paused else: @@ -511,7 +555,11 @@ class Task: It will not set to false if Task.call has been called. """ - return not TASK_HANDLER.active(self.task_id) + d = self.deferred + if d: + return d.called + else: + return None def exists(self): """ diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 9bf252d0a8..53ec661521 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -7,6 +7,7 @@ TODO: Not nearly all utilities are covered yet. import os.path import mock +from datetime import datetime, timedelta from django.test import TestCase from datetime import datetime @@ -413,5 +414,53 @@ class TestDelay(EvenniaTest): t.unpause() self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.char1.ndb.dummy_var = False - # restart condictions - # cancel a diferall directly, without calling task handler's cancel + # test automated removal of stale tasks. + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + t.cancel() + task_id = t.get_id() + self.assertFalse(t.active()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertTrue(task_id in _TASK_HANDLER.to_save) + self.assertTrue(task_id in _TASK_HANDLER.tasks) + # add a task to test automatic removal + _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + 6) # task handler time to 6 seconds after stale timeout + t2 = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + self.assertFalse(task_id in _TASK_HANDLER.to_save) + self.assertFalse(task_id in _TASK_HANDLER.tasks) + self.assertEqual(self.char1.ndb.dummy_var, False) + # test manual cleanup + t2.cancel() + _TASK_HANDLER.clock.advance(timedelay) # advance twisted reactor time past callback time + _TASK_HANDLER._now = datetime.now() + timedelta(seconds=30) # set TaskHandler's time to 30 seconnds from now + task_id = t2.get_id() + # test before stale_timeout time + _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method + # still in the task handler because stale timeout has not been reached + self.assertTrue(task_id in _TASK_HANDLER.to_save) + self.assertTrue(task_id in _TASK_HANDLER.tasks) + # advance past stale timeout + _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + 6) # task handler time to 6 seconds after stale timeout + _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method + self.assertFalse(task_id in _TASK_HANDLER.to_save) + self.assertFalse(task_id in _TASK_HANDLER.tasks) + self.char1.ndb.dummy_var = False + _TASK_HANDLER._now = False + # if _TASK_HANDLER.stale_timeout is 0 or less, automatic cleanup should not run + _TASK_HANDLER.stale_timeout = 0 + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + t.cancel() + self.assertFalse(t.active()) + _TASK_HANDLER.clock.advance(timedelay) # advance twisted's reactor past callback time + self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) + self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) + # add a task to test automatic removal + _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + 6) # task handler time to 6 seconds after stale timeout + t2 = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) + self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) + self.assertEqual(self.char1.ndb.dummy_var, False) + t.remove() + t2.remove() + self.char1.ndb.dummy_var = False + _TASK_HANDLER._now = False + # replicate a restart From 58308adea6e8e2b18339b0ec802958719f3e2def Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Mon, 19 Apr 2021 17:16:37 -0400 Subject: [PATCH 21/71] task handler remove_all method created. task handler server restart unit test created. Added method remove_all to task handler. (intended for unit testing) Created a method to mimic a server restart for the purpose of task handler. test_delay unit test passes. --- evennia/scripts/taskhandler.py | 24 ++++++++++++++++++++ evennia/utils/tests/test_utils.py | 37 +++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 214d1e3f8a..c9233adbd8 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -341,6 +341,30 @@ class TaskHandler(object): del d return True + def remove_all(self, save=True, cancel=True): + """ + Remove all tasks. + By default tasks are canceled and removed from the database also. + + Arguments: + save=True (bool): Should changes to persistent tasks be saved to database. + cancel=True (bool): Cancel scheduled tasks before removing it from task handler. + + Returns: + True (bool): if the removal completed successfully. + """ + tasks_ids = tuple(self.tasks.keys()) + for task_id in tasks_ids: + if cancel: + self.cancel(task_id) + del self.tasks[task_id] + tasks_ids = tuple(self.to_save.keys()) + for task_id in tasks_ids: + del self.to_save[task_id] + if save: + self.save() + return True + def do_task(self, task_id): """ Execute the task (call its callback). diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 53ec661521..5c71fe19bf 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -417,32 +417,30 @@ class TestDelay(EvenniaTest): # test automated removal of stale tasks. t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) t.cancel() - task_id = t.get_id() self.assertFalse(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertTrue(task_id in _TASK_HANDLER.to_save) - self.assertTrue(task_id in _TASK_HANDLER.tasks) + self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) + self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) # add a task to test automatic removal _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + 6) # task handler time to 6 seconds after stale timeout t2 = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - self.assertFalse(task_id in _TASK_HANDLER.to_save) - self.assertFalse(task_id in _TASK_HANDLER.tasks) + self.assertFalse(t.get_id() in _TASK_HANDLER.to_save) + self.assertFalse(t.get_id() in _TASK_HANDLER.tasks) self.assertEqual(self.char1.ndb.dummy_var, False) # test manual cleanup t2.cancel() _TASK_HANDLER.clock.advance(timedelay) # advance twisted reactor time past callback time _TASK_HANDLER._now = datetime.now() + timedelta(seconds=30) # set TaskHandler's time to 30 seconnds from now - task_id = t2.get_id() # test before stale_timeout time _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method # still in the task handler because stale timeout has not been reached - self.assertTrue(task_id in _TASK_HANDLER.to_save) - self.assertTrue(task_id in _TASK_HANDLER.tasks) + self.assertTrue(t2.get_id() in _TASK_HANDLER.to_save) + self.assertTrue(t2.get_id() in _TASK_HANDLER.tasks) # advance past stale timeout _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + 6) # task handler time to 6 seconds after stale timeout _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method - self.assertFalse(task_id in _TASK_HANDLER.to_save) - self.assertFalse(task_id in _TASK_HANDLER.tasks) + self.assertFalse(t2.get_id() in _TASK_HANDLER.to_save) + self.assertFalse(t2.get_id() in _TASK_HANDLER.tasks) self.char1.ndb.dummy_var = False _TASK_HANDLER._now = False # if _TASK_HANDLER.stale_timeout is 0 or less, automatic cleanup should not run @@ -459,8 +457,23 @@ class TestDelay(EvenniaTest): self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) self.assertEqual(self.char1.ndb.dummy_var, False) - t.remove() - t2.remove() + _TASK_HANDLER.remove_all() self.char1.ndb.dummy_var = False _TASK_HANDLER._now = False # replicate a restart + _TASK_HANDLER.remove_all() + _TASK_HANDLER.save() + self.assertFalse(_TASK_HANDLER.tasks) + self.assertFalse(_TASK_HANDLER.to_save) + # create a persistent task. + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + _TASK_HANDLER.save() + _TASK_HANDLER.remove_all(False) # remove all tasks, do not save this change. + _TASK_HANDLER.clock.advance(timedelay) # advance twisted reactor time past callback time + self.assertEqual(self.char1.ndb.dummy_var, False) # task has not run + _TASK_HANDLER.load() + _TASK_HANDLER.create_delays() + _TASK_HANDLER.clock.advance(timedelay) # Clock must advance to trigger, even if past timedelay + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + _TASK_HANDLER.remove_all() + self.char1.ndb.dummy_var = False From 33a2d6d35e71ca318e205131e90f645b45b754a9 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Mon, 19 Apr 2021 17:36:42 -0400 Subject: [PATCH 22/71] task handler call_task, Task.call methods created task handler call_task, Task.call methods created Added unit tests for these methods. All evennia unit tests pass --- evennia/scripts/taskhandler.py | 36 ++++++++++++++++++++++++++++++- evennia/utils/tests/test_utils.py | 6 ++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index c9233adbd8..c0905a5e09 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -365,6 +365,26 @@ class TaskHandler(object): self.save() return True + def call_task(self, task_id): + """ + Call the callback of a task. + Leave the task unaffected otherwise. + This does not use the task's deferred instance. + The only requirement is that the task exist in task handler. + + Args: + task_id (int): an existing task ID. + + Returns: + False (bool): if the task does not exist in task handler. + ?: The return of the task's callback. + """ + if task_id in self.tasks: + date, callback, args, kwargs, persistent, d = self.tasks.get(task_id) + else: # the task does not exist + return False + return callback(*args, **kwargs) + def do_task(self, task_id): """ Execute the task (call its callback). @@ -394,7 +414,7 @@ class TaskHandler(object): else: # the task does not exist return False if d: # it is remotely possible for a task to not have a deferral - if not d.called: # the task has not been called yet + if not d.called: # the task's deferred has not been called yet d.cancel() # cancel the automated callback else: # this task has no deferral, and should not be called return False @@ -455,6 +475,7 @@ class Task: pause(): Pause the callback of a task. unpause(): Process all callbacks made since pause() was called. do_task(): Execute the task (call its callback). + call(): Call the callback of this task. remove(): Remove a task without executing it. cancel(): Stop a task from automatically executing. active(): Check if a task is active (has not been called yet). @@ -530,6 +551,19 @@ class Task: """ return TASK_HANDLER.do_task(self.task_id) + def call(self): + """ + Call the callback of this task. + Leave the task unaffected otherwise. + This does not use the task's deferred instance. + The only requirement is that the task exist in task handler. + + Returns: + False (bool): if the task does not exist in task handler. + ?: The return of the task's callback. + """ + return TASK_HANDLER.call_task(self.task_id) + def remove(self): """ Remove a task without executing it. diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index 5c71fe19bf..fa67bc768f 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -333,8 +333,14 @@ class TestDelay(EvenniaTest): self.char1.ndb.dummy_var = False # test a persistent deferral, that completes after delay time t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + # call the task early to test Task.call and TaskHandler.call_task + result = t.call() + self.assertTrue(result) + del result + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') self.assertTrue(_TASK_HANDLER.active(t.get_id())) # test Task.get_id self.assertTrue(t.active()) + self.char1.ndb.dummy_var = False # Set variable to continue completion after delay time test. _TASK_HANDLER.clock.advance(timedelay) # make time pass self.assertTrue(t.called) # test Task.called property self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') From 8a64ee9830a411bea660fd95ff3af971373283ff Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:31:30 -0400 Subject: [PATCH 23/71] rpsystem.send_emote passes kwargs, uses sender as from_obj rpsystem.send_emote now passes kwargs to obj.msg. rpsystem.send_emote uses sender as from_obj when calling obj.msg All evennia unit tests pass. --- evennia/contrib/rpsystem.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/rpsystem.py b/evennia/contrib/rpsystem.py index 107d6c9c06..b5ebed2df7 100644 --- a/evennia/contrib/rpsystem.py +++ b/evennia/contrib/rpsystem.py @@ -480,7 +480,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False): return string, mapping -def send_emote(sender, receivers, emote, anonymous_add="first"): +def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): """ Main access function for distribute an emote. @@ -508,7 +508,9 @@ def send_emote(sender, receivers, emote, anonymous_add="first"): # we escape the object mappings since we'll do the language ones first # (the text could have nested object mappings). emote = _RE_REF.sub(r"{{#\1}}", emote) - + # if anonymous_add is passed as a kwarg, collect and remove it from kwargs + if 'anonymous_add' in kwargs: + anonymous_add = kwargs.pop('anonymous_add') if anonymous_add and not "#%i" % sender.id in obj_mapping: # no self-reference in the emote - add to the end key = "#%i" % sender.id @@ -566,7 +568,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first"): receiver_sdesc_mapping[rkey] = process_sdesc(receiver.key, receiver) # do the template replacement of the sdesc/recog {#num} markers - receiver.msg(sendemote.format(**receiver_sdesc_mapping)) + receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs) # ------------------------------------------------------------ From 4480bd6130ea362ba6d93837daed1e7ac163fb2a Mon Sep 17 00:00:00 2001 From: Ben Longden Date: Tue, 20 Apr 2021 22:30:10 +0100 Subject: [PATCH 24/71] Don't allow fuzzy match on db if exact match on module prototype --- evennia/prototypes/prototypes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 185b9dfc5d..a304dd8684 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -379,10 +379,12 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators else: mod_matches = _MODULE_PROTOTYPES + allow_fuzzy = True if key: if key in mod_matches: # exact match module_prototypes = [mod_matches[key]] + allow_fuzzy = False else: # fuzzy matching module_prototypes = [ @@ -406,7 +408,7 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators if key: # exact or partial match on key exact_match = db_matches.filter(Q(db_key__iexact=key)).order_by("db_key") - if not exact_match: + if not exact_match and allow_fuzzy: # try with partial match instead db_matches = db_matches.filter(Q(db_key__icontains=key)).order_by("db_key") else: @@ -423,7 +425,7 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators nmodules = len(module_prototypes) ndbprots = db_matches.count() if nmodules + ndbprots != 1: - raise KeyError(f"Found {nmodules + ndbprots} matching prototypes.") + raise KeyError(f"Found {nmodules + ndbprots} matching prototypes {module_prototypes}.") if return_iterators: # trying to get the entire set of prototypes - we must paginate From 15371026d56631992890dca7b2a3b2bcd21613ab Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Sun, 25 Apr 2021 22:38:35 -0400 Subject: [PATCH 25/71] task handler update updating taskhandler.py before updating unit tests. All evennia unit tests pass. --- evennia/scripts/taskhandler.py | 509 +++++++++++++----------------- evennia/utils/tests/test_utils.py | 8 +- 2 files changed, 224 insertions(+), 293 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index c0905a5e09..35b5b34889 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -15,9 +15,7 @@ TASK_HANDLER = None def handle_error(*args, **kwargs): - """ - Handle errors withing deferred objects. - """ + """Handle errors withing deferred objects.""" for arg in args: # suppress cancel errors if arg.type == DefCancelledError: @@ -25,10 +23,165 @@ def handle_error(*args, **kwargs): raise arg +class TaskHandlerTask: + """A object to represent a single TaskHandler task. + + Instance Attributes: + task_id (int): the global id for this task + deferred (deferred): a reference to this task's deferred + Propert Attributes: + paused (bool): check if the deferral of a task has been paused. + called(self): A task attribute to check if the deferral of a task has been called. + + Methods: + pause(): Pause the callback of a task. + unpause(): Process all callbacks made since pause() was called. + do_task(): Execute the task (call its callback). + call(): Call the callback of this task. + remove(): Remove a task without executing it. + cancel(): Stop a task from automatically executing. + active(): Check if a task is active (has not been called yet). + exists(): Check if a task exists. + get_id(): Returns the global id for this task. For use with + """ + + def __init__(self, task_id): + self.task_id = task_id + self.deferred = TASK_HANDLER.get_deferred(task_id) + + def get_deferred(self): + """Return the instance of the deferred the task id is using. + + Returns: + An instance of a deferral or False if there is no task with the id. + None is returned if there is no deferral affiliated with this id. + """ + return TASK_HANDLER.get_deferred(self.task_id) + + def pause(self): + """Pause the callback of a task. + To resume use TaskHandlerTask.unpause + """ + d = self.deferred + if d: + d.pause() + + def unpause(self): + """Process all callbacks made since pause() was called.""" + d = self.deferred + if d: + d.unpause() + + @property + def paused(self): + """A task attribute to check if the deferral of a task has been paused. + + This exists to mock usage of a twisted deferred object. + + This will return None if the deferred object for the task does not + exist or if the task no longer exists. + """ + d = self.deferred + if d: + return d.paused + else: + return None + + def do_task(self): + """Execute the task (call its callback). + If calling before timedelay cancel the deferral affliated to this task. + Remove the task from the dictionary of current tasks on a successful + callback. + + Returns: + bool or any: Set to `False` if the task does not exist in task + handler. Otherwise it will be the return of the task's callback. + + """ + return TASK_HANDLER.do_task(self.task_id) + + def call(self): + """Call the callback of a task. + Leave the task unaffected otherwise. + This does not use the task's deferred instance. + The only requirement is that the task exist in task handler. + + Returns: + bool or any: Set to `False` if the task does not exist in task + handler. Otherwise it will be the return of the task's callback. + + """ + return TASK_HANDLER.call_task(self.task_id) + + def remove(self): + """Remove a task without executing it. + Deletes the instance of the task's deferral. + + Args: + task_id (int): an existing task ID. + + Returns: + bool: True if the removal completed successfully. + + """ + return TASK_HANDLER.remove(self.task_id) + + def cancel(self): + """Stop a task from automatically executing. + This will not remove the task. + + Returns: + bool: True if the cancel completed successfully. + False if the cancel did not complete successfully. + """ + return TASK_HANDLER.cancel(self.task_id) + + def active(self): + """Check if a task is active (has not been called yet). + + Returns: + bool: True if a task is active (has not been called yet). False if + it is not (has been called) or if the task does not exist. + """ + return TASK_HANDLER.active(self.task_id) + + @property + def called(self): + """ + A task attribute to check if the deferral of a task has been called. + + This exists to mock usage of a twisted deferred object. + It will not set to false if Task.call has been called. + + """ + d = self.deferred + if d: + return d.called + else: + return None + + def exists(self): + """Check if a task exists. + Most task handler methods check for existence for you. + + Returns: + bool: Tru the task exists False if it does not. + """ + return TASK_HANDLER.exists(self.task_id) + + def get_id(self): + """ Returns the global id for this task. For use with + `evennia.scripts.taskhandler.TASK_HANDLER`. + + Returns: + task_id (int): global task id for this task. + """ + return self.task_id + + class TaskHandler(object): - """ - A light singleton wrapper allowing to access permanent tasks. + """A light singleton wrapper allowing to access permanent tasks. When `utils.delay` is called, the task handler is used to create the task. @@ -53,9 +206,8 @@ class TaskHandler(object): def load(self): """Load from the ServerConfig. - Note: - This should be automatically called when Evennia starts. - It populates `self.tasks` according to the ServerConfig. + This should be automatically called when Evennia starts. + It populates `self.tasks` according to the ServerConfig. """ to_save = False @@ -76,7 +228,7 @@ class TaskHandler(object): continue callback = getattr(obj, method) - self.tasks[task_id] = date, callback, args, kwargs, True, None + self.tasks[task_id] = (date, callback, args, kwargs, True, None) if self.stale_timeout > 0: # cleanup stale tasks. self.clean_stale_tasks() @@ -84,8 +236,7 @@ class TaskHandler(object): self.save() def clean_stale_tasks(self): - """ - remove uncalled but canceled from task handler. + """remove uncalled but canceled from task handler. By default this will not occur until a canceled task has been uncalled for 60 second after the time it should have been called. @@ -139,35 +290,30 @@ class TaskHandler(object): ServerConfig.objects.conf("delayed_tasks", self.to_save) def add(self, timedelay, callback, *args, **kwargs): - """Add a new persistent task in the configuration. + """Add a new task. + + If the persistent kwarg is truthy: + The callback, args and values for kwarg will be serialized. Type + and attribute errors during the serialization will be logged, + but will not throw exceptions. + For persisten tasks do not use memory references in the callback + function or arguments. After a restart those memory references are no + longer accurate. Args: timedelay (int or float): time in sedconds before calling the callback. callback (function or instance method): the callback itself any (any): any additional positional arguments to send to the callback - - Keyword Args: - persistent (bool, optional): persist the task (stores it). - Add will return the task's id for use with the global TASK_HANDLER. - any (any): additional keyword arguments to send to the callback + *args: positional arguments to pass to callback. + **kwargs: key word arguments to pass to callback. + persistent (bool, optional): persist the task (stores it). + persisten key and value is removed from kwargs it will + not be passed to callback. Returns: - task_id (int), the task's id intended for use with this class. - False, if the task has completed before addition finishes. + TaskHandlerTask: An object to represent a task. + Reference evennia.scripts.taskhandler.Task for complete details. - Notes: - This method has two return types. - An instance of twisted's Deferred class is standard. - If the persistent kwarg is truthy instead a task ID will be returned. - This task id can be used with task handler's do_task and remove methods. - - If the persistent kwarg is truthy: - The callback, args and values for kwarg will be serialized. Type - and attribute errors during the serialization will be logged, - but will not throw exceptions. - Do not use memory references in the callback function or arguments. - As those memory references will no longer acurately point to - the variable desired. """ # set the completion time # Only used on persistent tasks after a restart @@ -224,7 +370,7 @@ class TaskHandler(object): d = deferLater(self.clock, timedelay, callback, *args, **kwargs) d.addErrback(handle_error) - # some tasks may complete before the deferal can be added + # some tasks may complete before the deferred can be added if task_id in self.tasks: task = self.tasks.get(task_id) task = list(task) @@ -235,21 +381,18 @@ class TaskHandler(object): return False if self.stale_timeout > 0: self.clean_stale_tasks() - return Task(task_id) + return TaskHandlerTask(task_id) def exists(self, task_id): - """ - Test if a task exists. + """Check if a task exists. + Most task handler methods check for existence for you. Args: task_id (int): an existing task ID. Returns: - True (bool): if the task exists. - False (bool): if the task does not exist. + bool: Tru the task exists False if it does not. - Note: - Most task handler methods check for existence for you. """ if task_id in self.tasks: return True @@ -257,51 +400,37 @@ class TaskHandler(object): return False def active(self, task_id): - """ - Check if a task is active (has not been called yet). + """Check if a task is active (has not been called yet). Args: task_id (int): an existing task ID. Returns: - True (bool): If a task is active (has not been called yet). - False (bool): if the task - is not active (has already been called), - does not exist + bool: True if a task is active (has not been called yet). False if + it is not (has been called) or if the task does not exist. """ if task_id in self.tasks: # if the task has not been run, cancel it - d = self.get_deferred(task_id) - if d: # it is remotely possible for a task to not have a deferral - if d.called: - return False - else: # the callback has not been called yet. - return True - else: # this task has no deferral, and could not have been called - return True + deferred = self.get_deferred(task_id) + return not (deferred and deferred.called) else: return False def cancel(self, task_id): - """ - Stop a task from automatically executing. + """Stop a task from automatically executing. This will not remove the task. Args: task_id (int): an existing task ID. Returns: - True (bool): if the removal completed successfully. - False (bool): if the task: - does not exist, - has already run, - does not have a deferral instance created for the task. - None, if there was a raised exception + bool: True if the cancel completed successfully. + False if the cancel did not complete successfully. """ if task_id in self.tasks: # if the task has not been run, cancel it d = self.get_deferred(task_id) - if d: # it is remotely possible for a task to not have a deferral + if d: # it is remotely possible for a task to not have a deferred if d.called: return False else: # the callback has not been called yet. @@ -313,17 +442,14 @@ class TaskHandler(object): return False def remove(self, task_id): - """ - Remove a task without executing it. + """Remove a task without executing it. Deletes the instance of the task's deferral. Args: task_id (int): an existing task ID. Returns: - True (bool): if the removal completed successfully or if the a - task with the id does not exist. - None: if there was a raised exception + bool: True if the removal completed successfully. """ d = None @@ -341,33 +467,30 @@ class TaskHandler(object): del d return True - def remove_all(self, save=True, cancel=True): - """ - Remove all tasks. + def clear(self, save=True, cancel=True): + """clear all tasks. By default tasks are canceled and removed from the database also. - Arguments: + Args: save=True (bool): Should changes to persistent tasks be saved to database. cancel=True (bool): Cancel scheduled tasks before removing it from task handler. Returns: True (bool): if the removal completed successfully. """ - tasks_ids = tuple(self.tasks.keys()) - for task_id in tasks_ids: - if cancel: - self.cancel(task_id) - del self.tasks[task_id] - tasks_ids = tuple(self.to_save.keys()) - for task_id in tasks_ids: - del self.to_save[task_id] + if self.tasks: + for task_id in self.tasks.keys(): + if cancel: + self.cancel(task_id) + self.tasks = {} + if self.to_save: + self.to_save = {} if save: self.save() return True def call_task(self, task_id): - """ - Call the callback of a task. + """Call the callback of a task. Leave the task unaffected otherwise. This does not use the task's deferred instance. The only requirement is that the task exist in task handler. @@ -376,8 +499,9 @@ class TaskHandler(object): task_id (int): an existing task ID. Returns: - False (bool): if the task does not exist in task handler. - ?: The return of the task's callback. + bool or any: Set to `False` if the task does not exist in task + handler. Otherwise it will be the return of the task's callback. + """ if task_id in self.tasks: date, callback, args, kwargs, persistent, d = self.tasks.get(task_id) @@ -386,8 +510,7 @@ class TaskHandler(object): return callback(*args, **kwargs) def do_task(self, task_id): - """ - Execute the task (call its callback). + """Execute the task (call its callback). If calling before timedelay cancel the deferral affliated to this task. Remove the task from the dictionary of current tasks on a successful callback. @@ -396,16 +519,8 @@ class TaskHandler(object): task_id (int): a valid task ID. Returns: - False (bool): if the: - task no longer exists, - has no affliated instance of deferral - The return of the callback passed on task creation. - This makes it possible for the callback to also return False - None: if there was a raised exception - - Note: - On a successful call the task will be removed from the dictionary - of current tasks. + bool or any: Set to `False` if the task does not exist in task + handler. Otherwise it will be the return of the task's callback. """ callback_return = False @@ -430,214 +545,30 @@ class TaskHandler(object): task_id (int): a valid task ID. Returns: - An instance of a deferral or False if there is no task with the id. - None is returned if there is no deferral affiliated with this id. + deffered or None: An instance of a deferred or None if there is no + task with the id. None is returned if there is no deferral + affiliated with this id. """ if task_id in self.tasks: return self.tasks[task_id][5] else: - return False + return None def create_delays(self): """Create the delayed tasks for the persistent tasks. - - Note: - This method should be automatically called when Evennia starts. + This method should be automatically called when Evennia starts. """ now = datetime.now() - for task_id, (date, callbac, args, kwargs, _, _) in self.tasks.items(): - self.tasks[task_id] = date, callbac, args, kwargs, True, None + for task_id, (date, callback, args, kwargs, _, _) in self.tasks.items(): + self.tasks[task_id] = date, callback, args, kwargs, True, None seconds = max(0, (date - now).total_seconds()) d = deferLater(self.clock, seconds, self.do_task, task_id) d.addErrback(handle_error) - # some tasks may complete before the deferal can be added + # some tasks may complete before the deferred can be added if self.tasks.get(task_id, False): - self.tasks[task_id] = date, callbac, args, kwargs, True, d + self.tasks[task_id] = date, callback, args, kwargs, True, d # Create the soft singleton TASK_HANDLER = TaskHandler() - - -class Task: - """ - A object to represent a single TaskHandler task. - - Instance Attributes: - task_id (int): the global id for this task - deferred (deferred): a reference to this task's deferred - Propert Attributes: - paused (bool): check if the deferral of a task has been paused. - called(self): A task attribute to check if the deferral of a task has been called. - - Methods: - pause(): Pause the callback of a task. - unpause(): Process all callbacks made since pause() was called. - do_task(): Execute the task (call its callback). - call(): Call the callback of this task. - remove(): Remove a task without executing it. - cancel(): Stop a task from automatically executing. - active(): Check if a task is active (has not been called yet). - exists(): Check if a task exists. - get_id(): Returns the global id for this task. For use with - """ - def __init__(self, task_id): - self.task_id = task_id - self.deferred = TASK_HANDLER.get_deferred(task_id) - - def get_deferred(self): - """ - Return the instance of the deferred the task id is using. - - Returns: - An instance of a deferral or False if there is no task with the id. - None is returned if there is no deferral affiliated with this id. - """ - return TASK_HANDLER.get_deferred(self.task_id) - - def pause(self): - """ - Pause the callback of a task. - To resume use Task.unpause - """ - d = self.deferred - if d: - d.pause() - - def unpause(self): - """ - Process all callbacks made since pause() was called. - """ - d = self.deferred - if d: - d.unpause() - - @property - def paused(self): - """ - A task attribute to check if the deferral of a task has been paused. - - This exists to mock usage of a twisted deferred object. - - This will return None if the deferred object for the task does not - exist or if the task no longer exists. - """ - d = self.deferred - if d: - return d.paused - else: - return None - - def do_task(self): - """ - Execute the task (call its callback). - If calling before timedelay cancel the deferral affliated to this task. - Remove the task from the dictionary of current tasks on a successful - callback. - - Returns: - False (bool): if the: - task no longer exists, - has no affliated instance of deferral - The return of the callback passed on task creation. - This makes it possible for the callback to also return False - None: if there was a raised exception - - Note: - On a successful call the task will be removed from the dictionary - of current tasks. - - """ - return TASK_HANDLER.do_task(self.task_id) - - def call(self): - """ - Call the callback of this task. - Leave the task unaffected otherwise. - This does not use the task's deferred instance. - The only requirement is that the task exist in task handler. - - Returns: - False (bool): if the task does not exist in task handler. - ?: The return of the task's callback. - """ - return TASK_HANDLER.call_task(self.task_id) - - def remove(self): - """ - Remove a task without executing it. - Deletes the instance of the task's deferral. - - Returns: - True (bool): if the removal completed successfully or if the a - task with the id does not exist. - None: if there was a raised exception - - """ - return TASK_HANDLER.remove(self.task_id) - - def cancel(self): - """ - Stop a task from automatically executing. - This will not remove the task. - - Returns: - True (bool): if the removal completed successfully. - False (bool): if the task: - does not exist, - has already run, - does not have a deferral instance created for the task. - None, if there was a raised exception - """ - return TASK_HANDLER.cancel(self.task_id) - - def active(self): - """ - Check if a task is active (has not been called yet). - - Returns: - True (bool): If a task is active (has not been called yet). - False (bool): if the task - is not active (has already been called), - does not exist - """ - return TASK_HANDLER.active(self.task_id) - - @property - def called(self): - """ - A task attribute to check if the deferral of a task has been called. - - This exists to mock usage of a twisted deferred object. - It will not set to false if Task.call has been called. - - """ - d = self.deferred - if d: - return d.called - else: - return None - - def exists(self): - """ - Test if a task exists. - - Returns: - True (bool): if the task exists. - False (bool): if the task does not exist. - - Note: - Most task handler methods check for existence for you. - """ - return TASK_HANDLER.exists(self.task_id) - - def get_id(self): - """ - Returns the global id for this task. For use with - `evennia.scripts.taskhandler.TASK_HANDLER`. - - Returns: - task_id (int): global task id for this task. - """ - return self.task_id diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index fa67bc768f..e4d8cf6927 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -463,23 +463,23 @@ class TestDelay(EvenniaTest): self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) self.assertEqual(self.char1.ndb.dummy_var, False) - _TASK_HANDLER.remove_all() + _TASK_HANDLER.clear() self.char1.ndb.dummy_var = False _TASK_HANDLER._now = False # replicate a restart - _TASK_HANDLER.remove_all() + _TASK_HANDLER.clear() _TASK_HANDLER.save() self.assertFalse(_TASK_HANDLER.tasks) self.assertFalse(_TASK_HANDLER.to_save) # create a persistent task. t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) _TASK_HANDLER.save() - _TASK_HANDLER.remove_all(False) # remove all tasks, do not save this change. + _TASK_HANDLER.clear(False) # remove all tasks, do not save this change. _TASK_HANDLER.clock.advance(timedelay) # advance twisted reactor time past callback time self.assertEqual(self.char1.ndb.dummy_var, False) # task has not run _TASK_HANDLER.load() _TASK_HANDLER.create_delays() _TASK_HANDLER.clock.advance(timedelay) # Clock must advance to trigger, even if past timedelay self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - _TASK_HANDLER.remove_all() + _TASK_HANDLER.clear() self.char1.ndb.dummy_var = False From da9128dcdfd672e0b4f1cef143fa1db10463a299 Mon Sep 17 00:00:00 2001 From: Ben Longden Date: Wed, 21 Apr 2021 18:17:19 +0100 Subject: [PATCH 26/71] Mudlet likes GA's for prompts Don't sent GA on every message (not everything is a prompt) Do enforce line endings for mudlet, it wont display anything without it. --- evennia/server/portal/telnet.py | 5 ++--- evennia/server/portal/ttype.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 821e96aa24..8e5c4baf96 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -337,8 +337,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): line = line.replace(b"\n", b"\r\n") if not line.endswith(b"\r\n") and self.protocol_flags.get("FORCEDENDLINE", True): line += b"\r\n" - if not self.protocol_flags.get("NOGOAHEAD", True): - line += IAC + GA return self.transport.write(mccp_compress(self, line)) # Session hooks @@ -440,7 +438,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): prompt = mxp_parse(prompt) prompt = to_bytes(prompt, self) prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n") - prompt += IAC + GA + if not self.protocol_flags.get("NOGOAHEAD", True): + prompt += IAC + GA self.transport.write(mccp_compress(self, prompt)) else: if echo is not None: diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index 01e7ebf74a..0b0683b637 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -119,7 +119,7 @@ class Ttype(object): if clientname.startswith("MUDLET"): # supports xterm256 stably since 1.1 (2010?) xterm256 = clientname.split("MUDLET", 1)[1].strip() >= "1.1" - self.protocol.protocol_flags["FORCEDENDLINE"] = False + self.protocol.protocol_flags["FORCEDENDLINE"] = True if clientname.startswith("TINTIN++"): self.protocol.protocol_flags["FORCEDENDLINE"] = True From e9c66530cd009e186066f6bf5ce7943ce961bdc8 Mon Sep 17 00:00:00 2001 From: Ben Longden Date: Thu, 22 Apr 2021 11:01:21 +0100 Subject: [PATCH 27/71] FORCEENDLINE is True by default so no need to set Test also updated to check FORCEENDLINE after TTYPE is negotiated --- evennia/server/portal/tests.py | 3 +-- evennia/server/portal/ttype.py | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/evennia/server/portal/tests.py b/evennia/server/portal/tests.py index 6bd76db827..a67f1a4490 100644 --- a/evennia/server/portal/tests.py +++ b/evennia/server/portal/tests.py @@ -231,7 +231,6 @@ class TestTelnet(TwistedTestCase): self.transport.client = ["localhost"] self.transport.setTcpKeepAlive = Mock() d = self.proto.makeConnection(self.transport) - # test suppress_ga self.assertTrue(self.proto.protocol_flags["NOGOAHEAD"]) self.proto.dataReceived(IAC + DONT + SUPPRESS_GA) @@ -246,13 +245,13 @@ class TestTelnet(TwistedTestCase): self.assertEqual(self.proto.protocol_flags["SCREENHEIGHT"][0], 45) self.assertEqual(self.proto.handshakes, 6) # test ttype - self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"]) self.assertFalse(self.proto.protocol_flags["TTYPE"]) self.assertTrue(self.proto.protocol_flags["ANSI"]) self.proto.dataReceived(IAC + WILL + TTYPE) self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MUDLET", IAC, SE])) self.assertTrue(self.proto.protocol_flags["XTERM256"]) self.assertEqual(self.proto.protocol_flags["CLIENTNAME"], "MUDLET") + self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"]) self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"XTERM", IAC, SE])) self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MTTS 137", IAC, SE])) self.assertEqual(self.proto.handshakes, 5) diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index 0b0683b637..04d78b497f 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -119,10 +119,6 @@ class Ttype(object): if clientname.startswith("MUDLET"): # supports xterm256 stably since 1.1 (2010?) xterm256 = clientname.split("MUDLET", 1)[1].strip() >= "1.1" - self.protocol.protocol_flags["FORCEDENDLINE"] = True - - if clientname.startswith("TINTIN++"): - self.protocol.protocol_flags["FORCEDENDLINE"] = True if ( clientname.startswith("XTERM") From cd4c92515a198fd5d8b27b3fb7a73e785d1743d0 Mon Sep 17 00:00:00 2001 From: Ben Longden Date: Mon, 26 Apr 2021 10:31:42 +0100 Subject: [PATCH 28/71] Move prompt logic to a flag set by TTYPE (for Mudlet, currently) --- evennia/server/portal/suppress_ga.py | 1 + evennia/server/portal/telnet.py | 6 ++++-- evennia/server/portal/tests.py | 1 + evennia/server/portal/ttype.py | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/evennia/server/portal/suppress_ga.py b/evennia/server/portal/suppress_ga.py index a295582a26..0ef356021d 100644 --- a/evennia/server/portal/suppress_ga.py +++ b/evennia/server/portal/suppress_ga.py @@ -40,6 +40,7 @@ class SuppressGA(object): self.protocol = protocol self.protocol.protocol_flags["NOGOAHEAD"] = True + self.protocol.protocol_flags["NOPROMPTGOAHEAD"] = True # Used to send a GA after a prompt line only, set in TTYPE (per client) # tell the client that we prefer to suppress GA ... self.protocol.will(SUPPRESS_GA).addCallbacks(self.will_suppress_ga, self.wont_suppress_ga) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 8e5c4baf96..147d37485c 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -337,6 +337,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): line = line.replace(b"\n", b"\r\n") if not line.endswith(b"\r\n") and self.protocol_flags.get("FORCEDENDLINE", True): line += b"\r\n" + if not self.protocol_flags.get("NOGOAHEAD", True) and self.protocol_flags.get("NOPROMPTGOAHEAD", True): + line += IAC + GA return self.transport.write(mccp_compress(self, line)) # Session hooks @@ -438,8 +440,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): prompt = mxp_parse(prompt) prompt = to_bytes(prompt, self) prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n") - if not self.protocol_flags.get("NOGOAHEAD", True): - prompt += IAC + GA + if not self.protocol_flags.get("NOGOAHEAD", True) and not self.protocol_flags.get("NOPROMPTGOAHEAD", True): + prompt += IAC + GA self.transport.write(mccp_compress(self, prompt)) else: if echo is not None: diff --git a/evennia/server/portal/tests.py b/evennia/server/portal/tests.py index a67f1a4490..bd3cb94503 100644 --- a/evennia/server/portal/tests.py +++ b/evennia/server/portal/tests.py @@ -252,6 +252,7 @@ class TestTelnet(TwistedTestCase): self.assertTrue(self.proto.protocol_flags["XTERM256"]) self.assertEqual(self.proto.protocol_flags["CLIENTNAME"], "MUDLET") self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"]) + self.assertFalse(self.proto.protocol_flags["NOPROMPTGOAHEAD"]) self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"XTERM", IAC, SE])) self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MTTS 137", IAC, SE])) self.assertEqual(self.proto.handshakes, 5) diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index 04d78b497f..368ebecf1a 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -119,6 +119,8 @@ class Ttype(object): if clientname.startswith("MUDLET"): # supports xterm256 stably since 1.1 (2010?) xterm256 = clientname.split("MUDLET", 1)[1].strip() >= "1.1" + # Mudlet likes GA's on a prompt line for the prompt trigger to match. + self.protocol.protocol_flags["NOPROMPTGOAHEAD"] = False if ( clientname.startswith("XTERM") From 35ed709635d2bb0547d479da44770ebd14924c4d Mon Sep 17 00:00:00 2001 From: Ben Longden Date: Mon, 26 Apr 2021 12:04:35 +0100 Subject: [PATCH 29/71] Review feedback --- evennia/server/portal/telnet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 147d37485c..e61a42570a 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -337,7 +337,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): line = line.replace(b"\n", b"\r\n") if not line.endswith(b"\r\n") and self.protocol_flags.get("FORCEDENDLINE", True): line += b"\r\n" - if not self.protocol_flags.get("NOGOAHEAD", True) and self.protocol_flags.get("NOPROMPTGOAHEAD", True): + if not self.protocol_flags.get("NOGOAHEAD", True): line += IAC + GA return self.transport.write(mccp_compress(self, line)) @@ -440,8 +440,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): prompt = mxp_parse(prompt) prompt = to_bytes(prompt, self) prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n") - if not self.protocol_flags.get("NOGOAHEAD", True) and not self.protocol_flags.get("NOPROMPTGOAHEAD", True): - prompt += IAC + GA + if not self.protocol_flags.get("NOPROMPTGOAHEAD", self.protocol_flags.get("NOGOAHEAD", True)): + prompt += IAC + GA self.transport.write(mccp_compress(self, prompt)) else: if echo is not None: From ca54ed31e93915845f7a441ba52d1667b3d1e9bb Mon Sep 17 00:00:00 2001 From: Ben Longden Date: Mon, 26 Apr 2021 12:29:05 +0100 Subject: [PATCH 30/71] For mudlet we overwrite the negotiated GA value Suppress GA Activate the NOPROMPTGOAHEAD flag so GA's only sent on prompts --- evennia/server/portal/tests.py | 1 + evennia/server/portal/ttype.py | 1 + 2 files changed, 2 insertions(+) diff --git a/evennia/server/portal/tests.py b/evennia/server/portal/tests.py index bd3cb94503..c26c9cc46d 100644 --- a/evennia/server/portal/tests.py +++ b/evennia/server/portal/tests.py @@ -252,6 +252,7 @@ class TestTelnet(TwistedTestCase): self.assertTrue(self.proto.protocol_flags["XTERM256"]) self.assertEqual(self.proto.protocol_flags["CLIENTNAME"], "MUDLET") self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"]) + self.assertTrue(self.proto.protocol_flags["NOGOAHEAD"]) self.assertFalse(self.proto.protocol_flags["NOPROMPTGOAHEAD"]) self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"XTERM", IAC, SE])) self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MTTS 137", IAC, SE])) diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index 368ebecf1a..bf6e3e4139 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -120,6 +120,7 @@ class Ttype(object): # supports xterm256 stably since 1.1 (2010?) xterm256 = clientname.split("MUDLET", 1)[1].strip() >= "1.1" # Mudlet likes GA's on a prompt line for the prompt trigger to match. + self.protocol.protocol_flags["NOGOAHEAD"] = True self.protocol.protocol_flags["NOPROMPTGOAHEAD"] = False if ( From 6e4f7fa36fc7783562e163c54ea7dbee891e4838 Mon Sep 17 00:00:00 2001 From: Ben Longden Date: Mon, 26 Apr 2021 12:42:11 +0100 Subject: [PATCH 31/71] Only use GA-lite for mudlet if it's rejected NOGOAHEAD --- evennia/server/portal/ttype.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index bf6e3e4139..62ed853e9d 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -119,9 +119,10 @@ class Ttype(object): if clientname.startswith("MUDLET"): # supports xterm256 stably since 1.1 (2010?) xterm256 = clientname.split("MUDLET", 1)[1].strip() >= "1.1" - # Mudlet likes GA's on a prompt line for the prompt trigger to match. - self.protocol.protocol_flags["NOGOAHEAD"] = True - self.protocol.protocol_flags["NOPROMPTGOAHEAD"] = False + # Mudlet likes GA's on a prompt line for the prompt trigger to match, if it's not wanting NOGOAHEAD. + if not self.protocol.protocol_flags["NOGOAHEAD"]: + self.protocol.protocol_flags["NOGOAHEAD"] = True + self.protocol.protocol_flags["NOPROMPTGOAHEAD"] = False if ( clientname.startswith("XTERM") From bd276532a11b01f2f8257fbc4ec902c73eadeaa0 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Mon, 26 Apr 2021 08:59:35 -0400 Subject: [PATCH 32/71] task handler unit test revamp & bugfix revamped task handler unit tests found bug when a False persistent kwarg is passed to the add method. Resolved it. All evennia unit tests pass. Default run level and run level 2. --- evennia/scripts/taskhandler.py | 9 +- evennia/utils/tests/test_utils.py | 334 +++++++++++++++++------------- 2 files changed, 197 insertions(+), 146 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 35b5b34889..62a4c62987 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -67,7 +67,7 @@ class TaskHandlerTask: d.pause() def unpause(self): - """Process all callbacks made since pause() was called.""" + """Unpause a task, run the task if it has passed delay time.""" d = self.deferred if d: d.unpause() @@ -328,8 +328,9 @@ class TaskHandler(object): # record the task to the tasks dictionary persistent = kwargs.get("persistent", False) - if persistent: + if "persistent" in kwargs: del kwargs["persistent"] + if persistent: safe_args = [] safe_kwargs = {} @@ -358,10 +359,10 @@ class TaskHandler(object): else: safe_kwargs[key] = value - self.tasks[task_id] = (comp_time, callback, safe_args, safe_kwargs, True, None) + self.tasks[task_id] = (comp_time, callback, safe_args, safe_kwargs, persistent, None) self.save() else: # this is a non-persitent task - self.tasks[task_id] = (comp_time, callback, args, kwargs, True, None) + self.tasks[task_id] = (comp_time, callback, args, kwargs, persistent, None) # defer the task callback = self.do_task diff --git a/evennia/utils/tests/test_utils.py b/evennia/utils/tests/test_utils.py index e4d8cf6927..61c3ac0d33 100644 --- a/evennia/utils/tests/test_utils.py +++ b/evennia/utils/tests/test_utils.py @@ -323,163 +323,213 @@ class TestDelay(EvenniaTest): Test utils.delay. """ - def test_delay(self): + def setUp(self): + super().setUp() # get a reference of TASK_HANDLER - timedelay = 5 + self.timedelay = 5 global _TASK_HANDLER if _TASK_HANDLER is None: from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER _TASK_HANDLER.clock = task.Clock() self.char1.ndb.dummy_var = False - # test a persistent deferral, that completes after delay time - t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - # call the task early to test Task.call and TaskHandler.call_task - result = t.call() - self.assertTrue(result) - del result - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - self.assertTrue(_TASK_HANDLER.active(t.get_id())) # test Task.get_id - self.assertTrue(t.active()) - self.char1.ndb.dummy_var = False # Set variable to continue completion after delay time test. - _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertTrue(t.called) # test Task.called property - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - self.char1.ndb.dummy_var = False - # test a persistent deferral, that completes on a manual call - t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - self.assertTrue(t.active()) - result = t.do_task() - self.assertTrue(result) - self.assertFalse(t.exists()) - _TASK_HANDLER.clock.advance(timedelay) # make time pass, important keep - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - self.char1.ndb.dummy_var = False - # test a non persisten deferral, that completes after delay time. + + def tearDown(self): + super().tearDown() + _TASK_HANDLER.clear() + + def test_call_early(self): + # call a task early with call + for pers in (True, False): + t = utils.delay(self.timedelay, dummy_func, self.char1.dbref, persistent=pers) + result = t.call() + self.assertTrue(result) + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertTrue(t.exists()) + self.assertTrue(t.active()) + self.char1.ndb.dummy_var = False + + def test_do_task(self): + # call the task early with do_task + for pers in (True, False): + t = utils.delay(self.timedelay, dummy_func, self.char1.dbref, persistent=pers) + # call the task early to test Task.call and TaskHandler.call_task + result = t.do_task() + self.assertTrue(result) + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertFalse(t.exists()) + self.char1.ndb.dummy_var = False + + def test_deferred_call(self): + # wait for deferred to call + timedelay = self.timedelay + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + self.assertTrue(t.active()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertFalse(t.exists()) + self.char1.ndb.dummy_var = False + + def test_short_deferred_call(self): + # wait for deferred to call with a very short time + timedelay = .1 + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + self.assertTrue(t.active()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.assertFalse(t.exists()) + self.char1.ndb.dummy_var = False + + def test_active(self): + timedelay = self.timedelay t = utils.delay(timedelay, dummy_func, self.char1.dbref) + self.assertTrue(_TASK_HANDLER.active(t.get_id())) self.assertTrue(t.active()) _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - self.char1.ndb.dummy_var = False - # test a non-persistent deferral, that completes on a manual call - t = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(t.active()) - result = t.do_task() - self.assertTrue(result) - self.assertFalse(t.exists()) - _TASK_HANDLER.clock.advance(timedelay) # make time pass, important keep - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - self.char1.ndb.dummy_var = False - # test a non persisten deferral, with a short timedelay - t = utils.delay(.1, dummy_func, self.char1.dbref) - self.assertTrue(t.active()) - _TASK_HANDLER.clock.advance(.1) # make time pass - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - self.char1.ndb.dummy_var = False - # test canceling a deferral. - # after this the task_id 1 remains used by this canceled but unused task - t = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(t.active()) - success = t.cancel() + self.assertFalse(_TASK_HANDLER.active(t.get_id())) self.assertFalse(t.active()) - self.assertTrue(success) - self.assertTrue(t.exists()) - _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertEqual(self.char1.ndb.dummy_var, False) - self.char1.ndb.dummy_var = False - # test removing an active task + + def test_called(self): + timedelay = self.timedelay t = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(t.active()) - success = t.remove() - self.assertFalse(t.active()) + self.assertFalse(t.called) _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertEqual(self.char1.ndb.dummy_var, False) - self.assertFalse(t.exists()) - self.char1.ndb.dummy_var = False - # test removing a canceled task - t = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(t.active()) - deferal_inst = t.get_deferred() - deferal_inst.cancel() - self.assertFalse(t.active()) - success = t.remove() - _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertEqual(self.char1.ndb.dummy_var, False) - self.assertFalse(t.exists()) - self.char1.ndb.dummy_var = False - # test pause, paused and unpause - t = utils.delay(timedelay, dummy_func, self.char1.dbref) - self.assertTrue(t.active()) - t.pause() - self.assertTrue(t.paused) - t.unpause() - self.assertFalse(t.paused) - self.assertEqual(self.char1.ndb.dummy_var, False) - t.pause() - _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertEqual(self.char1.ndb.dummy_var, False) - t.unpause() - self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - self.char1.ndb.dummy_var = False - # test automated removal of stale tasks. - t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - t.cancel() - self.assertFalse(t.active()) - _TASK_HANDLER.clock.advance(timedelay) # make time pass - self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) - self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) + self.assertTrue(t.called) + + def test_cancel(self): + timedelay = self.timedelay + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + self.assertTrue(t.active()) + success = t.cancel() + self.assertFalse(t.active()) + self.assertTrue(success) + self.assertTrue(t.exists()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, False) + + def test_remove(self): + timedelay = self.timedelay + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + self.assertTrue(t.active()) + success = t.remove() + self.assertTrue(success) + self.assertFalse(t.active()) + self.assertFalse(t.exists()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, False) + + def test_remove_canceled(self): + # remove a canceled task + timedelay = self.timedelay + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + self.assertTrue(t.active()) + success = t.cancel() + self.assertTrue(success) + self.assertTrue(t.exists()) + self.assertFalse(t.active()) + success = t.remove() + self.assertTrue(success) + self.assertFalse(t.exists()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, False) + + def test_pause_unpause(self): + # remove a canceled task + timedelay = self.timedelay + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + self.assertTrue(t.active()) + t.pause() + self.assertTrue(t.paused) + t.unpause() + self.assertFalse(t.paused) + self.assertEqual(self.char1.ndb.dummy_var, False) + t.pause() + _TASK_HANDLER.clock.advance(timedelay) # make time pass + self.assertEqual(self.char1.ndb.dummy_var, False) + t.unpause() + self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') + self.char1.ndb.dummy_var = False + + def test_auto_stale_task_removal(self): + # automated removal of stale tasks. + timedelay = self.timedelay + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + t.cancel() + self.assertFalse(t.active()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + if pers: + self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) + self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) + # Make task handler's now time, after the stale timeout + _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + timedelay + 1) # add a task to test automatic removal - _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + 6) # task handler time to 6 seconds after stale timeout - t2 = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - self.assertFalse(t.get_id() in _TASK_HANDLER.to_save) - self.assertFalse(t.get_id() in _TASK_HANDLER.tasks) - self.assertEqual(self.char1.ndb.dummy_var, False) - # test manual cleanup - t2.cancel() - _TASK_HANDLER.clock.advance(timedelay) # advance twisted reactor time past callback time - _TASK_HANDLER._now = datetime.now() + timedelta(seconds=30) # set TaskHandler's time to 30 seconnds from now - # test before stale_timeout time - _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method - # still in the task handler because stale timeout has not been reached - self.assertTrue(t2.get_id() in _TASK_HANDLER.to_save) - self.assertTrue(t2.get_id() in _TASK_HANDLER.tasks) - # advance past stale timeout - _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + 6) # task handler time to 6 seconds after stale timeout - _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method - self.assertFalse(t2.get_id() in _TASK_HANDLER.to_save) - self.assertFalse(t2.get_id() in _TASK_HANDLER.tasks) - self.char1.ndb.dummy_var = False - _TASK_HANDLER._now = False - # if _TASK_HANDLER.stale_timeout is 0 or less, automatic cleanup should not run + t2 = utils.delay(timedelay, dummy_func, self.char1.dbref) + if pers: + self.assertFalse(t.get_id() in _TASK_HANDLER.to_save) + self.assertFalse(t.get_id() in _TASK_HANDLER.tasks) + self.assertEqual(self.char1.ndb.dummy_var, False) + _TASK_HANDLER.clear() + + def test_manual_stale_task_removal(self): + # manual removal of stale tasks. + timedelay = self.timedelay + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + t.cancel() + self.assertFalse(t.active()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + if pers: + self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) + self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) + # Make task handler's now time, after the stale timeout + _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + timedelay + 1) + _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method + if pers: + self.assertFalse(t.get_id() in _TASK_HANDLER.to_save) + self.assertFalse(t.get_id() in _TASK_HANDLER.tasks) + self.assertEqual(self.char1.ndb.dummy_var, False) + _TASK_HANDLER.clear() + + def test_disable_stale_removal(self): + # manual removal of stale tasks. + timedelay = self.timedelay _TASK_HANDLER.stale_timeout = 0 - t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - t.cancel() - self.assertFalse(t.active()) - _TASK_HANDLER.clock.advance(timedelay) # advance twisted's reactor past callback time - self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) - self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) - # add a task to test automatic removal - _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + 6) # task handler time to 6 seconds after stale timeout - t2 = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) - self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) - self.assertEqual(self.char1.ndb.dummy_var, False) - _TASK_HANDLER.clear() - self.char1.ndb.dummy_var = False - _TASK_HANDLER._now = False - # replicate a restart - _TASK_HANDLER.clear() - _TASK_HANDLER.save() - self.assertFalse(_TASK_HANDLER.tasks) - self.assertFalse(_TASK_HANDLER.to_save) - # create a persistent task. - t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) - _TASK_HANDLER.save() - _TASK_HANDLER.clear(False) # remove all tasks, do not save this change. + for pers in (False, True): + t = utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=pers) + t.cancel() + self.assertFalse(t.active()) + _TASK_HANDLER.clock.advance(timedelay) # make time pass + if pers: + self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) + self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) + # Make task handler's now time, after the stale timeout + _TASK_HANDLER._now = datetime.now() + timedelta(seconds=_TASK_HANDLER.stale_timeout + timedelay + 1) + t2 = utils.delay(timedelay, dummy_func, self.char1.dbref) + if pers: + self.assertTrue(t.get_id() in _TASK_HANDLER.to_save) + self.assertTrue(t.get_id() in _TASK_HANDLER.tasks) + self.assertEqual(self.char1.ndb.dummy_var, False) + # manual removal should still work + _TASK_HANDLER.clean_stale_tasks() # cleanup of stale tasks in in the save method + if pers: + self.assertFalse(t.get_id() in _TASK_HANDLER.to_save) + self.assertFalse(t.get_id() in _TASK_HANDLER.tasks) + _TASK_HANDLER.clear() + + def test_server_restart(self): + # emulate a server restart + timedelay = self.timedelay + utils.delay(timedelay, dummy_func, self.char1.dbref, persistent=True) + _TASK_HANDLER.clear(False) # remove all tasks from task handler, do not save this change. _TASK_HANDLER.clock.advance(timedelay) # advance twisted reactor time past callback time self.assertEqual(self.char1.ndb.dummy_var, False) # task has not run - _TASK_HANDLER.load() - _TASK_HANDLER.create_delays() + _TASK_HANDLER.load() # load persistent tasks from database. + _TASK_HANDLER.create_delays() # create new deffered instances from persistent tasks _TASK_HANDLER.clock.advance(timedelay) # Clock must advance to trigger, even if past timedelay self.assertEqual(self.char1.ndb.dummy_var, 'dummy_func ran') - _TASK_HANDLER.clear() - self.char1.ndb.dummy_var = False From cf8052b816c0011330ead39c85e871c85918a0e4 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Fri, 30 Apr 2021 11:26:49 -0400 Subject: [PATCH 33/71] doc strings updates. doc strings updates. Unit tests pass at default run level. --- evennia/scripts/taskhandler.py | 81 ++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 62a4c62987..bd979a9fde 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -15,7 +15,7 @@ TASK_HANDLER = None def handle_error(*args, **kwargs): - """Handle errors withing deferred objects.""" + """Handle errors within deferred objects.""" for arg in args: # suppress cancel errors if arg.type == DefCancelledError: @@ -24,14 +24,14 @@ def handle_error(*args, **kwargs): class TaskHandlerTask: - """A object to represent a single TaskHandler task. + """An object to represent a single TaskHandler task. Instance Attributes: task_id (int): the global id for this task deferred (deferred): a reference to this task's deferred - Propert Attributes: - paused (bool): check if the deferral of a task has been paused. - called(self): A task attribute to check if the deferral of a task has been called. + Property Attributes: + paused (bool): check if the deferred instance of a task has been paused. + called(self): A task attribute to check if the deferred instance of a task has been called. Methods: pause(): Pause the callback of a task. @@ -43,6 +43,7 @@ class TaskHandlerTask: active(): Check if a task is active (has not been called yet). exists(): Check if a task exists. get_id(): Returns the global id for this task. For use with + """ def __init__(self, task_id): @@ -53,8 +54,9 @@ class TaskHandlerTask: """Return the instance of the deferred the task id is using. Returns: - An instance of a deferral or False if there is no task with the id. - None is returned if there is no deferral affiliated with this id. + bool or deferred: An instance of a deferred or False if there is no task with the id. + None is returned if there is no deferred affiliated with this id. + """ return TASK_HANDLER.get_deferred(self.task_id) @@ -74,12 +76,14 @@ class TaskHandlerTask: @property def paused(self): - """A task attribute to check if the deferral of a task has been paused. + """A task attribute to check if the deferred instance of a task has been paused. This exists to mock usage of a twisted deferred object. - This will return None if the deferred object for the task does not - exist or if the task no longer exists. + Returns: + bool or None: True if the task was properly paused. None if the task does not have + a deferred instance. + """ d = self.deferred if d: @@ -89,7 +93,7 @@ class TaskHandlerTask: def do_task(self): """Execute the task (call its callback). - If calling before timedelay cancel the deferral affliated to this task. + If calling before timedelay, cancel the deferred instance affliated to this task. Remove the task from the dictionary of current tasks on a successful callback. @@ -115,7 +119,7 @@ class TaskHandlerTask: def remove(self): """Remove a task without executing it. - Deletes the instance of the task's deferral. + Deletes the instance of the task's deferred. Args: task_id (int): an existing task ID. @@ -133,6 +137,7 @@ class TaskHandlerTask: Returns: bool: True if the cancel completed successfully. False if the cancel did not complete successfully. + """ return TASK_HANDLER.cancel(self.task_id) @@ -142,16 +147,22 @@ class TaskHandlerTask: Returns: bool: True if a task is active (has not been called yet). False if it is not (has been called) or if the task does not exist. + """ return TASK_HANDLER.active(self.task_id) @property def called(self): """ - A task attribute to check if the deferral of a task has been called. + A task attribute to check if the deferred instance of a task has been called. This exists to mock usage of a twisted deferred object. - It will not set to false if Task.call has been called. + It will not set to True if Task.call has been called. This only happens if + task's deferred instance calls the callback. + + Returns: + bool: True if the deferred instance of this task has called the callback. + False if the deferred instnace of this task has not called the callback. """ d = self.deferred @@ -165,7 +176,8 @@ class TaskHandlerTask: Most task handler methods check for existence for you. Returns: - bool: Tru the task exists False if it does not. + bool: True the task exists False if it does not. + """ return TASK_HANDLER.exists(self.task_id) @@ -175,6 +187,7 @@ class TaskHandlerTask: Returns: task_id (int): global task id for this task. + """ return self.task_id @@ -241,6 +254,7 @@ class TaskHandler(object): By default this will not occur until a canceled task has been uncalled for 60 second after the time it should have been called. To adjust this time use TASK_HANDLER.stale_timeout. + """ clean_ids = [] for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): @@ -256,9 +270,7 @@ class TaskHandler(object): return True def save(self): - """ - Save the tasks in ServerConfig. - """ + """Save the tasks in ServerConfig.""" for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items(): if task_id in self.to_save: @@ -296,23 +308,23 @@ class TaskHandler(object): The callback, args and values for kwarg will be serialized. Type and attribute errors during the serialization will be logged, but will not throw exceptions. - For persisten tasks do not use memory references in the callback + For persistent tasks do not use memory references in the callback function or arguments. After a restart those memory references are no longer accurate. Args: - timedelay (int or float): time in sedconds before calling the callback. + timedelay (int or float): time in seconds before calling the callback. callback (function or instance method): the callback itself any (any): any additional positional arguments to send to the callback *args: positional arguments to pass to callback. - **kwargs: key word arguments to pass to callback. + **kwargs: keyword arguments to pass to callback. persistent (bool, optional): persist the task (stores it). - persisten key and value is removed from kwargs it will + persistent key and value is removed from kwargs it will not be passed to callback. Returns: TaskHandlerTask: An object to represent a task. - Reference evennia.scripts.taskhandler.Task for complete details. + Reference evennia.scripts.taskhandler.TaskHandlerTask for complete details. """ # set the completion time @@ -392,7 +404,7 @@ class TaskHandler(object): task_id (int): an existing task ID. Returns: - bool: Tru the task exists False if it does not. + bool: True the task exists False if it does not. """ if task_id in self.tasks: @@ -409,6 +421,7 @@ class TaskHandler(object): Returns: bool: True if a task is active (has not been called yet). False if it is not (has been called) or if the task does not exist. + """ if task_id in self.tasks: # if the task has not been run, cancel it @@ -427,6 +440,7 @@ class TaskHandler(object): Returns: bool: True if the cancel completed successfully. False if the cancel did not complete successfully. + """ if task_id in self.tasks: # if the task has not been run, cancel it @@ -437,14 +451,14 @@ class TaskHandler(object): else: # the callback has not been called yet. d.cancel() return True - else: # this task has no deferral + else: # this task has no deferred instance return False else: return False def remove(self, task_id): """Remove a task without executing it. - Deletes the instance of the task's deferral. + Deletes the instance of the task's deferred. Args: task_id (int): an existing task ID. @@ -463,7 +477,7 @@ class TaskHandler(object): if task_id in self.to_save: del self.to_save[task_id] self.save() # remove from ServerConfig.objects - # delete the instance of the deferral + # delete the instance of the deferred if d: del d return True @@ -478,6 +492,7 @@ class TaskHandler(object): Returns: True (bool): if the removal completed successfully. + """ if self.tasks: for task_id in self.tasks.keys(): @@ -512,7 +527,7 @@ class TaskHandler(object): def do_task(self, task_id): """Execute the task (call its callback). - If calling before timedelay cancel the deferral affliated to this task. + If calling before timedelay cancel the deferred instance affliated to this task. Remove the task from the dictionary of current tasks on a successful callback. @@ -529,10 +544,10 @@ class TaskHandler(object): date, callback, args, kwargs, persistent, d = self.tasks.get(task_id) else: # the task does not exist return False - if d: # it is remotely possible for a task to not have a deferral + if d: # it is remotely possible for a task to not have a deferred if not d.called: # the task's deferred has not been called yet d.cancel() # cancel the automated callback - else: # this task has no deferral, and should not be called + else: # this task has no deferred, and should not be called return False callback_return = callback(*args, **kwargs) self.remove(task_id) @@ -546,9 +561,9 @@ class TaskHandler(object): task_id (int): a valid task ID. Returns: - deffered or None: An instance of a deferred or None if there is no - task with the id. None is returned if there is no deferral - affiliated with this id. + bool or deferred: An instance of a deferred or False if there is no task with the id. + None is returned if there is no deferred affiliated with this id. + """ if task_id in self.tasks: return self.tasks[task_id][5] From f235572cac39c279fc06feef9951492219f7f2b7 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 9 May 2021 15:25:57 +0200 Subject: [PATCH 34/71] Fix edge case in TaskHandler when un-pickleable callable supplied --- evennia/scripts/taskhandler.py | 32 +++++++++++++++++--------------- evennia/utils/utils.py | 21 ++++++++++----------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index bd979a9fde..ab1b29666d 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -5,6 +5,7 @@ Module containing the task handler for Evennia deferred tasks, persistent or not from datetime import datetime, timedelta from twisted.internet import reactor +from pickle import PickleError from twisted.internet.task import deferLater from twisted.internet.defer import CancelledError as DefCancelledError from evennia.server.models import ServerConfig @@ -287,18 +288,8 @@ class TaskHandler(object): # Check if callback can be pickled. args and kwargs have been checked safe_callback = None - try: - dbserialize(callback) - except (TypeError, AttributeError): - raise ValueError( - "the specified callback {} cannot be pickled. " - "It must be a top-level function in a module or an " - "instance method.".format(callback) - ) - else: - safe_callback = callback - self.to_save[task_id] = dbserialize((date, safe_callback, args, kwargs)) + self.to_save[task_id] = dbserialize((date, callback, args, kwargs)) ServerConfig.objects.conf("delayed_tasks", self.to_save) def add(self, timedelay, callback, *args, **kwargs): @@ -318,8 +309,8 @@ class TaskHandler(object): any (any): any additional positional arguments to send to the callback *args: positional arguments to pass to callback. **kwargs: keyword arguments to pass to callback. - persistent (bool, optional): persist the task (stores it). - persistent key and value is removed from kwargs it will + - persistent (bool, optional): persist the task (stores it). + Persistent key and value is removed from kwargs it will not be passed to callback. Returns: @@ -346,11 +337,22 @@ class TaskHandler(object): safe_args = [] safe_kwargs = {} + # an unsaveable callback should immediately abort + try: + dbserialize(callback) + except (TypeError, AttributeError, PickleError): + raise ValueError( + "the specified callback {} cannot be pickled. " + "It must be a top-level function in a module or an " + "instance method.".format(callback) + ) + return + # Check that args and kwargs contain picklable information for arg in args: try: dbserialize(arg) - except (TypeError, AttributeError): + except (TypeError, AttributeError, PickleError): log_err( "The positional argument {} cannot be " "pickled and will not be present in the arguments " @@ -362,7 +364,7 @@ class TaskHandler(object): for key, value in kwargs.items(): try: dbserialize(value) - except (TypeError, AttributeError): + except (TypeError, AttributeError, PickleError): log_err( "The {} keyword argument {} cannot be " "pickled and will not be present in the arguments " diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 105f9474a6..4d0d3a6c42 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1030,20 +1030,19 @@ def delay(timedelay, callback, *args, **kwargs): after `timedelay` seconds. args (any, optional): Will be used as arguments to callback Keyword Args: - persistent (bool, optional): should make the delay persistent - over a reboot or reload + persistent (bool, optional): Should make the delay persistent + over a reboot or reload. Defaults to False. any (any): Will be used as keyword arguments to callback. Returns: - deferred (deferred): Will fire with callback after - `timedelay` seconds. Note that if `timedelay()` is used in the - commandhandler callback chain, the callback chain can be - defined directly in the command body and don't need to be - specified here. - Reference twisted.internet.defer.Deferred - if persistent kwarg is truthy: - task_id (int): the task's id intended for use with - evennia.scripts.taskhandler.TASK_HANDLER's do_task and remove methods. + deferred or int: If ``persistent`` kwarg is `False`, return deferred + that will fire with callback after `timedelay` seconds. Note that + if `timedelay()` is used in the commandhandler callback chain, the + callback chain can be defined directly in the command body and + don't need to be specified here. Reference twisted.internet.defer.Deferred. + If persistent kwarg is set, return the task's ID as an integer. This is + intended for use with ``evennia.scripts.taskhandler.TASK_HANDLER`` + `.do_task` and `.remove` methods. Note: The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will From 758094b9624fa90c24285f7e4291b54a918a79d0 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 9 May 2021 15:43:21 +0200 Subject: [PATCH 35/71] Fix PEP8 in PR #2390 --- evennia/server/portal/suppress_ga.py | 4 +++- evennia/server/portal/telnet.py | 3 ++- evennia/server/portal/ttype.py | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/evennia/server/portal/suppress_ga.py b/evennia/server/portal/suppress_ga.py index 0ef356021d..21332ea070 100644 --- a/evennia/server/portal/suppress_ga.py +++ b/evennia/server/portal/suppress_ga.py @@ -40,7 +40,9 @@ class SuppressGA(object): self.protocol = protocol self.protocol.protocol_flags["NOGOAHEAD"] = True - self.protocol.protocol_flags["NOPROMPTGOAHEAD"] = True # Used to send a GA after a prompt line only, set in TTYPE (per client) + self.protocol.protocol_flags[ + "NOPROMPTGOAHEAD" + ] = True # Used to send a GA after a prompt line only, set in TTYPE (per client) # tell the client that we prefer to suppress GA ... self.protocol.will(SUPPRESS_GA).addCallbacks(self.will_suppress_ga, self.wont_suppress_ga) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index e61a42570a..2467fea040 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -440,7 +440,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): prompt = mxp_parse(prompt) prompt = to_bytes(prompt, self) prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n") - if not self.protocol_flags.get("NOPROMPTGOAHEAD", self.protocol_flags.get("NOGOAHEAD", True)): + if not self.protocol_flags.get("NOPROMPTGOAHEAD", + self.protocol_flags.get("NOGOAHEAD", True)): prompt += IAC + GA self.transport.write(mccp_compress(self, prompt)) else: diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index 62ed853e9d..d74b3ae9db 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -119,7 +119,8 @@ class Ttype(object): if clientname.startswith("MUDLET"): # supports xterm256 stably since 1.1 (2010?) xterm256 = clientname.split("MUDLET", 1)[1].strip() >= "1.1" - # Mudlet likes GA's on a prompt line for the prompt trigger to match, if it's not wanting NOGOAHEAD. + # Mudlet likes GA's on a prompt line for the prompt trigger to + # match, if it's not wanting NOGOAHEAD. if not self.protocol.protocol_flags["NOGOAHEAD"]: self.protocol.protocol_flags["NOGOAHEAD"] = True self.protocol.protocol_flags["NOPROMPTGOAHEAD"] = False From f724927cd3824de3f3af6593f32999fd566e8dd6 Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Mon, 10 May 2021 12:38:47 -0400 Subject: [PATCH 36/71] delay docstring update delay docstring update The old one had not been properly updated or formatted. --- evennia/utils/utils.py | 48 +++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 4d0d3a6c42..88ad8002c5 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1021,44 +1021,34 @@ _TASK_HANDLER = None def delay(timedelay, callback, *args, **kwargs): - """ - Delay the calling of a callback (function). + """Delay the calling of a callback (function). + + The task handler (`evennia.scripts.taskhandler.TaskHandler`) will + be called for persistent or non-persistent tasks. + + If persistent is set to True. + The callback, its arguments and other keyword arguments will be saved + (serialized) in the database, assuming they can be. The callback will + be executed even after a server restart/reload, taking into account the + specified delay (and server down time). + Keep in mind that persistent tasks arguments and callback should not + use memory references. + All task's whose time delays have passed will be called on server startup. Args: timedelay (int or float): The delay in seconds callback (callable): Will be called as `callback(*args, **kwargs)` after `timedelay` seconds. args (any, optional): Will be used as arguments to callback + Keyword Args: - persistent (bool, optional): Should make the delay persistent - over a reboot or reload. Defaults to False. - any (any): Will be used as keyword arguments to callback. + persistent (bool, optional): should make the delay persistent + over a reboot or reload + any (any, optional): Will be used as keyword arguments to callback. Returns: - deferred or int: If ``persistent`` kwarg is `False`, return deferred - that will fire with callback after `timedelay` seconds. Note that - if `timedelay()` is used in the commandhandler callback chain, the - callback chain can be defined directly in the command body and - don't need to be specified here. Reference twisted.internet.defer.Deferred. - If persistent kwarg is set, return the task's ID as an integer. This is - intended for use with ``evennia.scripts.taskhandler.TASK_HANDLER`` - `.do_task` and `.remove` methods. - - Note: - The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will - be called for persistent or non-persistent tasks. - If persistent is set to True, the callback, its arguments - and other keyword arguments will be saved (serialized) in the database, - assuming they can be. The callback will be executed even after - a server restart/reload, taking into account the specified delay - (and server down time). - Keep in mind that persistent tasks arguments and callback should not - use memory references. - If persistent is set to True the delay function will return an int - which is the task's id itended for use with TASK_HANDLER's do_task - and remove methods. - - All task's whose time delays have passed will be called on server startup. + task (TaskHandlerTask): A task instance. + Reference: evennia.scripts.taskhandler.TaskHandlerTask """ global _TASK_HANDLER From 0fa5788821eb94e15864b050df677ad4775666eb Mon Sep 17 00:00:00 2001 From: davewiththenicehat <54369722+davewiththenicehat@users.noreply.github.com> Date: Tue, 11 May 2021 16:46:19 -0400 Subject: [PATCH 37/71] utils.delay docstring update Updated docstring to show delay returns a TaskHandlerTask instance. --- evennia/utils/utils.py | 44 +++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 88ad8002c5..4aadc02768 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -1021,34 +1021,38 @@ _TASK_HANDLER = None def delay(timedelay, callback, *args, **kwargs): - """Delay the calling of a callback (function). - - The task handler (`evennia.scripts.taskhandler.TaskHandler`) will - be called for persistent or non-persistent tasks. - - If persistent is set to True. - The callback, its arguments and other keyword arguments will be saved - (serialized) in the database, assuming they can be. The callback will - be executed even after a server restart/reload, taking into account the - specified delay (and server down time). - Keep in mind that persistent tasks arguments and callback should not - use memory references. - All task's whose time delays have passed will be called on server startup. + """ + Delay the calling of a callback (function). Args: - timedelay (int or float): The delay in seconds + timedelay (int or float): The delay in seconds. callback (callable): Will be called as `callback(*args, **kwargs)` after `timedelay` seconds. - args (any, optional): Will be used as arguments to callback + args (any): Will be used as arguments to callback. Keyword Args: - persistent (bool, optional): should make the delay persistent - over a reboot or reload - any (any, optional): Will be used as keyword arguments to callback. + persistent (bool, optional): If True the delay remains after a server restart. + persistent is False by default. + any (any): Will be used as keyword arguments to callback. Returns: - task (TaskHandlerTask): A task instance. - Reference: evennia.scripts.taskhandler.TaskHandlerTask + task (TaskHandlerTask): An instance of a task. + Refer to, evennia.scripts.taskhandler.TaskHandlerTask + + Note: + The task handler (`evennia.scripts.taskhandler.TASK_HANDLER`) will + be called for persistent or non-persistent tasks. + If persistent is set to True, the callback, its arguments + and other keyword arguments will be saved (serialized) in the database, + assuming they can be. The callback will be executed even after + a server restart/reload, taking into account the specified delay + (and server down time). + Keep in mind that persistent tasks arguments and callback should not + use memory references. + If persistent is set to True the delay function will return an int + which is the task's id itended for use with TASK_HANDLER's do_task + and remove methods. + All persistent tasks whose time delays have passed will be called on server startup. """ global _TASK_HANDLER From 56b1c259f742134a12e1440cae9fe522b75aefe9 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 15 May 2021 09:30:00 +0200 Subject: [PATCH 38/71] Create codeql-analysis.yml --- codeql-analysis.yml | 71 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 codeql-analysis.yml diff --git a/codeql-analysis.yml b/codeql-analysis.yml new file mode 100644 index 0000000000..4607af4bf0 --- /dev/null +++ b/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master, develop ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '41 2 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 6d4bcd8ba9e4be07b3aeb32b77bb2929c4ea473b Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Mon, 24 May 2021 05:54:49 +1000 Subject: [PATCH 39/71] docs: fix a few simple typos There are small typos in: - CODING_STYLE.md - evennia/commands/cmdhandler.py - evennia/commands/default/syscommands.py - evennia/contrib/building_menu.py - evennia/prototypes/prototypes.py Fixes: - Should read `traverse` rather than `traverese`. - Should read `shortcuts` rather than `shorcuts`. - Should read `multiple` rather than `multuple`. - Should read `multiple` rather than `mulitple`. - Should read `indentation` rather than `indendation`. Closes #2420 --- CODING_STYLE.md | 2 +- evennia/commands/cmdhandler.py | 2 +- evennia/commands/default/syscommands.py | 2 +- evennia/contrib/building_menu.py | 2 +- evennia/prototypes/prototypes.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CODING_STYLE.md b/CODING_STYLE.md index 31d5ac06da..79b6a2c674 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -10,7 +10,7 @@ on your sources. ## A quick list of code style points - * 4-space indendation, NO TABS! + * 4-space indentation, NO TABS! * Unix line endings. * CamelCase is only used for classes, nothing else. * All non-global variable names and all function names are to be diff --git a/evennia/commands/cmdhandler.py b/evennia/commands/cmdhandler.py index 2f51ce5815..8715860df6 100644 --- a/evennia/commands/cmdhandler.py +++ b/evennia/commands/cmdhandler.py @@ -606,7 +606,7 @@ def cmdhandler( cmd.raw_string = unformatted_raw_string # cmd.obj # set via on-object cmdset handler for each command, # since this may be different for every command when - # merging multuple cmdsets + # merging multiple cmdsets if hasattr(cmd, "obj") and hasattr(cmd.obj, "scripts"): # cmd.obj is automatically made available by the cmdhandler. diff --git a/evennia/commands/default/syscommands.py b/evennia/commands/default/syscommands.py index b6291d94d0..fbbc215801 100644 --- a/evennia/commands/default/syscommands.py +++ b/evennia/commands/default/syscommands.py @@ -71,7 +71,7 @@ class SystemNoMatch(COMMAND_DEFAULT_CLASS): # -# Command called when there were mulitple matches to the command. +# Command called when there were multiple matches to the command. # class SystemMultimatch(COMMAND_DEFAULT_CLASS): """ diff --git a/evennia/contrib/building_menu.py b/evennia/contrib/building_menu.py index ee85af7811..94910d6d25 100644 --- a/evennia/contrib/building_menu.py +++ b/evennia/contrib/building_menu.py @@ -577,7 +577,7 @@ class BuildingMenu(object): keys_go_back = ["@"] # The keys allowing to go back in the menu tree sep_keys = "." # The key separator for menus with more than 2 levels joker_key = "*" # The special key meaning "anything" in a choice key - min_shortcut = 1 # The minimum length of shorcuts when `key` is not set + min_shortcut = 1 # The minimum length of shortcuts when `key` is not set def __init__( self, diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index a304dd8684..e6ea4a585b 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -653,7 +653,7 @@ def validate_prototype( ) ) - # recursively traverese prototype_parent chain + # recursively traverse prototype_parent chain for protstring in make_iter(prototype_parent): protstring = protstring.lower() From c3b1689210e95f2598c4b06144f09bcb73b2cb0a Mon Sep 17 00:00:00 2001 From: Alex Hunley Date: Mon, 24 May 2021 17:09:37 -0400 Subject: [PATCH 40/71] Fix priv check when displaying names in CmdWho --- evennia/commands/default/account.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index b54f99f8a5..68bb53b47f 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -521,11 +521,11 @@ class CmdWho(COMMAND_DEFAULT_CLASS): continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time - account = session.get_account() + session_account = session.get_account() puppet = session.get_puppet() location = puppet.location.key if puppet and puppet.location else "None" table.add_row( - utils.crop(account.get_display_name(account), width=25), + utils.crop(session_account.get_display_name(account), width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1), utils.crop(puppet.get_display_name(account) if puppet else "None", width=25), @@ -542,9 +542,9 @@ class CmdWho(COMMAND_DEFAULT_CLASS): continue delta_cmd = time.time() - session.cmd_last_visible delta_conn = time.time() - session.conn_time - account = session.get_account() + session_account = session.get_account() table.add_row( - utils.crop(account.get_display_name(account), width=25), + utils.crop(session_account.get_display_name(account), width=25), utils.time_format(delta_conn, 0), utils.time_format(delta_cmd, 1), ) From 298d1b589fb95e722cc63e8dea04e6ad64a8e753 Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 3 Jun 2021 23:25:10 +0200 Subject: [PATCH 41/71] Update obj.cmdset before examine. Resolves #2428. --- evennia/commands/default/building.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index a159a67d28..1fe39cb704 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -2511,7 +2511,8 @@ class CmdExamine(ObjManipCommand): locks_string = " Default" output["Locks"] = locks_string # cmdsets - if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"): + if current_cmdset and not ( + len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"): # all() returns a 'stack', so make a copy to sort. def _format_options(cmdset): @@ -2735,6 +2736,9 @@ class CmdExamine(ObjManipCommand): account = obj.account objct = obj + # this is usually handled when a command runs, but when we examine + # we may have leftover inherited cmdsets directly after a move etc. + obj.cmdset.update() # using callback to print results whenever function returns. get_and_merge_cmdsets( obj, session, account, objct, mergemode, self.raw_string From 5ee9e90cea3c808ac2f9cd341f9955322963eb89 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 13 Jun 2021 22:02:18 +0200 Subject: [PATCH 42/71] Fix some broken master-doc pages --- docs/source/Add-a-simple-new-web-page.md | 5 +- docs/source/Async-Process.md | 13 +- docs/source/Attributes.md | 3 +- docs/source/Bootstrap-&-Evennia.md | 3 +- docs/source/Coordinates.md | 9 +- docs/source/Customize-channels.md | 3 +- docs/source/Default-Command-Help.md | 315 ++++++------------ docs/source/Dialogues-in-events.md | 3 +- docs/source/Evennia-for-MUSH-Users.md | 16 +- docs/source/Game-Planning.md | 6 +- docs/source/Glossary.md | 8 +- docs/source/Help-System-Tutorial.md | 4 +- docs/source/How-To-Get-And-Give-Help.md | 25 +- docs/source/Links.md | 16 +- docs/source/Online-Setup.md | 5 +- docs/source/Portal-And-Server.md | 3 +- docs/source/Python-3.md | 85 +---- docs/source/Python-basic-introduction.md | 3 +- docs/source/Python-basic-tutorial-part-two.md | 51 ++- docs/source/Screenshot.md | 3 +- docs/source/Server-Conf.md | 4 +- docs/source/Static-In-Game-Map.md | 22 +- docs/source/Turn-based-Combat-System.md | 18 +- docs/source/Tutorial-World-Introduction.md | 10 +- docs/source/Tutorials.md | 20 +- docs/source/Unit-Testing.md | 55 ++- docs/source/Web-Character-Generation.md | 20 +- docs/source/Web-Character-View-Tutorial.md | 5 +- docs/source/Web-Features.md | 5 +- 29 files changed, 236 insertions(+), 502 deletions(-) diff --git a/docs/source/Add-a-simple-new-web-page.md b/docs/source/Add-a-simple-new-web-page.md index f960c5a3e9..c146a54f10 100644 --- a/docs/source/Add-a-simple-new-web-page.md +++ b/docs/source/Add-a-simple-new-web-page.md @@ -69,14 +69,13 @@ If you'd rather not take advantage of Evennia's base styles, you can do somethin ``` - + ### The URL When you enter the address `http://localhost:4001/story` in your web browser, Django will parse that field to figure out which page you want to go to. You tell it which patterns are relevant in the file -[mygame/web/urls.py](https://github.com/evennia/evennia/blob/master/evennia/game_template/web/urls.p -y). +[mygame/web/urls.py](https://github.com/evennia/evennia/blob/master/evennia/game_template/web/urls.py). Open it now. Django looks for the variable `urlpatterns` in this file. You want to add your new pattern to the diff --git a/docs/source/Async-Process.md b/docs/source/Async-Process.md index f994d63ea4..ae18635738 100644 --- a/docs/source/Async-Process.md +++ b/docs/source/Async-Process.md @@ -98,16 +98,16 @@ An example of making an asynchronous call from inside a [Command](./Commands) de class CmdAsync(Command): key = "asynccommand" - + def func(self): - + def long_running_function(): #[... lots of time-consuming code ...] return final_value - + def at_return_function(r): self.caller.msg("The final value is %s" % r) - + def at_err_function(e): self.caller.msg("There was an error: %s" % e) @@ -150,8 +150,7 @@ Wait 10 seconds and 'Test!' should be echoed back to you. ## The @interactive decorator -As of Evennia 0.9, the `@interactive` [decorator](https://realpython.com/primer-on-python- -decorators/) +As of Evennia 0.9, the `@interactive` [decorator](https://realpython.com/primer-on-python-decorators/) is available. This makes any function or method possible to 'pause' and/or await player input in an interactive way. @@ -160,7 +159,7 @@ in an interactive way. @interactive def myfunc(caller): - + while True: caller.msg("Getting ready to wait ...") yield(5) diff --git a/docs/source/Attributes.md b/docs/source/Attributes.md index e8f91e7529..4ca0e82566 100644 --- a/docs/source/Attributes.md +++ b/docs/source/Attributes.md @@ -237,8 +237,7 @@ entities you can loop over in a for-loop. Attribute-saving supports the followin * [Dicts](https://docs.python.org/2/tutorial/datastructures.html#dictionaries), like `{1:2, "test":]`. * [Sets](https://docs.python.org/2/tutorial/datastructures.html#sets), like `{1,2,"test",}`. -* [collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDi -ct), like `OrderedDict((1,2), ("test", ))`. +* [collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict), like `OrderedDict((1,2), ("test", ))`. * [collections.Deque](https://docs.python.org/2/library/collections.html#collections.deque), like `deque((1,2,"test",))`. * *Nestings* of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each diff --git a/docs/source/Bootstrap-&-Evennia.md b/docs/source/Bootstrap-&-Evennia.md index 8f9781af51..fd7db78909 100644 --- a/docs/source/Bootstrap-&-Evennia.md +++ b/docs/source/Bootstrap-&-Evennia.md @@ -97,5 +97,4 @@ docs](https://getbootstrap.com/docs/4.0/layout/grid/) ## More Bootstrap Bootstrap also provides a huge amount of utilities, as well as styling and content elements. To -learn more about them, please [read the Bootstrap docs](https://getbootstrap.com/docs/4.0/getting- -started/introduction/) or read one of our other web tutorials. \ No newline at end of file +learn more about them, please [read the Bootstrap docs](https://getbootstrap.com/docs/4.0/getting-started/introduction/) or read one of our other web tutorials. diff --git a/docs/source/Coordinates.md b/docs/source/Coordinates.md index 410b779c95..beed340e48 100644 --- a/docs/source/Coordinates.md +++ b/docs/source/Coordinates.md @@ -51,7 +51,7 @@ class Room(DefaultRoom): See examples/object.py for a list of properties and methods available on all Objects. """ - + @property def x(self): """Return the X coordinate or None.""" @@ -72,7 +72,7 @@ class Room(DefaultRoom): """Return the Y coordinate or None.""" y = self.tags.get(category="coordy") return int(y) if isinstance(y, str) else None - + @y.setter def y(self, y): """Change the Y coordinate.""" @@ -87,7 +87,7 @@ class Room(DefaultRoom): """Return the Z coordinate or None.""" z = self.tags.get(category="coordz") return int(z) if isinstance(z, str) else None - + @z.setter def z(self, z): """Change the Z coordinate.""" @@ -99,8 +99,7 @@ class Room(DefaultRoom): ``` If you aren't familiar with the concept of properties in Python, I encourage you to read a good -tutorial on the subject. [This article on Python properties](https://www.programiz.com/python- -programming/property) +tutorial on the subject. [This article on Python properties](https://www.programiz.com/python-programming/property) is well-explained and should help you understand the idea. Let's look at our properties for `x`. First of all is the read property. diff --git a/docs/source/Customize-channels.md b/docs/source/Customize-channels.md index aeed0f4cd4..9c755eec4e 100644 --- a/docs/source/Customize-channels.md +++ b/docs/source/Customize-channels.md @@ -480,5 +480,4 @@ close from the code I've provided here. Notice, however, that this resource is external to Evennia and not maintained by anyone but the original author of this article. -[Read the full example on Github](https://github.com/vincent- -lg/avenew/blob/master/commands/comms.py) +[Read the full example on Github](https://github.com/vincent-lg/avenew/blob/master/commands/comms.py) diff --git a/docs/source/Default-Command-Help.md b/docs/source/Default-Command-Help.md index 11aeea97a7..42601a0a69 100644 --- a/docs/source/Default-Command-Help.md +++ b/docs/source/Default-Command-Help.md @@ -12,8 +12,7 @@ information about how commands work can be found in the documentation for [Comma ## A-Z -- [`__unloggedin_look_command`](https://github.com/evennia/evennia/wiki/Default-Command- -Help#wiki-`--unloggedin-look-command`-cmdunconnectedlook) - look when in unlogged-in state +- [`__unloggedin_look_command`](https://github.com/evennia/evennia/wiki/Default-Command-Help#wiki-`--unloggedin-look-command`-cmdunconnectedlook) - look when in unlogged-in state - [about](./Default-Command-Help#wiki-about-cmdabout) - show Evennia info - [access](./Default-Command-Help#wiki-access-cmdaccess) - show your current game access - [addcom](./Default-Command-Help#wiki-addcom-cmdaddcom) - add a channel alias and/or subscribe to a @@ -137,14 +136,12 @@ another ## Command details These are generated from the auto-documentation and are ordered by their source file location in -[evennia/commands/default/](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ -) +[evennia/commands/default/](https://github.com/evennia/evennia/tree/master/evennia/commands/default/) ### `account.py` -[View account.py -source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py) +[View account.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py) #### charcreate (CmdCharCreate) @@ -165,8 +162,7 @@ source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdCharCreate` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### chardelete (CmdCharDelete) @@ -184,8 +180,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdCharDelete` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### color (CmdColorTest) @@ -207,8 +202,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdColorTest` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### ic (CmdIC) @@ -234,8 +228,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdIC` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### look (CmdOOCLook) @@ -253,8 +246,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdOOCLook` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### ooc (CmdOOC) @@ -274,8 +266,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdOOC` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### option (CmdOption) @@ -299,8 +290,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdOption` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### password (CmdPassword) @@ -318,8 +308,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdPassword` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### quell (CmdQuell) @@ -386,8 +375,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdSessions` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultSession'* of class `SessionCmdSet` in [cmdset_session.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_session.py). +Belongs to command set *'DefaultSession'* of class `SessionCmdSet` in [cmdset_session.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_session.py). #### style (CmdStyle) @@ -407,8 +395,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_session.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdStyle` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### who (CmdWho) @@ -428,14 +415,12 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdWho` in [account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). ### `admin.py` -[View admin.py -source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py) +[View admin.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py) #### ban (CmdBan) @@ -476,8 +461,7 @@ source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ - **[`help_category`](./Help-System):** *"Admin"* - **Source:** class `CmdBan` in [admin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### boot (CmdBoot) @@ -500,8 +484,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Admin"* - **Source:** class `CmdBoot` in [admin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### emit (CmdEmit) @@ -530,8 +513,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Admin"* - **Source:** class `CmdEmit` in [admin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### force (CmdForce) @@ -550,8 +532,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdForce` in [admin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### perm (CmdPerm) @@ -575,8 +556,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Admin"* - **Source:** class `CmdPerm` in [admin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### unban (CmdUnban) @@ -597,8 +577,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Admin"* - **Source:** class `CmdUnban` in [admin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### userpassword (CmdNewPassword) @@ -616,8 +595,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Admin"* - **Source:** class `CmdNewPassword` in [admin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### wall (CmdWall) @@ -636,14 +614,12 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Admin"* - **Source:** class `CmdWall` in [admin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/admin.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). ### `batchprocess.py` -[View batchprocess.py -source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/batchprocess.py) +[View batchprocess.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/batchprocess.py) #### batchcode (CmdBatchCode) @@ -668,10 +644,8 @@ source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ - **aliases:** *batchcodes* - **[locks](./Locks):** *"cmd:superuser()"* - **[`help_category`](./Help-System):** *"Building"* -- **Source:** class `CmdBatchCode` in [batchprocess.py](https://github.com/evennia/evennia/tree/mast -er/evennia/commands/default/batchprocess.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +- **Source:** class `CmdBatchCode` in [batchprocess.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/batchprocess.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### batchcommands (CmdBatchCommands) @@ -692,16 +666,13 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **aliases:** *batchcmd*, *batchcommand* - **[locks](./Locks):** *"cmd:perm(batchcommands) or perm(Developer)"* - **[`help_category`](./Help-System):** *"Building"* -- **Source:** class `CmdBatchCommands` in [batchprocess.py](https://github.com/evennia/evennia/tree/ -master/evennia/commands/default/batchprocess.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +- **Source:** class `CmdBatchCommands` in [batchprocess.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/batchprocess.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). ### `building.py` -[View building.py -source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py) +[View building.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py) #### alias (CmdSetObjAlias) @@ -732,8 +703,7 @@ source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdSetObjAlias` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### cmdsets (CmdListCmdSets) @@ -752,8 +722,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdListCmdSets` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### command (ObjManipCommand) @@ -807,8 +776,7 @@ Belongs to command set *''* of class `` in - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdCopy` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### cpattr (CmdCpAttr) @@ -839,8 +807,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdCpAttr` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### create (CmdCreate) @@ -871,8 +838,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdCreate` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### desc (CmdDesc) @@ -894,8 +860,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdDesc` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### destroy (CmdDestroy) @@ -925,8 +890,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdDestroy` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### dig (CmdDig) @@ -958,8 +922,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdDig` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### examine (CmdExamine) @@ -986,8 +949,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdExamine` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### find (CmdFind) @@ -1018,8 +980,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdFind` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### link (CmdLink) @@ -1048,8 +1009,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdLink` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### lock (CmdLock) @@ -1089,8 +1049,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdLock` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### mvattr (CmdMvAttr) @@ -1115,8 +1074,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdMvAttr` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### name (CmdName) @@ -1135,8 +1093,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdName` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### open (CmdOpen) @@ -1144,8 +1101,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara open a new exit from the current room Usage: - open [;alias;alias..][:typeclass] [,[;alias;..][:typeclass]]] = - + open [;alias;alias..][:typeclass] [,[;alias;..][:typeclass]]] = Handles the creation of exits. If a destination is given, the exit will point there. The argument sets up an exit at the @@ -1159,8 +1115,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdOpen` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### script (CmdScript) @@ -1188,8 +1143,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdScript` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### set (CmdSetAttribute) @@ -1237,8 +1191,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdSetAttribute` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### sethome (CmdSetHome) @@ -1262,8 +1215,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdSetHome` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### spawn (CmdSpawn) @@ -1334,8 +1286,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdSpawn` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### tag (CmdTag) @@ -1365,8 +1316,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdTag` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### tel (CmdTeleport) @@ -1402,8 +1352,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdTeleport` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### tunnel (CmdTunnel) @@ -1438,8 +1387,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdTunnel` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### typeclass (CmdTypeclass) @@ -1495,8 +1443,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdTypeclass` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### unlink (CmdUnLink) @@ -1515,8 +1462,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdUnLink` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### wipe (CmdWipe) @@ -1539,14 +1485,12 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdWipe` in [building.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/building.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). ### `comms.py` -[View comms.py -source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py) +[View comms.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py) #### addcom (CmdAddCom) @@ -1567,8 +1511,7 @@ source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdAddCom` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### allcom (CmdAllCom) @@ -1590,8 +1533,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdAllCom` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### cboot (CmdCBoot) @@ -1612,8 +1554,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdCBoot` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### ccreate (CmdChannelCreate) @@ -1631,8 +1572,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdChannelCreate` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### cdesc (CmdCdesc) @@ -1651,8 +1591,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdCdesc` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### cdestroy (CmdCdestroy) @@ -1670,8 +1609,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdCdestroy` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### cemit (CmdCemit) @@ -1695,8 +1633,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdCemit` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### channels (CmdChannels) @@ -1718,8 +1655,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdChannels` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### clock (CmdClock) @@ -1738,8 +1674,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdClock` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### cwho (CmdCWho) @@ -1757,8 +1692,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdCWho` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### delcom (CmdDelCom) @@ -1780,8 +1714,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdDelCom` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### irc2chan (CmdIRC2Chan) @@ -1820,8 +1753,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdIRC2Chan` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### page (CmdPage) @@ -1846,8 +1778,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdPage` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### rss2chan (CmdRSS2Chan) @@ -1880,14 +1811,12 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Comms"* - **Source:** class `CmdRSS2Chan` in [comms.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/comms.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). ### `general.py` -[View general.py -source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py) +[View general.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py) #### access (CmdAccess) @@ -1926,8 +1855,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdDrop` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### get (CmdGet) @@ -1946,8 +1874,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdGet` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### give (CmdGive) @@ -1966,8 +1893,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdGive` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### home (CmdHome) @@ -1985,8 +1911,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdHome` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### inventory (CmdInventory) @@ -2005,8 +1930,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdInventory` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### look (CmdLook) @@ -2026,8 +1950,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdLook` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### nick (CmdNick) @@ -2077,8 +2000,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdNick` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### pose (CmdPose) @@ -2103,8 +2025,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdPose` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### say (CmdSay) @@ -2122,8 +2043,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdSay` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### setdesc (CmdSetDesc) @@ -2143,8 +2063,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdSetDesc` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### whisper (CmdWhisper) @@ -2164,8 +2083,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdWhisper` in [general.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/general.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). ### `help.py` @@ -2192,8 +2110,7 @@ source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdHelp` in [help.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/help.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### sethelp (CmdSetHelp) @@ -2227,14 +2144,12 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"Building"* - **Source:** class `CmdSetHelp` in [help.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/help.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). ### `system.py` -[View system.py -source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py) +[View system.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py) #### about (CmdAbout) @@ -2252,8 +2167,7 @@ source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdAbout` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### objects (CmdObjects) @@ -2273,8 +2187,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdObjects` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### py (CmdPy) @@ -2329,8 +2242,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdPy` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### reload (CmdReload) @@ -2350,8 +2262,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdReload` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### reset (CmdReset) @@ -2379,8 +2290,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdReset` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### scripts (CmdScripts) @@ -2410,8 +2320,7 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdScripts` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### server (CmdServerLoad) @@ -2455,8 +2364,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdServerLoad` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### service (CmdService) @@ -2484,8 +2392,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdService` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). #### shutdown (CmdShutdown) @@ -2503,8 +2410,7 @@ s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_chara - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdShutdown` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). +Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). #### time (CmdTime) @@ -2523,14 +2429,12 @@ thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py) - **[`help_category`](./Help-System):** *"System"* - **Source:** class `CmdTime` in [system.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/system.py). -Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](http -s://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). +Belongs to command set *'DefaultCharacter'* of class `CharacterCmdSet` in [cmdset_character.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_character.py). ### `unloggedin.py` -[View unloggedin.py -source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/unloggedin.py) +[View unloggedin.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/unloggedin.py) #### __unloggedin_look_command (CmdUnconnectedLook) @@ -2551,8 +2455,7 @@ source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/ - **[`help_category`](./Help-System):** *"General"* - **Source:** class `CmdUnconnectedLook` in [unloggedin.py](https://github.com/evennia/evennia/tree/ master/evennia/commands/default/unloggedin.py). -Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](h -ttps://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). +Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). *OBS: This is a [[System Command|Commands]]. System commands have fixed keys and are called by the server in specific situations.* @@ -2573,10 +2476,8 @@ server in specific situations.* - **aliases:** *con*, *conn*, *co* - **[locks](./Locks):** *"cmd:all()"* - **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdUnconnectedConnect` in [unloggedin.py](https://github.com/evennia/evennia/tr -ee/master/evennia/commands/default/unloggedin.py). -Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](h -ttps://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). +- **Source:** class `CmdUnconnectedConnect` in [unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/unloggedin.py). +Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). #### create (CmdUnconnectedCreate) @@ -2595,10 +2496,8 @@ ttps://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_un - **aliases:** *cre*, *cr* - **[locks](./Locks):** *"cmd:all()"* - **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdUnconnectedCreate` in [unloggedin.py](https://github.com/evennia/evennia/tre -e/master/evennia/commands/default/unloggedin.py). -Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](h -ttps://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). +- **Source:** class `CmdUnconnectedCreate` in [unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/unloggedin.py). +Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). #### help (CmdUnconnectedHelp) @@ -2615,10 +2514,8 @@ ttps://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_un - **aliases:** *?*, *h* - **[locks](./Locks):** *"cmd:all()"* - **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdUnconnectedHelp` in [unloggedin.py](https://github.com/evennia/evennia/tree/ -master/evennia/commands/default/unloggedin.py). -Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](h -ttps://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). +- **Source:** class `CmdUnconnectedHelp` in [unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/unloggedin.py). +Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). #### quit (CmdUnconnectedQuit) @@ -2636,8 +2533,6 @@ ttps://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_un - **aliases:** *qu*, *q* - **[locks](./Locks):** *"cmd:all()"* - **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdUnconnectedQuit` in [unloggedin.py](https://github.com/evennia/evennia/tree/ -master/evennia/commands/default/unloggedin.py). -Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](h -ttps://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). +- **Source:** class `CmdUnconnectedQuit` in [unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/unloggedin.py). +Belongs to command set *'DefaultUnloggedin'* of class `UnloggedinCmdSet` in [cmdset_unloggedin.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_unloggedin.py). diff --git a/docs/source/Dialogues-in-events.md b/docs/source/Dialogues-in-events.md index 9e4760c6ba..f3bb5c0d06 100644 --- a/docs/source/Dialogues-in-events.md +++ b/docs/source/Dialogues-in-events.md @@ -236,8 +236,7 @@ another. - **Q:** can I have two characters answering to the same dialogue in exactly the same way? - **A:** It's possible but not so easy to do. Usually, event grouping is set in code, and depends on different games. However, if it is for some infrequent occurrences, it's easy to do using -[chained events](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README -.md#chained-events). +[chained events](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md#chained-events). - **Q:** is it possible to deploy callbacks on all characters sharing the same prototype? - **A:** not out of the box. This depends on individual settings in code. One can imagine that all characters of some type would share some events, but this is game-specific. Rooms of the same zone diff --git a/docs/source/Evennia-for-MUSH-Users.md b/docs/source/Evennia-for-MUSH-Users.md index ad579dc68c..db742eb0e0 100644 --- a/docs/source/Evennia-for-MUSH-Users.md +++ b/docs/source/Evennia-for-MUSH-Users.md @@ -88,8 +88,8 @@ another which is again somewhat remniscent at least of the *effect* of `@parent based inheritance of MUSH. There are other differences for sure, but that should give some feel for things. Enough with the -theory. Let's get down to more practical matters next. To install, see the [Getting Started -instructions](Getting-Started). +theory. Let's get down to more practical matters next. To install, see the +[Getting Started instructions](./Getting-Started). ## A first step making things more familiar @@ -138,8 +138,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet): Note that Python cares about indentation, so make sure to indent with the same number of spaces as shown above! -So what happens above? We [import the module](http://www.linuxtopia.org/online_books/programming_boo -ks/python_programming/python_ch28s03.html) `evennia/contrib/multidescer.py` at the top. Once +So what happens above? We [import the module](http://www.linuxtopia.org/online_books/programming_books/python_programming/python_ch28s03.html) `evennia/contrib/multidescer.py` at the top. Once imported we can access stuff inside that module using full stop (`.`). The multidescer is defined as a class `CmdMultiDesc` (we could find this out by opening said module in a text editor). At the bottom we create a new instance of this class and add it to the `CharacterCmdSet` class. For the @@ -206,17 +205,14 @@ developer changing the underlying Python code. If you are a *Developer* and are interested in making a more MUSH-like Evennia game, a good start is to look into the Evennia [Tutorial for a first MUSH-like game](./Tutorial-for-basic-MUSH-like-game). That steps through building a simple little game from scratch and helps to acquaint you with the -various corners of Evennia. There is also the [Tutorial for running roleplaying sessions](Evennia- -for-roleplaying-sessions) that can be of interest. +various corners of Evennia. There is also the [Tutorial for running roleplaying sessions](./Evennia-for-roleplaying-sessions) that can be of interest. An important aspect of making things more familiar for *Players* is adding new and tweaking existing -commands. How this is done is covered by the [Tutorial on adding new commands](Adding-Command- -Tutorial). You may also find it useful to shop through the `evennia/contrib/` folder. The [Tutorial +commands. How this is done is covered by the [Tutorial on adding new commands](./Adding-Command-Tutorial). You may also find it useful to shop through the `evennia/contrib/` folder. The [Tutorial world](Tutorial-World-Introduction) is a small single-player quest you can try (it’s not very MUSH- like but it does show many Evennia concepts in action). Beyond that there are [many more tutorials](Tutorials) to try out. If you feel you want a more visual overview you can also look at [Evennia in pictures](https://evennia.blogspot.se/2016/05/evennia-in-pictures.html). -… And of course, if you need further help you can always drop into the [Evennia chatroom](http://web -chat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) or post a +… And of course, if you need further help you can always drop into the [Evennia chatroom](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) or post a question in our [forum/mailing list](https://groups.google.com/forum/#%21forum/evennia)! diff --git a/docs/source/Game-Planning.md b/docs/source/Game-Planning.md index f272d978f9..f5e2f6e663 100644 --- a/docs/source/Game-Planning.md +++ b/docs/source/Game-Planning.md @@ -4,11 +4,7 @@ So you have Evennia up and running. You have a great game idea in mind. Now it's time to start cracking! But where to start? Here are some ideas for a workflow. Note that the suggestions on this page are just that - suggestions. Also, they are primarily aimed at a lone hobby designer or a small -team developing a game in their free time. There is an article in the Imaginary Realities e-zine -which was written by the Evennia lead dev. It focuses more on you finding out your motivations for -making a game - you can [read the article here](http://journal.imaginary- -realities.com/volume-07/issue-03/where-do-i-begin/index.html). - +team developing a game in their free time. Below are some minimal steps for getting the first version of a new game world going with players. It's worth to at least make the attempt to do these steps in order even if you are itching to jump diff --git a/docs/source/Glossary.md b/docs/source/Glossary.md index cb93d7c731..65ea8eca11 100644 --- a/docs/source/Glossary.md +++ b/docs/source/Glossary.md @@ -90,7 +90,7 @@ monsters and other NPCs. You can [read more about it here](./Objects#subclasses- similar to Rails for the Ruby language. It is one of Evennia's central library dependencies (the other one is [Twisted](./Glossary#twisted)). Evennia uses Django for two main things - to map all database operations to Python and for structuring our web site. - + Through Django, we can work with any supported database (SQlite3, Postgres, MySQL ...) using generic Python instead of database-specific SQL: A database table is represented in Django as a Python class (called a *model*). An Python instance of such a class represents a row in that table. @@ -204,8 +204,7 @@ infrastructure. Git itself is a separate project. ### _object_ -In general Python (and other [object-oriented languages](https://en.wikipedia.org/wiki/Object- -oriented_programming)), an `object` is what we call the instance of a *class*. But one of Evennia's +In general Python (and other [object-oriented languages](https://en.wikipedia.org/wiki/Object-oriented_programming)), an `object` is what we call the instance of a *class*. But one of Evennia's core [typeclasses](./Glossary#typeclasss) is also called "Object". To separate these in the docs we try to use `object` to refer to the general term and capitalized `Object` when we refer to the typeclass. @@ -246,8 +245,7 @@ not have to be. ### _property_ A _property_ is a general term used for properties on any Python object. The term also sometimes -refers to the `property` built-in function of Python ([read more here](https://www.python- -course.eu/python3_properties.php)). Note the distinction between properties, +refers to the `property` built-in function of Python ([read more here](https://www.python-course.eu/python3_properties.php)). Note the distinction between properties, [fields](./Glossary#field) and [Attributes](./Glossary#attribute). ### _repository_ diff --git a/docs/source/Help-System-Tutorial.md b/docs/source/Help-System-Tutorial.md index c191838bd0..3d570c98c5 100644 --- a/docs/source/Help-System-Tutorial.md +++ b/docs/source/Help-System-Tutorial.md @@ -1,9 +1,7 @@ # Help System Tutorial -**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](Web- -Tutorial).** Reading the three first parts of the [Django -tutorial](https://docs.djangoproject.com/en/2.2/) might help as well. +**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](./Web-Tutorial).** Reading the three first parts of the [Django tutorial](https://docs.djangoproject.com/en/2.2/) might help as well. This tutorial will show you how to access the help system through your website. Both help commands and regular help entries will be visible, depending on the logged-in user or an anonymous character. diff --git a/docs/source/How-To-Get-And-Give-Help.md b/docs/source/How-To-Get-And-Give-Help.md index 03de9701b7..dd3d817c6f 100644 --- a/docs/source/How-To-Get-And-Give-Help.md +++ b/docs/source/How-To-Get-And-Give-Help.md @@ -4,12 +4,12 @@ ### How to *get* Help If you cannot find what you are looking for in the [online documentation](./index), here's what to do: - + - If you think the documentation is not clear enough and are short on time, fill in our quick little [online form][form] and let us know - no login required. Maybe the docs need to be improved or a new tutorial added! Note that while this form is useful as a suggestion box we cannot answer questions or reply to you. Use the discussion group or chat (linked below) if you want feedback. -- If you have trouble with a missing feature or a problem you think is a bug, go to the +- If you have trouble with a missing feature or a problem you think is a bug, go to the [issue tracker][issues] and search to see if has been reported/suggested already. If you can't find an existing entry create a new one. - If you need help, want to start a discussion or get some input on something you are working on, @@ -28,8 +28,8 @@ eventually! Evennia is a completely non-funded project. It relies on the time donated by its users and developers in order to progress. -The first and easiest way you as a user can help us out is by taking part in [community -discussions][group] and by giving feedback on what is good or bad. Report bugs you find and features +The first and easiest way you as a user can help us out is by taking part in +[community discussions][group] and by giving feedback on what is good or bad. Report bugs you find and features you lack to our [issue tracker][issues]. Just the simple act of letting developers know you are out there using their program is worth a lot. Generally mentioning and reviewing Evennia elsewhere is also a nice way to spread the word. @@ -50,24 +50,19 @@ fixed (if you want to put up a bounty yourself you can do so via our page on github. ... And finally, if you want to help motivate and support development you can also drop some coins -in the developer's cup. You can [make a donation via PayPal][paypal] or, even better, [become an -Evennia patron on Patreon][patreon]! This is a great way to tip your hat and show that you +in the developer's cup. You can [make a donation via PayPal][paypal] or, even better, +[become an Evennia patron on Patreon][patreon]! This is a great way to tip your hat and show that you appreciate the work done with the server! Finally, if you want to encourage the community to resolve a particular -[form]: https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZG -c6MQ#gid=0 +[form]: https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0 [group]: http://groups.google.com/group/evennia/ [issues]: https://github.com/evennia/evennia/issues -[issues-master]: https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%2 -0label%3Abug%20label%3Amaster-branch +[issues-master]: https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Abug%20label%3Amaster-branch [chat]: http://webchat.freenode.net/?channels=evennia -[paypal]: https://www.paypal.com/se/cgi-bin/webscr?cmd=_flow&SESSION=Z-VlOvfGjYq2qvCDOUGpb6C8Due7skT -0qOklQEy5EbaD1f0eyEQaYlmCc8O&dispatch=5885d80a13c0db1f8e263663d3faee8d64ad11bbf4d2a5a1a0d303a50933f9 +[paypal]: https://www.paypal.com/se/cgi-bin/webscr?cmd=_flow&SESSION=Z-VlOvfGjYq2qvCDOUGpb6C8Due7skT0qOklQEy5EbaD1f0eyEQaYlmCc8O&dispatch=5885d80a13c0db1f8e263663d3faee8d64ad11bbf4d2a5a1a0d303a50933f9 b2 -[donate-img]: http://images-focus-opensocial.googleusercontent.com/gadgets/proxy?url=https://www.pay -palobjects.com/en%255fUS/SE/i/btn/btn%255fdonateCC%255fLG.gif&container=focus&gadget=a&rewriteMime=i -mage/* +[donate-img]: http://images-focus-opensocial.googleusercontent.com/gadgets/proxy?url=https://www.paypalobjects.com/en%255fUS/SE/i/btn/btn%255fdonateCC%255fLG.gif&container=focus&gadget=a&rewriteMime=image/* [patreon]: https://www.patreon.com/griatch [patreon-img]: http://www.evennia.com/_/rsrc/1424724909023/home/evennia_patreon_100x100.png [issues-bounties]: https://github.com/evennia/evennia/labels/bounty diff --git a/docs/source/Links.md b/docs/source/Links.md index 92ca8974f1..e0945bdcb4 100644 --- a/docs/source/Links.md +++ b/docs/source/Links.md @@ -6,8 +6,7 @@ - [evennia.com](http://www.evennia.com) - Main Evennia portal page. Links to all corners of Evennia. - [Evennia github page](https://github.com/evennia/evennia) - Download code and read documentation. -- [Evennia official chat channel](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRy -dWUmMTE9MTk1JjEyPXRydWUbb) - Our official IRC chat #evennia at irc.freenode.net:6667. +- [Evennia official chat channel](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) - Our official IRC chat #evennia at irc.freenode.net:6667. - [Evennia forums/mailing list](http://groups.google.com/group/evennia) - Web interface to our google group. - [Evennia development blog](http://evennia.blogspot.se/) - Musings from the lead developer. @@ -106,8 +105,7 @@ Influential mailing list active 1996-2004. Advanced game design discussions. - [Mud-dev wiki](http://mud-dev.wikidot.com/) - A (very) slowly growing resource on MUD creation. - [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD telnet protocols. -- [Mud Tech's fun/cool but ...](http://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget- -ship-damned-thing/) - Greg Taylor gives good advice on mud design. +- [Mud Tech's fun/cool but ...](http://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-ship-damned-thing/) - Greg Taylor gives good advice on mud design. - [Lost Library of MOO](http://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in particular moo). - [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) - @@ -118,14 +116,12 @@ articles (not MUD-specific) - [The Alexandrian](http://thealexandrian.net/) - A blog about tabletop roleplaying and board games, but with lots of general discussion about rule systems and game balance that could be applicable also for MUDs. -- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world- -design/the-laws-of-online-world-design/) - thought-provoking guidelines and things to think about +- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world-design/the-laws-of-online-world-design/) - thought-provoking guidelines and things to think about when designing a virtual multiplayer world (Raph is known for *Ultima Online* among other things). ### Literature -- Richard Bartle *Designing Virtual Worlds* ([amazon page](http://www.amazon.com/Designing-Virtual- -Worlds-Richard-Bartle/dp/0131018167)) - Essential reading for the design of any persistent game +- Richard Bartle *Designing Virtual Worlds* ([amazon page](http://www.amazon.com/Designing-Virtual-Worlds-Richard-Bartle/dp/0131018167)) - Essential reading for the design of any persistent game world, written by the co-creator of the original game *MUD*. Published in 2003 but it's still as relevant now as when it came out. Covers everything you need to know and then some. - Zed A. Shaw *Learn Python the Hard way* ([homepage](https://learnpythonthehardway.org/)) - Despite @@ -161,7 +157,7 @@ economic system. - [GIT](http://git-scm.com/) - [Documentation](http://git-scm.com/documentation) - [Learn GIT in 15 minutes](http://try.github.io/levels/1/challenges/1) (interactive tutorial) - + ### Python Info - [Python Website](http://www.python.org/) @@ -177,4 +173,4 @@ programming curriculum for different skill levels - Wiki [Home](./index) Icons made by [Freepik](http://www.freepik.com"-title="Freepik">Freepik) from [flaticon.com](http://www.flaticon.com), licensed under [Creative Commons BY -3.0](http://creativecommons.org/licenses/by/3.0). \ No newline at end of file +3.0](http://creativecommons.org/licenses/by/3.0). diff --git a/docs/source/Online-Setup.md b/docs/source/Online-Setup.md index 52a0b5f8a5..998488fcb4 100644 --- a/docs/source/Online-Setup.md +++ b/docs/source/Online-Setup.md @@ -238,8 +238,7 @@ Also, on Freenode visit the #letsencrypt channel for assistance from the communi additional resource, Let's Encrypt has a very active [community forum](https://community.letsencrypt.org/). -[A blog where someone sets up Let's Encrypt](https://www.digitalocean.com/community/tutorials/how- -to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04) +[A blog where someone sets up Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04) The only process missing from all of the above documentation is how to pass verification. This is how Let's Encrypt verifies that you have control over your domain (not necessarily ownership, it's @@ -415,7 +414,7 @@ Evennia users: | `Linode`_ | Cloud | $5/month / | Multiple regions. Smallest option provides 1GB RAM | | | | on-demand | | +------------------------+----------------+----------------+----------------------------------------------------------+ - | `Genesis MUD hosting`_ | Shell account | $8/month | Dedicated MUD host with very limited memory offerings. | + | `Genesis MUD hosting`_ | Shell account | $8/month | Dedicated MUD host with very limited memory offerings. | | | | | When last investigated (2017) ran a 13 years old Python | | | | | version (2.4) (convince them to update or compile). | | | | | Note that Evennia needs *at least* the "Deluxe" package | diff --git a/docs/source/Portal-And-Server.md b/docs/source/Portal-And-Server.md index 22aa115db3..03ad6de9bc 100644 --- a/docs/source/Portal-And-Server.md +++ b/docs/source/Portal-And-Server.md @@ -8,8 +8,7 @@ If you are new to the concept, the main purpose of separating the two is to have the Portal but keep the MUD running on the Server. This way one can restart/reload the game (the Server part) without Accounts getting disconnected. -![portal and server layout](https://474a3b9f-a-62cb3a1a-s- -sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia_server_portal.png) +![portal and server layout](https://474a3b9f-a-62cb3a1a-s-sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia_server_portal.png) The Server and Portal are glued together via an AMP (Asynchronous Messaging Protocol) connection. This allows the two programs to communicate seamlessly. diff --git a/docs/source/Python-3.md b/docs/source/Python-3.md index 10cbd03093..96b3479a83 100644 --- a/docs/source/Python-3.md +++ b/docs/source/Python-3.md @@ -1,87 +1,4 @@ # Python 3 -> *Note: Evennia only supports Python 2.7+ at this time. This page gathers various development -information relevant to server developers.* +Evennia supports Python 3+ since v0.8. This page is deprecated. -Django can work with Python 2 and 3 already, though changes may be required to how the Evennia code -uses it. Twisted has much Python 3 compatibility, but not all modules within it have been ported -yet. The -[twisted.python.dist3](https://twistedmatrix.com/documents/current/api/twisted.python.dist3.html) -module gives some information about what's ported, and I'm compiling a list of missing modules with -related bug reports which can be found below. The list is based on a search for import statements in -the Evennia source code, please add anything that's missing. - -Part of this process is expected to be writing more tests for Evennia. One encouraging recent port -to Python 3 in Twisted is its Trial test framework, which may need to be used by Evennia to ensure -it still works correctly with Twisted on Python 3. - -# "Strings" -Broadly (and perhaps over-simplified): - -* Twisted [expects bytes](http://twistedmatrix.com/trac/wiki/FrequentlyAskedQuestions#WhydontTwisted -snetworkmethodssupportUnicodeobjectsaswellasstrings) -* Django [expects "" to be unicode](https://docs.djangoproject.com/en/1.8/topics/python3/#unicode- -literals) - -I think we should use (roughly speaking) "" for unicode and b"" for bytes everywhere, but I need to -look at the impacts of this more closely. - -# Links - -* http://twistedmatrix.com/documents/current/core/howto/python3.html -* https://twistedmatrix.com/trac/wiki/Plan/Python3 -* [Twisted Python3 bugs](https://twistedmatrix.com/trac/query?status=assigned&status=new&status=reop -ened&group=status&milestone=Python-3.x) - -# Twisted module status - -x = not ported to Python 3 -/ = ported to Python 3 - -* twisted.application.internet / -* twisted.application.service / -* twisted.conch x (not used directly) - * ~https://twistedmatrix.com/trac/ticket/5102~ / - * ~https://twistedmatrix.com/trac/ticket/4993~ / -* twisted.conch.insults.insults x -* twisted.conch.interfaces x -* twisted.conch.manhole x -* twisted.conch.manhole_ssh x -* twisted.conch.ssh.common x -* twisted.conch.ssh.keys x - * ~https://twistedmatrix.com/trac/ticket/7998~ / - * "twisted.conch.ssh.keys should be ported to Python 3" -* twisted.conch.ssh.userauth x -* twisted.conch.telnet x -* twisted.cred.checkers / -* twisted.cred.portal / -* twisted.internet.defer / -* twisted.internet.interfaces / -* twisted.internet.protocol / -* twisted.internet.reactor / -* twisted.internet.ssl / -* twisted.internet.task / -* twisted.internet.threads / -* twisted.protocols.amp x - * ~https://twistedmatrix.com/trac/ticket/6833~ / - * "Port twisted.protocols.amp to Python 3" -* twisted.protocols.policies / -* twisted.python.components / -* twisted.python.log / -* twisted.python.threadpool / -* twisted.web.http (x) - * Partial support. Sufficient? -* twisted.web.resource / -* twisted.web.server (x) - * Partial support. Sufficient? -* twisted.web.static / -* twisted.web.proxy / -* twisted.web.wsgi x - * ~https://twistedmatrix.com/trac/ticket/7993~ / - * "'twisted.web.wsgi' should be ported to Python 3" - * Seems to be making good progress -* twisted.words.protocols.irc x - * https://twistedmatrix.com/trac/ticket/6320 - * "Python 3 support for twisted.words.protocols.irc" - * ~https://twistedmatrix.com/trac/ticket/6564~ - * "Replace usage of builtin reduce in twisted.words" diff --git a/docs/source/Python-basic-introduction.md b/docs/source/Python-basic-introduction.md index 759aa570d8..f79688367f 100644 --- a/docs/source/Python-basic-introduction.md +++ b/docs/source/Python-basic-introduction.md @@ -196,8 +196,7 @@ your life a lot easier. This is a [reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html); try not to use these words anywhere else. - A function name can not have spaces but otherwise we could have called it almost anything. We call -it `hello_world`. Evennia follows [Python's standard naming -style](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#a-quick-list-of-code-style- +it `hello_world`. Evennia follows [Python's standard naming style](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#a-quick-list-of-code-style- points) with lowercase letters and underscores. Use this style for now. - `who` is what we call the *argument* to our function. Arguments are variables we pass to the function. We could have named it anything and we could also have multiple arguments separated by diff --git a/docs/source/Python-basic-tutorial-part-two.md b/docs/source/Python-basic-tutorial-part-two.md index 7eebbd04d3..a95bb74d7f 100644 --- a/docs/source/Python-basic-tutorial-part-two.md +++ b/docs/source/Python-basic-tutorial-part-two.md @@ -55,8 +55,7 @@ A *class* is like a "factory" or blueprint. From a class you then create individ if class is`Dog`, an instance of `Dog` might be `fido`. Our in-game persona is of a class `Character`. The superuser `christine` is an *instance* of the `Character` class (an instance is also often referred to as an *object*). This is an important concept in *object oriented -programming*. You are wise to [familiarize yourself with it](https://en.wikipedia.org/wiki/Class- -based_programming) a little. +programming*. You are wise to [familiarize yourself with it](https://en.wikipedia.org/wiki/Class-based_programming) a little. > In other terms: > * class: A description of a thing, all the methods (code) and data (information) @@ -176,8 +175,7 @@ also explore it [online on github](https://github.com/evennia/evennia/tree/maste The structure of the library directly reflects how you import from it. -- To, for example, import [the text justify -function](https://github.com/evennia/evennia/blob/master/evennia/utils/utils.py#L201) from +- To, for example, import [the text justify function](https://github.com/evennia/evennia/blob/master/evennia/utils/utils.py#L201) from `evennia/utils/utils.py` you would do `from evennia.utils.utils import justify`. In your code you could then just call `justify(...)` to access its functionality. - You could also do `from evennia.utils import utils`. In code you would then have to write @@ -191,15 +189,13 @@ import in Python. Now, remember that our `characters.py` module did `from evennia import DefaultCharacter`. But if we look at the contents of the `evennia` folder, there is no `DefaultCharacter` anywhere! This is -because Evennia gives a large number of optional "shortcuts", known as [the "flat" API](Evennia- -API). The intention is to make it easier to remember where to find stuff. The flat API is defined in +because Evennia gives a large number of optional "shortcuts", known as [the "flat" API](./Evennia-API). The intention is to make it easier to remember where to find stuff. The flat API is defined in that weirdly named `__init__.py` file. This file just basically imports useful things from all over Evennia so you can more easily find them in one place. We could [just look at the documenation](github:evennia#typeclasses) to find out where we can look at our `DefaultCharacter` parent. But for practice, let's figure it out. Here is where -`DefaultCharacter` [is imported -from](https://github.com/evennia/evennia/blob/master/evennia/__init__.py#L188) inside `__init__.py`: +`DefaultCharacter` [is imported from](https://github.com/evennia/evennia/blob/master/evennia/__init__.py#L188) inside `__init__.py`: ```python from .objects.objects import DefaultCharacter @@ -231,8 +227,7 @@ is the same thing, just a little easier to remember. In the previous section we traced the parent of our `Character` class to be `DefaultCharacter` in -[evennia/objects/objects.py](https://github.com/evennia/evennia/blob/master/evennia/objects/objects. -py). +[evennia/objects/objects.py](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py). Open that file and locate the `DefaultCharacter` class. It's quite a bit down in this module so you might want to search using your editor's (or browser's) search function. Once you find it, you'll find that the class starts like this: @@ -276,8 +271,7 @@ outside ``` -... And so on (you can see the full [class online -here](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L1915)). Here we +... And so on (you can see the full [class online here](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L1915)). Here we have functional code! These methods may not be directly visible in `Character` back in our game dir, but they are still available since `Character` is a child of `DefaultCharacter` above. Here is a brief summary of the methods we find in `DefaultCharacter` (follow in the code to see if you can see @@ -309,8 +303,7 @@ class DefaultCharacter(DefaultObject): This means that `DefaultCharacter` is in *itself* a child of something called `DefaultObject`! Let's see what this parent class provides. It's in the same module as `DefaultCharacter`, you just need to -[scroll up near the -top](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L193): +[scroll up near the top](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L193): ```python class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): @@ -327,11 +320,9 @@ the doc string of `msg`. > As seen, `DefaultObject` actually has multiple parents. In one of those the basic `key` property is defined, but we won't travel further up the inheritance tree in this tutorial. If you are -interested to see them, you can find `TypeclassBase` in [evennia/typeclasses/models.py](https://gith -ub.com/evennia/evennia/blob/master/evennia/typeclasses/models.py#L93) and `ObjectDB` in [evennia/obj +interested to see them, you can find `TypeclassBase` in [evennia/typeclasses/models.py](https://github.com/evennia/evennia/blob/master/evennia/typeclasses/models.py#L93) and `ObjectDB` in [evennia/obj ects/models.py](https://github.com/evennia/evennia/blob/master/evennia/objects/models.py#L121). We -will also not go into the details of [Multiple -Inheritance](https://docs.python.org/2/tutorial/classes.html#multiple-inheritance) or +will also not go into the details of [Multiple Inheritance](https://docs.python.org/2/tutorial/classes.html#multiple-inheritance) or [Metaclasses](http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html) here. The general rule is that if you realize that you need these features, you already know enough to use them. @@ -384,8 +375,7 @@ By default the `at_before_say` method doesn't do anything. It just takes the `me `return`s it just the way it was (the `return` is another reserved Python word). > We won't go into `**kwargs` here, but it (and its sibling `*args`) is also important to -understand, extra reading is [here for -`**kwargs`](https://stackoverflow.com/questions/1769403/understanding-kwargs-in-python). +understand, extra reading is [here for `**kwargs`](https://stackoverflow.com/questions/1769403/understanding-kwargs-in-python). Now, open your game folder and edit `mygame/typeclasses/characters.py`. Locate your `Character` class and modify it as such: @@ -407,8 +397,7 @@ through the method. Note that `f` in front of the string, it means we turned the string into a 'formatted string'. We can now easily inject stuff directly into the string by wrapping them in curly brackets `{ }`. In this example, we put the incoming `message` into the string, followed by an ellipsis. This is only -one way to format a string. Python has very powerful [string -formatting](https://docs.python.org/2/library/string.html#format-specification-mini-language) and +one way to format a string. Python has very powerful [string formatting](https://docs.python.org/2/library/string.html#format-specification-mini-language) and you are wise to learn it well, considering your game will be mainly text-based. > You could also copy & paste the relevant method from `DefaultObject` here to get the full doc @@ -454,8 +443,7 @@ program. IPython ... ... - In [1]: -IPython has some very nice ways to explore what Evennia has to offer. + In [1]: IPython has some very nice ways to explore what Evennia has to offer. > import evennia > evennia. @@ -489,15 +477,14 @@ and resources [on our link page](./Links). We have touched upon many of the concepts here but to use Evennia and to be able to follow along in the code, you will need basic understanding of Python [modules](http://docs.python.org/2/tutorial/modules.html), -[variables](http://www.tutorialspoint.com/python/python_variable_types.htm), [conditional -statements](http://docs.python.org/tutorial/controlflow.html#if-statements), +[variables](http://www.tutorialspoint.com/python/python_variable_types.htm), +[conditional statements](http://docs.python.org/tutorial/controlflow.html#if-statements), [loops](http://docs.python.org/tutorial/controlflow.html#for-statements), -[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions), [lists, -dictionaries, list comprehensions](http://docs.python.org/tutorial/datastructures.html) and [string -formatting](http://docs.python.org/tutorial/introduction.html#strings). You should also have a basic -understanding of [object-oriented -programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) and what Python -[Classes](http://docs.python.org/tutorial/classes.html) are. +[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions), +[lists, dictionaries, list comprehensions](http://docs.python.org/tutorial/datastructures.html) +and [string formatting](http://docs.python.org/tutorial/introduction.html#strings). You should also have a basic +understanding of [object-oriented programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) +and what Python [Classes](http://docs.python.org/tutorial/classes.html) are. Once you have familiarized yourself, or if you prefer to pick Python up as you go, continue to one of the beginning-level [Evennia tutorials](./Tutorials) to gradually build up your understanding. diff --git a/docs/source/Screenshot.md b/docs/source/Screenshot.md index 71352aac94..29bd21024f 100644 --- a/docs/source/Screenshot.md +++ b/docs/source/Screenshot.md @@ -1,8 +1,7 @@ # Screenshot -![evennia_screenshot2017](https://user- -images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png) +![evennia_screenshot2017](https://user-images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png) *(right-click and choose your browser's equivalent of "view image" to see it full size)* This screenshot shows a vanilla [install](./Getting-Started) of the just started Evennia MUD server. diff --git a/docs/source/Server-Conf.md b/docs/source/Server-Conf.md index eedb0b3d3e..b0b6afe19f 100644 --- a/docs/source/Server-Conf.md +++ b/docs/source/Server-Conf.md @@ -10,8 +10,8 @@ The "Settings" file referenced throughout the documentation is the file `mygame/server/conf/settings.py`. This is automatically created on the first run of `evennia --init` (see the [Getting Started](./Getting-Started) page). -Your new `settings.py` is relatively bare out of the box. Evennia's core settings file is actually [ -evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default +Your new `settings.py` is relatively bare out of the box. Evennia's core settings file is actually +[evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default .py) and is considerably more extensive (it is also heavily documented so you should refer to this file directly for the available settings). diff --git a/docs/source/Static-In-Game-Map.md b/docs/source/Static-In-Game-Map.md index a0c4e3d1b0..6d0a4a8435 100644 --- a/docs/source/Static-In-Game-Map.md +++ b/docs/source/Static-In-Game-Map.md @@ -18,8 +18,7 @@ sanity of their players. And when they do, the game becomes possible to map. Thi an example of a simple but flexible in-game map system to further help player's to navigate. We will To simplify development and error-checking we'll break down the work into bite-size chunks, each -building on what came before. For this we'll make extensive use of the [Batch code processor](Batch- -Code-Processor), so you may want to familiarize yourself with that. +building on what came before. For this we'll make extensive use of the [Batch code processor](./Batch-Code-Processor), so you may want to familiarize yourself with that. 1. **Planning the map** - Here we'll come up with a small example map to use for the rest of the tutorial. @@ -36,7 +35,7 @@ map we designed before. O─O─O To the south, the glow of a campfire can be seen. To the east lie ≈↑│↑∩ the vast mountains and to the west is heard the waves of the sea. ↑▲O▲↑ - + Exits: north(#8), east(#9), south(#10), west(#11) ``` @@ -46,9 +45,7 @@ don't show in the documentation wiki. ## Planning the Map -Let's begin with the fun part! Maps in MUDs come in many different [shapes and -sizes](http://journal.imaginary-realities.com/volume-05/issue-01/modern-interface-modern- -mud/index.html). Some appear as just boxes connected by lines. Others have complex graphics that are +Let's begin with the fun part! Maps in MUDs come in many different [shapes and sizes](http://journal.imaginary-realities.com/volume-05/issue-01/modern-interface-modern-mud/index.html). Some appear as just boxes connected by lines. Others have complex graphics that are external to the game itself. Our map will be in-game text but that doesn't mean we're restricted to the normal alphabet! If @@ -275,12 +272,12 @@ def return_map(): This function returns the whole map """ map = "" - + #For each row in our map, add it to map for valuey in world_map: map += valuey map += "\n" - + return map def return_minimap(x, y, radius = 2): @@ -289,12 +286,12 @@ def return_minimap(x, y, radius = 2): Returning all chars in a 2 char radius from (x,y) """ map = "" - + #For each row we need, add the characters we need. for valuey in world_map[y-radius:y+radius+1]: for valuex in valuey[x-radius:x+radius+1]: map += valuex map += "\n" - + return map ``` @@ -411,6 +408,5 @@ You should now have a mapped little world and a basic understanding of batchcode easily new game defining features can be added to Evennia. You can easily build from this tutorial by expanding the map and creating more rooms to explore. Why -not add more features to your game by trying other tutorials: [Add weather to your world](Weather- -Tutorial), [fill your world with NPC's](./Tutorial-Aggressive-NPCs) or [implement a combat -system](Turn-based-Combat-System). +not add more features to your game by trying other tutorials: [Add weather to your world](./Weather-Tutorial), +[fill your world with NPC's](./Tutorial-Aggressive-NPCs) or [implement a combat system](./Turn-based-Combat-System). diff --git a/docs/source/Turn-based-Combat-System.md b/docs/source/Turn-based-Combat-System.md index d0a0dc864c..578129453a 100644 --- a/docs/source/Turn-based-Combat-System.md +++ b/docs/source/Turn-based-Combat-System.md @@ -2,8 +2,7 @@ This tutorial gives an example of a full, if simplified, combat system for Evennia. It was inspired -by the discussions held on the [mailing -list](https://groups.google.com/forum/#!msg/evennia/wnJNM2sXSfs/-dbLRrgWnYMJ). +by the discussions held on the [mailing list](https://groups.google.com/forum/#!msg/evennia/wnJNM2sXSfs/-dbLRrgWnYMJ). ## Overview of combat system concepts @@ -49,8 +48,7 @@ free. - The commands are (in our example) simple; they can either `hit `, `feint ` or `parry `. They can also `defend`, a generic passive defense. Finally they may choose to `disengage/flee`. -- When attacking we use a classic [rock-paper-scissors](https://en.wikipedia.org/wiki/Rock-paper- -scissors) mechanic to determine success: `hit` defeats `feint`, which defeats `parry` which defeats +- When attacking we use a classic [rock-paper-scissors](https://en.wikipedia.org/wiki/Rock-paper-scissors) mechanic to determine success: `hit` defeats `feint`, which defeats `parry` which defeats `hit`. `defend` is a general passive action that has a percentage chance to win against `hit` (only). - `disengage/flee` must be entered two times in a row and will only succeed if there is no `hit` @@ -67,8 +65,7 @@ characters and handles all the combat information. Since Scripts are database en that the combat will not be affected by a server reload. - A combat [command set](./Command-Sets) with the relevant commands needed for combat, such as the various attack/defend options and the `flee/disengage` command to leave the combat mode. -- A rule resolution system. The basics of making such a module is described in the [rule system -tutorial](Implementing-a-game-rule-system). We will only sketch such a module here for our end-turn +- A rule resolution system. The basics of making such a module is described in the [rule system tutorial](./Implementing-a-game-rule-system). We will only sketch such a module here for our end-turn combat resolution. - An `attack` [command](./Commands) for initiating the combat mode. This is added to the default command set. It will create the combat handler and add the character(s) to it. It will also assign @@ -168,7 +165,7 @@ class CombatHandler(DefaultScript): commands). We know this by checking the existence of the `normal_turn_end` NAttribute, set just before calling force_repeat. - + """ if self.ndb.normal_turn_end: # we get here because the turn ended normally @@ -190,7 +187,7 @@ class CombatHandler(DefaultScript): ("defend", character, None)] # set up back-reference self._init_character(character) - + def remove_character(self, character): "Remove combatant from handler" if character.id in self.db.characters: @@ -311,7 +308,7 @@ class CmdHit(Command): self.caller.msg("You add 'hit' to the combat queue") else: self.caller.msg("You can only queue two actions per turn!") - + # tell the handler to check if turn is over self.caller.ndb.combat_handler.check_end_turn() ``` @@ -347,8 +344,7 @@ class CombatCmdSet(CmdSet): ## Rules module -A general way to implement a rule module is found in the [rule system tutorial](Implementing-a-game- -rule-system). Proper resolution would likely require us to change our Characters to store things +A general way to implement a rule module is found in the [rule system tutorial](./Implementing-a-game-rule-system). Proper resolution would likely require us to change our Characters to store things like strength, weapon skills and so on. So for this example we will settle for a very simplistic rock-paper-scissors kind of setup with some randomness thrown in. We will not deal with damage here but just announce the results of each turn. In a real system the Character objects would hold stats diff --git a/docs/source/Tutorial-World-Introduction.md b/docs/source/Tutorial-World-Introduction.md index 4c45e041ae..746647d4f7 100644 --- a/docs/source/Tutorial-World-Introduction.md +++ b/docs/source/Tutorial-World-Introduction.md @@ -60,13 +60,7 @@ will automatically be unquelled. ## Gameplay -![the castle off the moor](https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/22916c25-6299-4 -53d-a221-446ec839f567/da2pmzu-46d63c6d-9cdc-41dd-87d6-1106db5a5e1a.jpg/v1/fill/w_600,h_849,q_75,strp -/the_castle_off_the_moor_by_griatch_art_da2pmzu-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1N -iJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD04NDkiLCJwYXRoIjoiXC9m -XC8yMjkxNmMyNS02Mjk5LTQ1M2QtYTIyMS00NDZlYzgzOWY1NjdcL2RhMnBtenUtNDZkNjNjNmQtOWNkYy00MWRkLTg3ZDYtMTEw -NmRiNWE1ZTFhLmpwZyIsIndpZHRoIjoiPD02MDAifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.o -muS3D1RmFiZCy9OSXiIita-HxVGrBok3_7asq0rflw) +![the castle off the moor](https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/22916c25-6299-453d-a221-446ec839f567/da2pmzu-46d63c6d-9cdc-41dd-87d6-1106db5a5e1a.jpg/v1/fill/w_600,h_849,q_75,strp/the_castle_off_the_moor_by_griatch_art_da2pmzu-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD04NDkiLCJwYXRoIjoiXC9mXC8yMjkxNmMyNS02Mjk5LTQ1M2QtYTIyMS00NDZlYzgzOWY1NjdcL2RhMnBtenUtNDZkNjNjNmQtOWNkYy00MWRkLTg3ZDYtMTEwNmRiNWE1ZTFhLmpwZyIsIndpZHRoIjoiPD02MDAifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.omuS3D1RmFiZCy9OSXiIita-HxVGrBok3_7asq0rflw) *To get into the mood of this miniature quest, imagine you are an adventurer out to find fame and @@ -88,7 +82,7 @@ the scenes of the tutorial). - *defend* will lower the chance to taking damage on your enemy's next attack. - You *can* run from a fight that feels too deadly. Expect to be chased though. - Being defeated is a part of the experience ... - + ## Uninstall Uninstalling the tutorial world basically means deleting all the rooms and objects it consists of. diff --git a/docs/source/Tutorials.md b/docs/source/Tutorials.md index 32fc379b3e..7a2dc54bf7 100644 --- a/docs/source/Tutorials.md +++ b/docs/source/Tutorials.md @@ -2,8 +2,8 @@ Before continuing to read these tutorials (and especially before you start to code or build your -game in earnest) it's strongly recommended that you read the [Evennia coding introduction](Coding- -Introduction) as well as the [Planning your own game](./Game-Planning) pages first. +game in earnest) it's strongly recommended that you read the +[Evennia coding introduction](./Coding-Introduction) as well as the [Planning your own game](./Game-Planning) pages first. Please note that it's not within the scope of our tutorials to teach you basic Python. If you are new to the language, expect to have to look up concepts you are unfamiliar with. Usually a quick @@ -34,22 +34,17 @@ _General code practices for newbie game developers._ To use Evennia, you will need basic understanding of Python [modules](http://docs.python.org/3.7/tutorial/modules.html), -[variables](http://www.tutorialspoint.com/python/python_variable_types.htm), [conditional -statements](http://docs.python.org/tutorial/controlflow.html#if-statements), +[variables](http://www.tutorialspoint.com/python/python_variable_types.htm), [conditional statements](http://docs.python.org/tutorial/controlflow.html#if-statements), [loops](http://docs.python.org/tutorial/controlflow.html#for-statements), -[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions), [lists, -dictionaries, list comprehensions](http://docs.python.org/tutorial/datastructures.html) and [string -formatting](http://docs.python.org/tutorial/introduction.html#strings). You should also have a basic -understanding of [object-oriented -programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) and what Python +[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions), [lists, dictionaries, list comprehensions](http://docs.python.org/tutorial/datastructures.html) and [string formatting](http://docs.python.org/tutorial/introduction.html#strings). You should also have a basic +understanding of [object-oriented programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) and what Python [Classes](http://docs.python.org/tutorial/classes.html) are. - [Python tutorials for beginners](https://wiki.python.org/moin/BeginnersGuide/NonProgrammers) - external link with tutorials for those not familiar with coding in general or Python in particular. - [Tutorial: Version Control](./Version-Control) - use GIT to organize your code both for your own game project and for contributing to Evennia. -- MIT offers free courses in many subjects. Their [Introduction to Computer Science and -Programming](https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-00sc- +- MIT offers free courses in many subjects. Their [Introduction to Computer Science and Programming](https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-00sc- introduction-to-computer-science-and-programming-spring-2011/) uses Python as its language of choice. Longer path, but more in-depth. Definitely worth a look. @@ -113,8 +108,7 @@ Evennia. - [Tutorial: Handling virtual time in your game](./Gametime-Tutorial) - [Tutorial: Setting up a coordinate system for rooms](./Coordinates) - [Tutorial: customize the way channels and channel commands work in your game](./Customize-channels) -- [Tutorial: Adding unit tests to your game project](./Unit-Testing#testing-for-game-development-mini- -tutorial) +- [Tutorial: Adding unit tests to your game project](./Unit-Testing#testing-for-game-development-mini- tutorial) ### Contrib diff --git a/docs/source/Unit-Testing.md b/docs/source/Unit-Testing.md index edf43efda8..2e2e1a9e08 100644 --- a/docs/source/Unit-Testing.md +++ b/docs/source/Unit-Testing.md @@ -55,8 +55,7 @@ forces Evennia to use this settings file over the default one. Evennia's test suite makes use of Django unit test system, which in turn relies on Python's *unittest* module. -> If you want to help out writing unittests for Evennia, take a look at Evennia's [coveralls.io -page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of +> If you want to help out writing unittests for Evennia, take a look at Evennia's [coveralls.io page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of test coverage and which does not. To make the test runner find the tests, they must be put in a module named `test*.py` (so `test.py`, @@ -74,16 +73,16 @@ To test the results, you use special methods of the `TestCase` class. Many of t "`assert`", such as `assertEqual` or `assertTrue`. Example of a `TestCase` class: - + ```python import unittest - + # the function we want to test from mypath import myfunc - + class TestObj(unittest.TestCase): "This tests a function myfunc." - + def test_return_value(self): "test method. Makes sure return value is as expected." expected_return = "This is me being nice." @@ -98,8 +97,7 @@ Example of a `TestCase` class: self.assertEqual(expected_return, actual_return) ``` -You might also want to read the [documentation for the unittest -module](http://docs.python.org/library/unittest.html). +You might also want to read the [documentation for the unittest module](http://docs.python.org/library/unittest.html). ### Using the EvenniaTest class @@ -108,8 +106,7 @@ initiates a range of useful properties on themselves for testing Evennia systems `.account` and `.session` representing a mock connected Account and its Session and `.char1` and `.char2` representing Characters complete with a location in the test database. These are all useful when testing Evennia system requiring any of the default Evennia typeclasses as inputs. See the full -definition of the `EvenniaTest` class in [evennia/utils/test_resources.py](https://github.com/evenni -a/evennia/blob/master/evennia/utils/test_resources.py). +definition of the `EvenniaTest` class in [evennia/utils/test_resources.py](https://github.com/evennia/evennia/blob/master/evennia/utils/test_resources.py). ```python # in a test module @@ -164,9 +161,7 @@ of the Evennia distribution and its unit tests should be run with all other Even The way to do this is to only temporarily add your models to the `INSTALLED_APPS` directory when the test runs. here is an example of how to do it. -> Note that this solution, derived from this [stackexchange -answer](http://stackoverflow.com/questions/502916/django-how-to-create-a-model-dynamically-just-for- -testing#503435) is currently untested! Please report your findings. +> Note that this solution, derived from this [stackexchange answer](http://stackoverflow.com/questions/502916/django-how-to-create-a-model-dynamically-just-for-testing#503435) is currently untested! Please report your findings. ```python # a file contrib/mycontrib/tests.py @@ -199,7 +194,7 @@ class TestMyModel(EvenniaTest): from django.db.models import loading loading.cache.loaded = False call_command('syncdb', verbosity=0) - + def tearDown(self): settings.configure(**OLD_DEFAULT_SETTINGS) django.setup() @@ -290,11 +285,11 @@ just to show how unit testing works: # mygame/commands/tests.py import unittest - + class TestString(unittest.TestCase): - + """Unittest for strings (just a basic example).""" - + def test_upper(self): """Test the upper() str method.""" self.assertEqual('foo'.upper(), 'FOO') @@ -317,7 +312,7 @@ Let's execute that test to see if it works. . ---------------------------------------------------------------------- Ran 1 test in 0.001s - + OK Destroying test database for alias 'default'... @@ -330,8 +325,8 @@ to see how it looks when it fails. ### Testing commands -This section will test the proper execution of the 'abilities' command, as described in the [First -Steps Coding](First-Steps-Coding) page. Follow this tutorial to create the 'abilities' command, we +This section will test the proper execution of the 'abilities' command, as described in the +[First Steps Coding](./First-Steps-Coding) page. Follow this tutorial to create the 'abilities' command, we will need it to test it. Testing commands in Evennia is a bit more complex than the simple testing example we have seen. @@ -347,14 +342,14 @@ already have in `commands` from before. # bottom of mygame/commands/tests.py from evennia.commands.default.tests import CommandTest - + from commands.command import CmdAbilities from typeclasses.characters import Character - + class TestAbilities(CommandTest): - + character_typeclass = Character - + def test_simple(self): self.call(CmdAbilities(), "", "STR: 5, AGI: 4, MAG: 2") ``` @@ -390,7 +385,7 @@ Let's run our new test: .. ---------------------------------------------------------------------- Ran 2 tests in 0.156s - + OK Destroying test database for alias 'default'... @@ -405,19 +400,19 @@ will have nothing but static output to test. Here we are going to learn how to t output.
This tutorial assumes you have a basic understanding of what regular expressions are. If you do not -I recommend reading the `Introduction` and `Simple Pattern` sections at [Python regular expressions -tutorial](https://docs.python.org/3/howto/regex.html). If you do plan on making a complete Evennia +I recommend reading the `Introduction` and `Simple Pattern` sections at +[Python regular expressions tutorial](https://docs.python.org/3/howto/regex.html). If you do plan on making a complete Evennia project learning regular expressions will save a great deal of time.
Append the code below to your `tests.py` file.
```python # bottom of mygame/commands/tests.py - + class TestDynamicAbilities(CommandTest): - + character_typeclass = Character - + def test_simple(self): cmd_abil_result = self.call(CmdAbilities(), "") self.assertRegex(cmd_abil_result, "STR: \d+, AGI: \d+, MAG: \d+") diff --git a/docs/source/Web-Character-Generation.md b/docs/source/Web-Character-Generation.md index e570af528c..fe7e129b61 100644 --- a/docs/source/Web-Character-Generation.md +++ b/docs/source/Web-Character-Generation.md @@ -15,8 +15,7 @@ selection screen when you log into the game later). Other modes can be used with auto-puppet the new Character. You should have some familiarity with how Django sets up its Model Template View framework. You need -to understand what is happening in the basic [Web Character View tutorial](Web-Character-View- -Tutorial). If you don’t understand the listed tutorial or have a grasp of Django basics, please look +to understand what is happening in the basic [Web Character View tutorial](./Web-Character-View-Tutorial). If you don’t understand the listed tutorial or have a grasp of Django basics, please look at the [Django tutorial](https://docs.djangoproject.com/en/1.8/intro/) to get a taste of what Django does, before throwing Evennia into the mix (Evennia shares its API and attributes with the website interface). This guide will outline the format of the models, views, urls, and html templates @@ -29,32 +28,28 @@ Here are some screenshots of the simple app we will be making. Index page, with no character application yet done: *** -![Index page, with no character application yet done.](https://lh3.googleusercontent.com/-57KuSWHXQ_ -M/VWcULN152tI/AAAAAAAAEZg/kINTmVlHf6M/w425-h189-no/webchargen_index2.gif) +![Index page, with no character application yet done.](https://lh3.googleusercontent.com/-57KuSWHXQ_M/VWcULN152tI/AAAAAAAAEZg/kINTmVlHf6M/w425-h189-no/webchargen_index2.gif) *** Having clicked the "create" link you get to create your character (here we will only have name and background, you can add whatever is needed to fit your game): *** -![Character creation.](https://lh3.googleusercontent.com/-ORiOEM2R_yQ/VWcUKgy84rI/AAAAAAAAEZY/B3CBh3 -FHii4/w607-h60-no/webchargen_creation.gif) +![Character creation.](https://lh3.googleusercontent.com/-ORiOEM2R_yQ/VWcUKgy84rI/AAAAAAAAEZY/B3CBh3FHii4/w607-h60-no/webchargen_creation.gif) *** Back to the index page. Having entered our character application (we called our character "TestApp") you see it listed: *** -![Having entered an application.](https://lh6.googleusercontent.com/-HlxvkvAimj4/VWcUKjFxEiI/AAAAAAA -AEZo/gLppebr05JI/w321-h194-no/webchargen_index1.gif) +![Having entered an application.](https://lh6.googleusercontent.com/-HlxvkvAimj4/VWcUKjFxEiI/AAAAAAAAEZo/gLppebr05JI/w321-h194-no/webchargen_index1.gif) *** We can also view an already written character application by clicking on it - this brings us to the *detail* page: *** -![Detail view of character application.](https://lh6.googleusercontent.com/-2m1UhSE7s_k/VWcUKfLRfII/ -AAAAAAAAEZc/UFmBOqVya4k/w267-h175-no/webchargen_detail.gif) +![Detail view of character application.](https://lh6.googleusercontent.com/-2m1UhSE7s_k/VWcUKfLRfII/AAAAAAAAEZc/UFmBOqVya4k/w267-h175-no/webchargen_detail.gif) *** ## Installing an App @@ -281,7 +276,7 @@ After all of this, our `views.py` file should look like something like this: ```python # file mygame/web/chargen/views.py - + from django.shortcuts import render from web.chargen.models import CharApp from web.chargen.forms import AppForm @@ -544,8 +539,7 @@ created character object. Thankfully, the Evennia API makes this easy. As sad as it is, if your server is open to the web, bots might come to visit and take advantage of your open form to create hundreds, thousands, millions of characters if you give them the -opportunity. This section shows you how to use the [No CAPCHA -reCAPCHA](https://www.google.com/recaptcha/intro/invisible.html) designed by Google. Not only is it +opportunity. This section shows you how to use the [No CAPCHA reCAPCHA](https://www.google.com/recaptcha/intro/invisible.html) designed by Google. Not only is it easy to use, it is user-friendly... for humans. A simple checkbox to check, except if Google has some suspicion, in which case you will have a more difficult test with an image and the usual text inside. It's worth pointing out that, as long as Google doesn't suspect you of being a robot, this diff --git a/docs/source/Web-Character-View-Tutorial.md b/docs/source/Web-Character-View-Tutorial.md index 880093be8a..e5992e10ee 100644 --- a/docs/source/Web-Character-View-Tutorial.md +++ b/docs/source/Web-Character-View-Tutorial.md @@ -1,8 +1,7 @@ # Web Character View Tutorial -**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](Web- -Tutorial).** +**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](./Web-Tutorial).** In this tutorial we will create a web page that displays the stats of a game character. For this, and all other pages we want to make specific to our game, we'll need to create our own Django "app" @@ -226,4 +225,4 @@ page by using {{ object.get_absolute_url }} in any template where you have a giv *Now that you've made a basic page and app with Django, you may want to read the full Django tutorial to get a better idea of what it can do. [You can find Django's tutorial -here](https://docs.djangoproject.com/en/1.8/intro/tutorial01/).* \ No newline at end of file +here](https://docs.djangoproject.com/en/1.8/intro/tutorial01/).* diff --git a/docs/source/Web-Features.md b/docs/source/Web-Features.md index 40709ef43f..e28b630913 100644 --- a/docs/source/Web-Features.md +++ b/docs/source/Web-Features.md @@ -35,7 +35,7 @@ You customize your website from your game directory. In the folder `web` you'll `static`, `templates`, `static_overrides` and `templates_overrides`. The first two of those are populated automatically by Django and used to serve the website. You should not edit anything in them - the change will be lost. To customize the website you'll need to copy the file you want to -change from the `web/website/template/` or `web/website/static/ path to the corresponding place +change from the `web/website/template/` or `web/website/static/` path to the corresponding place under one of `_overrides` directories. Example: To override or modify `evennia/web/website/template/website/index.html` you need to @@ -89,8 +89,7 @@ the root of the website. It will now our own function `myview` from a new module `mygame.com` in the address bar. If we had wanted to add a view for `http://mygame.com/awesome`, the regular expression would have been `^/awesome`. -Look at [evennia/web/website/views.py](https://github.com/evennia/evennia/blob/master/evennia/web/we -bsite/views.py#L82) to see the inputs and outputs you must have to define a view. Easiest may be to +Look at [evennia/web/website/views.py](https://github.com/evennia/evennia/blob/master/evennia/web/website/views.py#L82) to see the inputs and outputs you must have to define a view. Easiest may be to copy the default file to `mygame/web` to have something to modify and expand on. Restart the server and reload the page in the browser - the website will now use your custom view. From 89d8d46e64f44c9acc4797259398e0528381d257 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 18 Jun 2021 19:34:39 +0200 Subject: [PATCH 43/71] Try reporting a little more info from taskhandler deserialization error --- evennia/scripts/taskhandler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index ab1b29666d..3b826844a1 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -340,11 +340,11 @@ class TaskHandler(object): # an unsaveable callback should immediately abort try: dbserialize(callback) - except (TypeError, AttributeError, PickleError): + except (TypeError, AttributeError, PickleError) as err: raise ValueError( - "the specified callback {} cannot be pickled. " + "the specified callback {callback} cannot be pickled. " "It must be a top-level function in a module or an " - "instance method.".format(callback) + "instance method ({err}).".format(callback=callback, err=err) ) return From c33db6243b9720e6cbfbce09fe341a8f6c510675 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 19 Jun 2021 14:57:24 +0200 Subject: [PATCH 44/71] Fix legacy bytes conversion for older twisted --- evennia/server/portal/mccp.py | 3 +-- evennia/server/portal/mssp.py | 7 +++---- evennia/server/portal/mxp.py | 3 +-- evennia/server/portal/naws.py | 5 ++--- evennia/server/portal/suppress_ga.py | 4 +--- evennia/server/portal/telnet_oob.py | 17 ++++++++--------- evennia/server/portal/ttype.py | 8 +++----- 7 files changed, 19 insertions(+), 28 deletions(-) diff --git a/evennia/server/portal/mccp.py b/evennia/server/portal/mccp.py index 2d00e479b6..7ecba2d9d4 100644 --- a/evennia/server/portal/mccp.py +++ b/evennia/server/portal/mccp.py @@ -15,10 +15,9 @@ This protocol is implemented by the telnet protocol importing mccp_compress and calling it from its write methods. """ import zlib -from twisted.python.compat import _bytesChr as chr # negotiations for v1 and v2 of the protocol -MCCP = chr(86) # b"\x56" +MCCP = bytes([86]) # b"\x56" FLUSH = zlib.Z_SYNC_FLUSH diff --git a/evennia/server/portal/mssp.py b/evennia/server/portal/mssp.py index d939bcc37b..227a713cbf 100644 --- a/evennia/server/portal/mssp.py +++ b/evennia/server/portal/mssp.py @@ -12,11 +12,10 @@ active players and so on. """ from django.conf import settings from evennia.utils import utils -from twisted.python.compat import _bytesChr as bchr -MSSP = bchr(70) # b"\x46" -MSSP_VAR = bchr(1) # b"\x01" -MSSP_VAL = bchr(2) # b"\x02" +MSSP = bytes([70]) # b"\x46" +MSSP_VAR = bytes([1]) # b"\x01" +MSSP_VAL = bytes([2]) # b"\x02" # try to get the customized mssp info, if it exists. MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={}) diff --git a/evennia/server/portal/mxp.py b/evennia/server/portal/mxp.py index 8ff773036d..7c0f2325a5 100644 --- a/evennia/server/portal/mxp.py +++ b/evennia/server/portal/mxp.py @@ -14,12 +14,11 @@ http://www.gammon.com.au/mushclient/addingservermxp.htm """ import re -from twisted.python.compat import _bytesChr as bchr LINKS_SUB = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL) # MXP Telnet option -MXP = bchr(91) # b"\x5b" +MXP = bytes([91]) # b"\x5b" MXP_TEMPSECURE = "\x1B[4z" MXP_SEND = MXP_TEMPSECURE + '' + "\\2" + MXP_TEMPSECURE + "" diff --git a/evennia/server/portal/naws.py b/evennia/server/portal/naws.py index caa9f73402..23bc6f2e7b 100644 --- a/evennia/server/portal/naws.py +++ b/evennia/server/portal/naws.py @@ -11,10 +11,9 @@ client and update it when the size changes """ from codecs import encode as codecs_encode from django.conf import settings -from twisted.python.compat import _bytesChr as bchr -NAWS = bchr(31) # b"\x1f" -IS = bchr(0) # b"\x00" +NAWS = bytes([31]) # b"\x1f" +IS = bytes([0]) # b"\x00" # default taken from telnet specification DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT diff --git a/evennia/server/portal/suppress_ga.py b/evennia/server/portal/suppress_ga.py index 21332ea070..4bd8dc2c45 100644 --- a/evennia/server/portal/suppress_ga.py +++ b/evennia/server/portal/suppress_ga.py @@ -13,9 +13,7 @@ It is set as the NOGOAHEAD protocol_flag option. http://www.faqs.org/rfcs/rfc858.html """ -from twisted.python.compat import _bytesChr as bchr - -SUPPRESS_GA = bchr(3) # b"\x03" +SUPPRESS_GA = bytes([3]) # b"\x03" # default taken from telnet specification diff --git a/evennia/server/portal/telnet_oob.py b/evennia/server/portal/telnet_oob.py index 1570feb003..656aac9eaa 100644 --- a/evennia/server/portal/telnet_oob.py +++ b/evennia/server/portal/telnet_oob.py @@ -31,23 +31,22 @@ applicable. import re import json from evennia.utils.utils import is_iter -from twisted.python.compat import _bytesChr as bchr # General Telnet from twisted.conch.telnet import IAC, SB, SE # MSDP-relevant telnet cmd/opt-codes -MSDP = bchr(69) -MSDP_VAR = bchr(1) -MSDP_VAL = bchr(2) -MSDP_TABLE_OPEN = bchr(3) -MSDP_TABLE_CLOSE = bchr(4) +MSDP = bytes([69]) +MSDP_VAR = bytes([1]) +MSDP_VAL = bytes([2]) +MSDP_TABLE_OPEN = bytes([3]) +MSDP_TABLE_CLOSE = bytes([4]) -MSDP_ARRAY_OPEN = bchr(5) -MSDP_ARRAY_CLOSE = bchr(6) +MSDP_ARRAY_OPEN = bytes([5]) +MSDP_ARRAY_CLOSE = bytes([6]) # GMCP -GMCP = bchr(201) +GMCP = bytes([201]) # pre-compiled regexes diff --git a/evennia/server/portal/ttype.py b/evennia/server/portal/ttype.py index d74b3ae9db..e74b8e638d 100644 --- a/evennia/server/portal/ttype.py +++ b/evennia/server/portal/ttype.py @@ -10,12 +10,10 @@ etc. If the client does not support TTYPE, this will be ignored. All data will be stored on the protocol's protocol_flags dictionary, under the 'TTYPE' key. """ -from twisted.python.compat import _bytesChr as bchr - # telnet option codes -TTYPE = bchr(24) # b"\x18" -IS = bchr(0) # b"\x00" -SEND = bchr(1) # b"\x01" +TTYPE = bytes([24]) # b"\x18" +IS = bytes([0]) # b"\x00" +SEND = bytes([1]) # b"\x01" # terminal capabilities and their codes MTTS = [ From 5a7e132aee8671690cfd4d5ad9f6466ff42c7453 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 19 Jun 2021 15:29:15 +0200 Subject: [PATCH 45/71] Python 3.9 support. Resolves #2436. --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24de2405fa..5aff3dc50c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ - Latin (la) i18n translation (jamalainm) - Made the `evennia` dir possible to use without gamedir for purpose of doc generation. +### Backports from 1.0 to 0.9.5 since 0.9.5 release + +- Fix to TaskHandler to complate api and allow manipulation of `utils.delay` + return as originall intended. +- Support for Python 3.9. + ### Evennia 0.9.5 (Nov 2020) A transitional release, including new doc system. From 4f74ea2debdf6974a966fe1ffc13ba06795b196e Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 19 Jun 2021 15:57:36 +0200 Subject: [PATCH 46/71] Fix taskhandler pickling method instances. Resolves #2439 --- evennia/scripts/taskhandler.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/evennia/scripts/taskhandler.py b/evennia/scripts/taskhandler.py index 3b826844a1..ac2fef9890 100644 --- a/evennia/scripts/taskhandler.py +++ b/evennia/scripts/taskhandler.py @@ -279,17 +279,24 @@ class TaskHandler(object): if not persistent: continue + safe_callback = callback if getattr(callback, "__self__", None): # `callback` is an instance method obj = callback.__self__ name = callback.__name__ - callback = (obj, name) + safe_callback = (obj, name) # Check if callback can be pickled. args and kwargs have been checked - safe_callback = None + try: + dbserialize(safe_callback) + except (TypeError, AttributeError, PickleError) as err: + raise ValueError( + "the specified callback {callback} cannot be pickled. " + "It must be a top-level function in a module or an " + "instance method ({err}).".format(callback=callback, err=err) + ) - - self.to_save[task_id] = dbserialize((date, callback, args, kwargs)) + self.to_save[task_id] = dbserialize((date, safe_callback, args, kwargs)) ServerConfig.objects.conf("delayed_tasks", self.to_save) def add(self, timedelay, callback, *args, **kwargs): @@ -337,17 +344,6 @@ class TaskHandler(object): safe_args = [] safe_kwargs = {} - # an unsaveable callback should immediately abort - try: - dbserialize(callback) - except (TypeError, AttributeError, PickleError) as err: - raise ValueError( - "the specified callback {callback} cannot be pickled. " - "It must be a top-level function in a module or an " - "instance method ({err}).".format(callback=callback, err=err) - ) - return - # Check that args and kwargs contain picklable information for arg in args: try: From e1eec45b05c1b6667aabeb5d1b1f2b91ae79ff4c Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 20 Jun 2021 10:56:20 +0200 Subject: [PATCH 47/71] Fix CI issue for postgres with django 2.2. --- .github/workflows/github_action_test_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_action_test_suite.yml b/.github/workflows/github_action_test_suite.yml index bcc65fb47d..6f1450501b 100644 --- a/.github/workflows/github_action_test_suite.yml +++ b/.github/workflows/github_action_test_suite.yml @@ -64,7 +64,7 @@ jobs: run: | python -m pip install --upgrade pip pip install wheel - pip install psycopg2-binary + pip install psycopg2-binary>=2.8,<2.9 # fix issue for Django 2.2 pip install mysqlclient pip install coveralls pip install codacy-coverage From 76af934fd257c99cb94af7c0db962fcb72c6dae1 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 20 Jun 2021 11:08:52 +0200 Subject: [PATCH 48/71] Another CI fix --- .github/workflows/github_action_test_suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github_action_test_suite.yml b/.github/workflows/github_action_test_suite.yml index 6f1450501b..9bd8674744 100644 --- a/.github/workflows/github_action_test_suite.yml +++ b/.github/workflows/github_action_test_suite.yml @@ -64,7 +64,7 @@ jobs: run: | python -m pip install --upgrade pip pip install wheel - pip install psycopg2-binary>=2.8,<2.9 # fix issue for Django 2.2 + pip install psycopg2-binary==2.8.6 # fix issue for Django 2.2 pip install mysqlclient pip install coveralls pip install codacy-coverage From 42a3d0c59c3a6545433acaf77fa75f507897c20f Mon Sep 17 00:00:00 2001 From: luciensadi Date: Mon, 9 Aug 2021 10:13:59 -0700 Subject: [PATCH 49/71] Update evennia.js Fix for misplaced parens-- previous code checked 'is the output of `typeof(listener === 'function')` truthy', which is equivalent of `typeof(boolean)` which is always true. --- evennia/web/webclient/static/webclient/js/evennia.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/web/webclient/static/webclient/js/evennia.js b/evennia/web/webclient/static/webclient/js/evennia.js index d406db1453..401c841cb5 100644 --- a/evennia/web/webclient/static/webclient/js/evennia.js +++ b/evennia/web/webclient/static/webclient/js/evennia.js @@ -195,7 +195,7 @@ An "emitter" object must have a function // to listen to cmdname events. // var on = function (cmdname, listener) { - if (typeof(listener === 'function')) { + if (typeof(listener) === 'function') { listeners[cmdname] = listener; }; }; From c425f95193099b5978a271261f9a8fc59ce597c0 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 16 Aug 2021 21:10:44 +0200 Subject: [PATCH 50/71] Make EvTable.add_row forward row-options correctly. Resolve #2389. --- evennia/utils/evtable.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 399e9c0133..6a7420569e 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -308,7 +308,7 @@ def fill(text, width=_DEFAULT_WIDTH, **kwargs): # EvCell class (see further down for the EvTable itself) -class EvCell(object): +class EvCell: """ Holds a single data cell for the table. A cell has a certain width and height and contains one or more lines of data. It can shrink @@ -976,14 +976,17 @@ class EvColumn(object): Available keywods as per `EvCell.__init__`. """ + # column-level options override those in kwargs + options = {**kwargs, **self.options} + ypos = kwargs.get("ypos", None) if ypos is None or ypos > len(self.column): # add to the end - self.column.extend([EvCell(data, **self.options) for data in args]) + self.column.extend([EvCell(data, **options) for data in args]) else: # insert cells before given index ypos = min(len(self.column) - 1, max(0, int(ypos))) - new_cells = [EvCell(data, **self.options) for data in args] + new_cells = [EvCell(data, **options) for data in args] self.column = self.column[:ypos] + new_cells + self.column[ypos:] # self._balance(**kwargs) @@ -1010,6 +1013,7 @@ class EvColumn(object): Keywords as per `EvCell.__init__`. """ + # column-level options take precedence here kwargs.update(self.options) self.column[index].reformat(**kwargs) From c4806716daefac9f6fbb693adbf683f547b0bd62 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 16 Aug 2021 21:31:53 +0200 Subject: [PATCH 51/71] Give correct error on `set/desc here`. Resolve #2382. --- evennia/commands/default/building.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 1fe39cb704..b485073979 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1874,6 +1874,11 @@ class CmdSetAttribute(ObjManipCommand): if len(attrs) > 1: caller.msg("The Line editor can only be applied " "to one attribute at a time.") return + if not attrs: + caller.msg("Use `set/edit /` to define the Attribute to edit.\nTo " + "edit the current room description, use `set/edit here/desc` (or " + "use the `desc` command).") + return self.edit_handler(obj, attrs[0]) return if not value: From 125c5664fbdf8249c4907a0a18f8a45071059a4a Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 16 Aug 2021 21:36:01 +0200 Subject: [PATCH 52/71] Handle objects as keys in ndb.all(). Resolve #2378. --- evennia/typeclasses/attributes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 26bbeff04a..dfd9837d82 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -1128,4 +1128,4 @@ class NAttributeHandler(object): """ if return_tuples: return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")] - return [key for key in self._store if not key.startswith("_")] + return [key for key in self._store if not str(key).startswith("_")] From 77c16a4779497a50ce34e675dd604a266bb5fe79 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 6 Sep 2021 21:48:09 +0200 Subject: [PATCH 53/71] Up twisted requirement to 21.7.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fce3aaa557..00f90ccfc7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # general django >= 2.2.5, < 3.0 -twisted >= 20.3.0, < 21.0.0 +twisted >= 21.7.0, < 22.0.0 pytz django-sekizai inflect From 884fc4f21ea02ff5ccdcddebdb15a0d6d08e5563 Mon Sep 17 00:00:00 2001 From: james Date: Tue, 28 Sep 2021 19:09:26 -0400 Subject: [PATCH 54/71] small change to sync the doc example with imported name in game_template/commands/command.py --- docs/source/First-Steps-Coding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/First-Steps-Coding.md b/docs/source/First-Steps-Coding.md index 76b1f4cf8c..fa54b4484d 100644 --- a/docs/source/First-Steps-Coding.md +++ b/docs/source/First-Steps-Coding.md @@ -167,7 +167,7 @@ module has all the imports already set up along with some useful documentation. the bottom of this file: ```python - class CmdAbilities(Command): + class CmdAbilities(BaseCommand): """ List abilities From 18028b92327bbc9508331f7670079a875efae28f Mon Sep 17 00:00:00 2001 From: Marcos Marado Date: Fri, 1 Oct 2021 16:26:16 +0100 Subject: [PATCH 55/71] Getting Started: more troubleshooting info Issues #2434 or #2070 show that the installation guide can be a little bit confusing regarding on which directory should the user be when trying to pip install. This commit adds an entry on the Troubleshooting section, in hopes of helping those who stumble with that process. --- docs/source/Getting-Started.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/Getting-Started.md b/docs/source/Getting-Started.md index ecc38aa7d0..d2b6d2c25c 100644 --- a/docs/source/Getting-Started.md +++ b/docs/source/Getting-Started.md @@ -486,6 +486,9 @@ you can run `evennia -l`, or (in the future) start the server with `evennia star - Under some not-updated Linux distributions you may run into errors with a too-old `setuptools` or missing `functools`. If so, update your environment with `pip install --upgrade pip wheel setuptools`. Then try `pip install -e evennia` again. +- If you get an `setup.py not found` error message while trying to `pip install`, make sure you are + in the right directory. You should be at the same level of the `evenv` directory, and the + `evennia` git repository. Note that there is an `evennia` directory inside of the repository too. - One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; `Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/` with errors like `distutils.errors.DistutilsError: Could not find suitable distribution for @@ -536,4 +539,4 @@ determined by Evennia. If you installed Twisted in a non-standard location this you should update the line to the real location. - Some users have reported issues with Windows WSL and anti-virus software during Evennia development. Timeout errors and the inability to run `evennia connections` may be due to your anti- -virus software interfering. Try disabling or changing your anti-virus software settings. \ No newline at end of file +virus software interfering. Try disabling or changing your anti-virus software settings. From d6475d27e6b8e93d5ca667260d6a4328d03f5c57 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 2 Oct 2021 18:44:33 +0200 Subject: [PATCH 56/71] Catch unicode-decode error in logger display --- evennia/utils/logger.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index 7997327ab0..28b19aa05a 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -392,7 +392,15 @@ class EvenniaLogFile(logfile.LogFile): Returns: lines (list): lines from our _file attribute. """ - return [line.decode("utf-8") for line in self._file.readlines(*args, **kwargs)] + lines = [] + for line in self._file.readlines(*args, **kwargs): + try: + lines.append(line.decode("utf-8")) + except UnicodeDecodeError: + try: + lines.append(str(line)) + except Exception: + lines.append("") _LOG_FILE_HANDLES = {} # holds open log handles From 8ce11a2e158752775bd67b40ce6b749ac76ded5f Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 2 Oct 2021 18:47:12 +0200 Subject: [PATCH 57/71] Further fix of logger --- evennia/utils/logger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index 28b19aa05a..6122eb30b5 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -401,6 +401,7 @@ class EvenniaLogFile(logfile.LogFile): lines.append(str(line)) except Exception: lines.append("") + return lines _LOG_FILE_HANDLES = {} # holds open log handles From 4de925a07eec3dc264ae28d9c9b269a3dbc0f01b Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 2 Oct 2021 18:57:51 +0200 Subject: [PATCH 58/71] Test generator for log file return --- evennia/utils/logger.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index 6122eb30b5..c38e1e8260 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -390,18 +390,17 @@ class EvenniaLogFile(logfile.LogFile): **kwargs: same kwargs as file.readlines Returns: - lines (list): lines from our _file attribute. + Generator: lines from our _file attribute. """ - lines = [] for line in self._file.readlines(*args, **kwargs): try: - lines.append(line.decode("utf-8")) + lin = line.decode("utf-8") except UnicodeDecodeError: try: - lines.append(str(line)) + lin = str(lin) except Exception: - lines.append("") - return lines + lin = "" + yield lin _LOG_FILE_HANDLES = {} # holds open log handles From 1785846a8a96badab515a9879fe5ce7f0e7b6833 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 2 Oct 2021 19:05:26 +0200 Subject: [PATCH 59/71] More log tweaks --- evennia/utils/logger.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index c38e1e8260..e7b0811740 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -390,17 +390,18 @@ class EvenniaLogFile(logfile.LogFile): **kwargs: same kwargs as file.readlines Returns: - Generator: lines from our _file attribute. + lines (list): lines from our _file attribute. """ + lines = [] for line in self._file.readlines(*args, **kwargs): try: - lin = line.decode("utf-8") + lines.append(line.decode("utf-8")) except UnicodeDecodeError: try: - lin = str(lin) + lines.append(str(line)) except Exception: - lin = "" - yield lin + lines.append("") + return lines _LOG_FILE_HANDLES = {} # holds open log handles @@ -519,7 +520,7 @@ def tail_log_file(filename, offset, nlines, callback=None): lines_found = filehandle.readlines() block_count -= 1 # return the right number of lines - lines_found = lines_found[-nlines - offset : -offset if offset else None] + lines_found = lines_found[-nlines - offset: -offset if offset else None] if callback: callback(lines_found) return None From cfac6a0fee0528d342e1975c7bf4397111b3d5e3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 2 Oct 2021 19:16:47 +0200 Subject: [PATCH 60/71] Lower maxnum of channel lines for web display --- evennia/web/website/views.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/evennia/web/website/views.py b/evennia/web/website/views.py index fe65bc5c8b..3840262bdb 100644 --- a/evennia/web/website/views.py +++ b/evennia/web/website/views.py @@ -912,7 +912,7 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView): attributes = ["name"] # How many log entries to read and display. - max_num_lines = 10000 + max_num_lines = 1000 def get_context_data(self, **kwargs): """ @@ -936,9 +936,12 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView): for log in (x.strip() for x in tail_log_file(filename, 0, self.max_num_lines)): if not log: continue - time, msg = log.split(" [-] ") - time_key = time.split(":")[0] - + try: + time, msg = log.split(" [-] ") + time_key = time.split(":")[0] + except ValueError: + # malformed log line - skip line + continue bucket.append({"key": time_key, "timestamp": time, "message": msg}) # Add the processed entries to the context From 7d999a7b62ee1aa65b258ebbf826b0ddc323ee61 Mon Sep 17 00:00:00 2001 From: henddher Date: Sun, 3 Oct 2021 15:40:35 -0500 Subject: [PATCH 61/71] Typos fixed. --- evennia/utils/evtable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/utils/evtable.py b/evennia/utils/evtable.py index 6a7420569e..e619753f4b 100644 --- a/evennia/utils/evtable.py +++ b/evennia/utils/evtable.py @@ -1105,7 +1105,7 @@ class EvTable(object): Notes: Beyond those table-specific keywords, the non-overlapping keywords - of `EcCell.__init__` are also available. These will be passed down + of `EvCell.__init__` are also available. These will be passed down to every cell in the table. """ @@ -1548,7 +1548,7 @@ class EvTable(object): to the end. Args: - args (`EvColum` or multiple strings): Either a single EvColumn instance or + args (`EvColumn` or multiple strings): Either a single EvColumn instance or a number of data string arguments to be used to create a new column. header (str, optional): The header text for the column xpos (int, optional): Index position in table *before* which From 990411ba94664b8b7213e48c7b2a75a9d6fabda4 Mon Sep 17 00:00:00 2001 From: henddher Date: Sun, 3 Oct 2021 16:56:45 -0500 Subject: [PATCH 62/71] Convert key to lowercase in search_prototype. --- evennia/prototypes/prototypes.py | 4 ++++ evennia/prototypes/tests.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index e6ea4a585b..1efa4c86fa 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -365,6 +365,10 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators be found as a match. """ + # prototype keys are always in lowecase + if key: + key = key.lower() + # search module prototypes mod_matches = {} diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index f7eac5d912..24bbe67234 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -336,6 +336,8 @@ class TestProtLib(EvenniaTest): self.assertTrue(match) match = protlib.search_prototype(self.prot["prototype_key"]) self.assertEqual(match, [self.prot]) + match = protlib.search_prototype(self.prot["Prototype_Key"]) + self.assertEqual(match, [self.prot]) @override_settings(PROT_FUNC_MODULES=["evennia.prototypes.protfuncs"], CLIENT_DEFAULT_WIDTH=20) From ba1ecd0cce40f1b79fd8ad1412efeeed777f66df Mon Sep 17 00:00:00 2001 From: henddher Date: Sun, 3 Oct 2021 17:04:41 -0500 Subject: [PATCH 63/71] Unit test --- evennia/prototypes/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 24bbe67234..026786d547 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -336,7 +336,7 @@ class TestProtLib(EvenniaTest): self.assertTrue(match) match = protlib.search_prototype(self.prot["prototype_key"]) self.assertEqual(match, [self.prot]) - match = protlib.search_prototype(self.prot["Prototype_Key"]) + match = protlib.search_prototype(self.prot["prototype_key"].upper()) self.assertEqual(match, [self.prot]) From 6908fec3d9bc3eecedc3e15dd1a1a1db62687617 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 5 Oct 2021 23:22:50 +0200 Subject: [PATCH 64/71] Up min Django version to 3.2. Update recommended. --- docs/source/Getting-Started.md | 6 +++--- evennia/server/evennia_launcher.py | 4 ++-- evennia/settings_default.py | 3 ++- requirements.txt | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/source/Getting-Started.md b/docs/source/Getting-Started.md index d2b6d2c25c..42b2c363a3 100644 --- a/docs/source/Getting-Started.md +++ b/docs/source/Getting-Started.md @@ -48,7 +48,7 @@ everything in the following sections. - Windows (Vista, Win7, Win8, Win10) - Mac OSX (>=10.5 recommended) -- [Python](http://www.python.org) (v3.7, 3.8 are tested) +- [Python](http://www.python.org) (v3.7, 3.8 or 3.9) - [virtualenv](http://pypi.python.org/pypi/virtualenv) for making isolated Python environments. Installed with `pip install virtualenv`. @@ -56,13 +56,13 @@ everything in the following sections. updating Evennia itself - Mac users can use the [git-osx-installer](http://code.google.com/p/git-osx-installer/) or the [MacPorts version](http://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac). -- [Twisted](http://twistedmatrix.com) (v19.0+) +- [Twisted](http://twistedmatrix.com) (v21.0+) - [ZopeInterface](http://www.zope.org/Products/ZopeInterface) (v3.0+) - usually included in Twisted packages - Linux/Mac users may need the `gcc` and `python-dev` packages or equivalent. - Windows users need [MS Visual C++](https://aka.ms/vs/16/release/vs_buildtools.exe) and *maybe* [pypiwin32](https://pypi.python.org/pypi/pypiwin32). -- [Django](http://www.djangoproject.com) (v2.2.x), be warned that latest dev +- [Django](http://www.djangoproject.com) (v3.2.x), be warned that latest dev version is usually untested with Evennia) ## Linux Install diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 9fed573caf..6b0747bc6b 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -93,8 +93,8 @@ SRESET = chr(19) # shutdown server in reset mode # requirements PYTHON_MIN = "3.7" TWISTED_MIN = "18.0.0" -DJANGO_MIN = "2.2.5" -DJANGO_LT = "3.0" +DJANGO_MIN = "3.2" +DJANGO_LT = "3.3" try: sys.path[1] = EVENNIA_ROOT diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 8dd6f41374..15c7807977 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -278,7 +278,8 @@ ATTRIBUTE_STORED_MODEL_RENAME = [ (("players", "playerdb"), ("accounts", "accountdb")), (("typeclasses", "defaultplayer"), ("typeclasses", "defaultaccount")), ] - +# Default type of autofield (required by Django) +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' ###################################################################### # Evennia pluggable modules diff --git a/requirements.txt b/requirements.txt index 00f90ccfc7..4d80639eef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # Evennia dependencies # general -django >= 2.2.5, < 3.0 +django >= 3.2, < 3.3 twisted >= 21.7.0, < 22.0.0 pytz django-sekizai From d40dcb26a25c5a0b91ddd4a4cfba1edb94bb92e3 Mon Sep 17 00:00:00 2001 From: Shaun Oakenfull Date: Wed, 6 Oct 2021 08:24:21 +1000 Subject: [PATCH 65/71] Fix typo in comment environments typo corrected. --- evennia/utils/evmenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index b015406374..7f2ddbed09 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -384,7 +384,7 @@ class CmdEvMenuNode(Command): caller = self.caller # we store Session on the menu since this can be hard to - # get in multisession environemtns if caller is an Account. + # get in multisession environments if caller is an Account. menu = caller.ndb._evmenu if not menu: if _restore(caller): From c2973d953f7792b444ca9f0aa4cae719a7d00da3 Mon Sep 17 00:00:00 2001 From: Shaun Oakenfull Date: Wed, 6 Oct 2021 08:46:04 +1000 Subject: [PATCH 66/71] add triple quotes for docstring --- docs/source/First-Steps-Coding.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/First-Steps-Coding.md b/docs/source/First-Steps-Coding.md index fa54b4484d..6e8aac4ce1 100644 --- a/docs/source/First-Steps-Coding.md +++ b/docs/source/First-Steps-Coding.md @@ -182,7 +182,7 @@ the bottom of this file: help_category = "General" def func(self): - "implements the actual functionality" + """implements the actual functionality""" str, agi, mag = self.caller.get_abilities() string = "STR: %s, AGI: %s, MAG: %s" % (str, agi, mag) @@ -242,7 +242,7 @@ functionality. Here is an example of how the file could look: assume it looks like a stone in this example. """ def at_object_creation(self): - "Called when object is first created" + """Called when object is first created""" self.db.wise_texts = \ ["Stones have feelings too.", "To live like a stone is to not have lived at all.", From d32e32c534a5dce135b379a41dfde02b6ac86e41 Mon Sep 17 00:00:00 2001 From: Shaun Oakenfull Date: Wed, 6 Oct 2021 08:49:10 +1000 Subject: [PATCH 67/71] Revert "add triple quotes for docstring" This reverts commit c2973d953f7792b444ca9f0aa4cae719a7d00da3. --- docs/source/First-Steps-Coding.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/First-Steps-Coding.md b/docs/source/First-Steps-Coding.md index 6e8aac4ce1..fa54b4484d 100644 --- a/docs/source/First-Steps-Coding.md +++ b/docs/source/First-Steps-Coding.md @@ -182,7 +182,7 @@ the bottom of this file: help_category = "General" def func(self): - """implements the actual functionality""" + "implements the actual functionality" str, agi, mag = self.caller.get_abilities() string = "STR: %s, AGI: %s, MAG: %s" % (str, agi, mag) @@ -242,7 +242,7 @@ functionality. Here is an example of how the file could look: assume it looks like a stone in this example. """ def at_object_creation(self): - """Called when object is first created""" + "Called when object is first created" self.db.wise_texts = \ ["Stones have feelings too.", "To live like a stone is to not have lived at all.", From 6444ad928aaa6c7d06c9575a90627117af6184b1 Mon Sep 17 00:00:00 2001 From: Shaun Oakenfull Date: Wed, 6 Oct 2021 08:52:09 +1000 Subject: [PATCH 68/71] triple quotes [Documentation] add triple quotes for docstring --- docs/source/First-Steps-Coding.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/First-Steps-Coding.md b/docs/source/First-Steps-Coding.md index fa54b4484d..6e8aac4ce1 100644 --- a/docs/source/First-Steps-Coding.md +++ b/docs/source/First-Steps-Coding.md @@ -182,7 +182,7 @@ the bottom of this file: help_category = "General" def func(self): - "implements the actual functionality" + """implements the actual functionality""" str, agi, mag = self.caller.get_abilities() string = "STR: %s, AGI: %s, MAG: %s" % (str, agi, mag) @@ -242,7 +242,7 @@ functionality. Here is an example of how the file could look: assume it looks like a stone in this example. """ def at_object_creation(self): - "Called when object is first created" + """Called when object is first created""" self.db.wise_texts = \ ["Stones have feelings too.", "To live like a stone is to not have lived at all.", From e89ca2c3e6adf8016d12712bd13266fe2265b984 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 26 Oct 2021 21:14:33 +0200 Subject: [PATCH 69/71] Convert master docs to use MyST --- docs/Makefile | 19 +- docs/README.md | 4 +- docs/pylib/api_rst2md.py | 31 + docs/pylib/auto_link_remapper.py | 119 +- docs/pylib/copy_from_wiki.py | 21 +- docs/pylib/fmtwidth.py | 45 + docs/pylib/update_default_cmd_index.py | 111 + .../A-voice-operated-elevator-using-events.md | 6 +- docs/source/Accounts.md | 24 +- docs/source/Add-a-wiki-on-your-website.md | 2 +- docs/source/Adding-Command-Tutorial.md | 14 +- .../Adding-Object-Typeclass-Tutorial.md | 10 +- docs/source/Administrative-Docs.md | 56 +- docs/source/Arxcode-installing-help.md | 8 +- docs/source/Async-Process.md | 4 +- docs/source/Attributes.md | 32 +- docs/source/Banning.md | 4 +- docs/source/Batch-Code-Processor.md | 4 +- docs/source/Batch-Command-Processor.md | 6 +- docs/source/Batch-Processors.md | 6 +- .../Bootstrap-Components-and-Utilities.md | 6 +- docs/source/Builder-Docs.md | 30 +- docs/source/Building-Permissions.md | 4 +- docs/source/Building-Quickstart.md | 20 +- docs/source/Building-a-mech-tutorial.md | 10 +- docs/source/Client-Support-Grid.md | 137 +- docs/source/Coding-FAQ.md | 42 +- docs/source/Coding-Introduction.md | 10 +- docs/source/Coding-Utils.md | 36 +- docs/source/Command-Cooldown.md | 4 +- docs/source/Command-Duration.md | 6 +- docs/source/Command-Prompt.md | 2 +- docs/source/Command-Sets.md | 26 +- docs/source/Command-System.md | 10 +- docs/source/Commands.md | 56 +- docs/source/Communications.md | 16 +- docs/source/Connection-Screen.md | 6 +- docs/source/Continuous-Integration.md | 2 +- docs/source/Contributing-Docs.md | 998 +++---- docs/source/Contributing.md | 4 +- docs/source/Coordinates.md | 2 +- docs/source/Custom-Protocols.md | 10 +- docs/source/Customize-channels.md | 2 +- docs/source/Default-Command-Help.md | 2538 ----------------- docs/source/Default-Commands.md | 107 + docs/source/Default-Exit-Errors.md | 10 +- docs/source/Developer-Central.md | 134 +- docs/source/Directory-Overview.md | 42 +- docs/source/Docs-refactoring.md | 6 +- docs/source/Dynamic-In-Game-Map.md | 8 +- docs/source/EvMenu.md | 34 +- docs/source/EvMore.md | 2 +- docs/source/Evennia-API.md | 118 +- docs/source/Evennia-Introduction.md | 26 +- docs/source/Evennia-for-MUSH-Users.md | 14 +- .../Evennia-for-roleplaying-sessions.md | 18 +- docs/source/Execute-Python-Code.md | 10 +- docs/source/First-Steps-Coding.md | 30 +- docs/source/Game-Planning.md | 12 +- docs/source/Gametime-Tutorial.md | 2 +- docs/source/Getting-Started.md | 74 +- docs/source/Glossary.md | 193 +- docs/source/Guest-Logins.md | 6 +- docs/source/Help-System-Tutorial.md | 2 +- docs/source/Help-System.md | 8 +- docs/source/How-To-Get-And-Give-Help.md | 6 +- .../How-to-connect-Evennia-to-Twitter.md | 8 +- docs/source/IRC.md | 2 +- .../source/Implementing-a-game-rule-system.md | 4 +- docs/source/Inputfuncs.md | 10 +- docs/source/Installing-on-Android.md | 6 +- docs/source/Internationalization.md | 2 +- docs/source/Links.md | 2 +- docs/source/Locks.md | 40 +- docs/source/Manually-Configuring-Color.md | 8 +- docs/source/Messagepath.md | 10 +- docs/source/MonitorHandler.md | 6 +- docs/source/NPC-shop-Tutorial.md | 16 +- docs/source/New-Models.md | 6 +- docs/source/Nicks.md | 8 +- docs/source/OOB.md | 10 +- docs/source/Objects.md | 32 +- docs/source/Online-Setup.md | 148 +- ...nd-arguments,-theory-and-best-practices.md | 4 +- docs/source/Portal-And-Server.md | 2 +- docs/source/Profiling.md | 2 +- docs/source/Python-basic-introduction.md | 16 +- docs/source/Python-basic-tutorial-part-two.md | 26 +- docs/source/Quirks.md | 4 +- docs/source/RSS.md | 2 +- docs/source/Running-Evennia-in-Docker.md | 2 +- docs/source/Screenshot.md | 2 +- docs/source/Scripts.md | 18 +- docs/source/Security.md | 2 +- docs/source/Server-Conf.md | 10 +- docs/source/Sessions.md | 20 +- docs/source/Signals.md | 2 +- docs/source/Soft-Code.md | 2 +- docs/source/Spawner-and-Prototypes.md | 18 +- docs/source/Start-Stop-Reload.md | 8 +- docs/source/Static-In-Game-Map.md | 24 +- docs/source/Tags.md | 14 +- docs/source/TextTags.md | 10 +- docs/source/TickerHandler.md | 6 +- docs/source/Turn-based-Combat-System.md | 20 +- docs/source/Tutorial-Aggressive-NPCs.md | 6 +- docs/source/Tutorial-NPCs-listening.md | 4 +- docs/source/Tutorial-Searching-For-Objects.md | 24 +- docs/source/Tutorial-Tweeting-Game-Stats.md | 4 +- docs/source/Tutorial-Vehicles.md | 12 +- docs/source/Tutorial-World-Introduction.md | 4 +- .../Tutorial-for-basic-MUSH-like-game.md | 30 +- docs/source/Tutorials.md | 106 +- docs/source/Typeclasses.md | 30 +- docs/source/Understanding-Color-Tags.md | 4 +- docs/source/Unit-Testing.md | 6 +- docs/source/Updating-Your-Game.md | 7 +- docs/source/Using-MUX-as-a-Standard.md | 2 +- docs/source/Using-Travis.md | 2 +- docs/source/Version-Control.md | 2 +- docs/source/Weather-Tutorial.md | 2 +- docs/source/Web-Character-Generation.md | 10 +- docs/source/Web-Character-View-Tutorial.md | 2 +- docs/source/Web-Features.md | 4 +- docs/source/Web-Tutorial.md | 8 +- docs/source/Wiki-Index.md | 280 +- docs/source/Zones.md | 4 +- .../api/{evennia-api.rst => evennia-api.md} | 3 + ...ounts.rst => evennia.accounts.accounts.md} | 3 + ...ts.admin.rst => evennia.accounts.admin.md} | 3 + ...unts.bots.rst => evennia.accounts.bots.md} | 3 + ...anager.rst => evennia.accounts.manager.md} | 3 + ...ennia.accounts.rst => evennia.accounts.md} | 3 + ....models.rst => evennia.accounts.models.md} | 3 + ...ler.rst => evennia.commands.cmdhandler.md} | 3 + ...rser.rst => evennia.commands.cmdparser.md} | 3 + ....cmdset.rst => evennia.commands.cmdset.md} | 3 + ....rst => evennia.commands.cmdsethandler.md} | 3 + ...ommand.rst => evennia.commands.command.md} | 3 + ...st => evennia.commands.default.account.md} | 3 + ....rst => evennia.commands.default.admin.md} | 3 + ... evennia.commands.default.batchprocess.md} | 3 + ...t => evennia.commands.default.building.md} | 3 + ...vennia.commands.default.cmdset_account.md} | 3 + ...nnia.commands.default.cmdset_character.md} | 3 + ...vennia.commands.default.cmdset_session.md} | 3 + ...nia.commands.default.cmdset_unloggedin.md} | 3 + ....rst => evennia.commands.default.comms.md} | 3 + ...st => evennia.commands.default.general.md} | 3 + ...p.rst => evennia.commands.default.help.md} | 3 + ...efault.rst => evennia.commands.default.md} | 3 + ...=> evennia.commands.default.muxcommand.md} | 3 + ...> evennia.commands.default.syscommands.md} | 3 + ...rst => evennia.commands.default.system.md} | 3 + ....rst => evennia.commands.default.tests.md} | 3 + ...=> evennia.commands.default.unloggedin.md} | 3 + ...ennia.commands.rst => evennia.commands.md} | 3 + ...comms.admin.rst => evennia.comms.admin.md} | 3 + ...er.rst => evennia.comms.channelhandler.md} | 3 + ...comms.comms.rst => evennia.comms.comms.md} | 3 + ...managers.rst => evennia.comms.managers.md} | 3 + .../{evennia.comms.rst => evennia.comms.md} | 3 + ...mms.models.rst => evennia.comms.models.md} | 3 + ...b.barter.rst => evennia.contrib.barter.md} | 3 + ...u.rst => evennia.contrib.building_menu.md} | 3 + ...chargen.rst => evennia.contrib.chargen.md} | 3 + ...othing.rst => evennia.contrib.clothing.md} | 3 + ...s.rst => evennia.contrib.color_markups.md} | 3 + ...rst => evennia.contrib.custom_gametime.md} | 3 + ...ntrib.dice.rst => evennia.contrib.dice.md} | 3 + ...gin.rst => evennia.contrib.email_login.md} | 3 + ...m.rst => evennia.contrib.extended_room.md} | 3 + ...dfill.rst => evennia.contrib.fieldfill.md} | 3 + ...ersub.rst => evennia.contrib.gendersub.md} | 3 + ..._bar.rst => evennia.contrib.health_bar.md} | 3 + ....contrib.ingame_python.callbackhandler.md} | 3 + ...evennia.contrib.ingame_python.commands.md} | 3 + ...ennia.contrib.ingame_python.eventfuncs.md} | 3 + ...n.rst => evennia.contrib.ingame_python.md} | 3 + ... evennia.contrib.ingame_python.scripts.md} | 3 + ...=> evennia.contrib.ingame_python.tests.md} | 3 + ...nnia.contrib.ingame_python.typeclasses.md} | 3 + ...=> evennia.contrib.ingame_python.utils.md} | 3 + ...ntrib.mail.rst => evennia.contrib.mail.md} | 3 + ...lder.rst => evennia.contrib.mapbuilder.md} | 3 + ...evennia.contrib.rst => evennia.contrib.md} | 3 + ...ogin.rst => evennia.contrib.menu_login.md} | 3 + ...cer.rst => evennia.contrib.multidescer.md} | 3 + ...puzzles.rst => evennia.contrib.puzzles.md} | 3 + ...vennia.contrib.random_string_generator.md} | 3 + ...uage.rst => evennia.contrib.rplanguage.md} | 3 + ...system.rst => evennia.contrib.rpsystem.md} | 3 + ...t => evennia.contrib.security.auditing.md} | 3 + ...nnia.contrib.security.auditing.outputs.md} | 3 + ...ennia.contrib.security.auditing.server.md} | 3 + ...vennia.contrib.security.auditing.tests.md} | 3 + ...curity.rst => evennia.contrib.security.md} | 3 + ...door.rst => evennia.contrib.simpledoor.md} | 3 + ..._exit.rst => evennia.contrib.slow_exit.md} | 3 + ...npc.rst => evennia.contrib.talking_npc.md} | 3 + ...ect.rst => evennia.contrib.tree_select.md} | 3 + ...ttle.rst => evennia.contrib.turnbattle.md} | 3 + ...=> evennia.contrib.turnbattle.tb_basic.md} | 3 + ...=> evennia.contrib.turnbattle.tb_equip.md} | 3 + ...=> evennia.contrib.turnbattle.tb_items.md} | 3 + ...=> evennia.contrib.turnbattle.tb_magic.md} | 3 + ...=> evennia.contrib.turnbattle.tb_range.md} | 3 + ...ontrib.tutorial_examples.bodyfunctions.md} | 3 + ...ib.tutorial_examples.cmdset_red_button.md} | 3 + ...b.tutorial_examples.example_batch_code.md} | 3 + ...t => evennia.contrib.tutorial_examples.md} | 3 + ...a.contrib.tutorial_examples.red_button.md} | 3 + ...b.tutorial_examples.red_button_scripts.md} | 3 + ...vennia.contrib.tutorial_examples.tests.md} | 3 + ...nnia.contrib.tutorial_world.intro_menu.md} | 3 + ....rst => evennia.contrib.tutorial_world.md} | 3 + ... => evennia.contrib.tutorial_world.mob.md} | 3 + ...evennia.contrib.tutorial_world.objects.md} | 3 + ...> evennia.contrib.tutorial_world.rooms.md} | 3 + ...and.rst => evennia.contrib.unixcommand.md} | 3 + ...ness.rst => evennia.contrib.wilderness.md} | 3 + ...a.help.admin.rst => evennia.help.admin.md} | 3 + ...lp.manager.rst => evennia.help.manager.md} | 3 + .../api/{evennia.help.rst => evennia.help.md} | 3 + ...help.models.rst => evennia.help.models.md} | 3 + ...ckfuncs.rst => evennia.locks.lockfuncs.md} | 3 + ...ndler.rst => evennia.locks.lockhandler.md} | 3 + .../{evennia.locks.rst => evennia.locks.md} | 3 + docs/source/api/{evennia.rst => evennia.md} | 3 + ...cts.admin.rst => evennia.objects.admin.md} | 3 + ...manager.rst => evennia.objects.manager.md} | 3 + ...evennia.objects.rst => evennia.objects.md} | 3 + ...s.models.rst => evennia.objects.models.md} | 3 + ...objects.rst => evennia.objects.objects.md} | 3 + ...a.prototypes.rst => evennia.prototypes.md} | 3 + ....menus.rst => evennia.prototypes.menus.md} | 3 + ...cs.rst => evennia.prototypes.protfuncs.md} | 3 + ...s.rst => evennia.prototypes.prototypes.md} | 3 + ...wner.rst => evennia.prototypes.spawner.md} | 3 + ...pts.admin.rst => evennia.scripts.admin.md} | 3 + ...manager.rst => evennia.scripts.manager.md} | 3 + ...evennia.scripts.rst => evennia.scripts.md} | 3 + ...s.models.rst => evennia.scripts.models.md} | 3 + ....rst => evennia.scripts.monitorhandler.md} | 3 + ...r.rst => evennia.scripts.scripthandler.md} | 3 + ...scripts.rst => evennia.scripts.scripts.md} | 3 + ...ler.rst => evennia.scripts.taskhandler.md} | 3 + ...r.rst => evennia.scripts.tickerhandler.md} | 3 + ...rver.admin.rst => evennia.server.admin.md} | 3 + ...lient.rst => evennia.server.amp_client.md} | 3 + ...st => evennia.server.connection_wizard.md} | 3 + ...ons.rst => evennia.server.deprecations.md} | 3 + ...rst => evennia.server.evennia_launcher.md} | 3 + ...vennia.server.game_index_client.client.md} | 3 + ...st => evennia.server.game_index_client.md} | 3 + ...ennia.server.game_index_client.service.md} | 3 + ...up.rst => evennia.server.initial_setup.md} | 3 + ...funcs.rst => evennia.server.inputfuncs.md} | 3 + ....manager.rst => evennia.server.manager.md} | 3 + .../{evennia.server.rst => evennia.server.md} | 3 + ...er.models.rst => evennia.server.models.md} | 3 + ...l.amp.rst => evennia.server.portal.amp.md} | 3 + ...st => evennia.server.portal.amp_server.md} | 3 + ...rst => evennia.server.portal.grapevine.md} | 3 + ...l.irc.rst => evennia.server.portal.irc.md} | 3 + ...mccp.rst => evennia.server.portal.mccp.md} | 3 + ...er.portal.rst => evennia.server.portal.md} | 3 + ...mssp.rst => evennia.server.portal.mssp.md} | 3 + ...l.mxp.rst => evennia.server.portal.mxp.md} | 3 + ...naws.rst => evennia.server.portal.naws.md} | 3 + ...al.rst => evennia.server.portal.portal.md} | 3 + ...nia.server.portal.portalsessionhandler.md} | 3 + ...l.rss.rst => evennia.server.portal.rss.md} | 3 + ...l.ssh.rst => evennia.server.portal.ssh.md} | 3 + ...l.ssl.rst => evennia.server.portal.ssl.md} | 3 + ...t => evennia.server.portal.suppress_ga.md} | 3 + ...et.rst => evennia.server.portal.telnet.md} | 3 + ...st => evennia.server.portal.telnet_oob.md} | 3 + ...st => evennia.server.portal.telnet_ssl.md} | 3 + ...sts.rst => evennia.server.portal.tests.md} | 3 + ...ype.rst => evennia.server.portal.ttype.md} | 3 + ...rst => evennia.server.portal.webclient.md} | 3 + ...> evennia.server.portal.webclient_ajax.md} | 3 + ...> evennia.server.profiling.dummyrunner.md} | 3 + ....server.profiling.dummyrunner_settings.md} | 3 + ...filing.rst => evennia.server.profiling.md} | 3 + ...st => evennia.server.profiling.memplot.md} | 3 + ...vennia.server.profiling.settings_mixin.md} | 3 + ... evennia.server.profiling.test_queries.md} | 3 + ....rst => evennia.server.profiling.tests.md} | 3 + ... => evennia.server.profiling.timetrace.md} | 3 + ...er.server.rst => evennia.server.server.md} | 3 + ...on.rst => evennia.server.serversession.md} | 3 + ....session.rst => evennia.server.session.md} | 3 + ...r.rst => evennia.server.sessionhandler.md} | 3 + ....signals.rst => evennia.server.signals.md} | 3 + ...hrottle.rst => evennia.server.throttle.md} | 3 + ...ators.rst => evennia.server.validators.md} | 3 + ...server.rst => evennia.server.webserver.md} | 3 + ...efault.rst => evennia.settings_default.md} | 3 + ...admin.rst => evennia.typeclasses.admin.md} | 3 + ....rst => evennia.typeclasses.attributes.md} | 3 + ...rs.rst => evennia.typeclasses.managers.md} | 3 + ...typeclasses.rst => evennia.typeclasses.md} | 3 + ...dels.rst => evennia.typeclasses.models.md} | 3 + ...s.tags.rst => evennia.typeclasses.tags.md} | 3 + ...a.utils.ansi.rst => evennia.utils.ansi.md} | 3 + ...s.rst => evennia.utils.batchprocessors.md} | 3 + ...ainers.rst => evennia.utils.containers.md} | 3 + ...ils.create.rst => evennia.utils.create.md} | 3 + ...alize.rst => evennia.utils.dbserialize.md} | 3 + ...eveditor.rst => evennia.utils.eveditor.md} | 3 + ...ils.evform.rst => evennia.utils.evform.md} | 3 + ...ils.evmenu.rst => evennia.utils.evmenu.md} | 3 + ...ils.evmore.rst => evennia.utils.evmore.md} | 3 + ...s.evtable.rst => evennia.utils.evtable.md} | 3 + ...gametime.rst => evennia.utils.gametime.md} | 3 + ....rst => evennia.utils.idmapper.manager.md} | 3 + ...idmapper.rst => evennia.utils.idmapper.md} | 3 + ...s.rst => evennia.utils.idmapper.models.md} | 3 + ...ts.rst => evennia.utils.idmapper.tests.md} | 3 + ...funcs.rst => evennia.utils.inlinefuncs.md} | 3 + ...ils.logger.rst => evennia.utils.logger.md} | 3 + .../{evennia.utils.rst => evennia.utils.md} | 3 + ...ses.rst => evennia.utils.optionclasses.md} | 3 + ...ler.rst => evennia.utils.optionhandler.md} | 3 + ...field.rst => evennia.utils.picklefield.md} | 3 + ...ils.search.rst => evennia.utils.search.md} | 3 + ...es.rst => evennia.utils.test_resources.md} | 3 + ...xt2html.rst => evennia.utils.text2html.md} | 3 + ...utils.utils.rst => evennia.utils.utils.md} | 3 + ...cs.rst => evennia.utils.validatorfuncs.md} | 3 + .../api/{evennia.web.rst => evennia.web.md} | 3 + ...ennia.web.urls.rst => evennia.web.urls.md} | 3 + ...ends.rst => evennia.web.utils.backends.md} | 3 + ...t => evennia.web.utils.general_context.md} | 3 + ...nia.web.utils.rst => evennia.web.utils.md} | 3 + ...re.rst => evennia.web.utils.middleware.md} | 3 + ...s.tests.rst => evennia.web.utils.tests.md} | 3 + ...webclient.rst => evennia.web.webclient.md} | 3 + ...urls.rst => evennia.web.webclient.urls.md} | 3 + ...ews.rst => evennia.web.webclient.views.md} | 3 + ...forms.rst => evennia.web.website.forms.md} | 3 + ...web.website.rst => evennia.web.website.md} | 3 + ...nnia.web.website.templatetags.addclass.md} | 3 + ...st => evennia.web.website.templatetags.md} | 3 + ...tests.rst => evennia.web.website.tests.md} | 3 + ...e.urls.rst => evennia.web.website.urls.md} | 3 + ...views.rst => evennia.web.website.views.md} | 3 + docs/source/conf.py | 151 +- docs/source/index.md | 48 +- docs/source/toc.md | 513 +++- evennia/accounts/accounts.py | 2 +- evennia/accounts/manager.py | 2 +- evennia/commands/default/system.py | 2 + evennia/commands/default/unloggedin.py | 3 + evennia/objects/manager.py | 2 +- evennia/scripts/manager.py | 2 +- evennia/utils/search.py | 1 + 359 files changed, 3275 insertions(+), 4567 deletions(-) create mode 100755 docs/pylib/api_rst2md.py create mode 100644 docs/pylib/fmtwidth.py create mode 100644 docs/pylib/update_default_cmd_index.py delete mode 100644 docs/source/Default-Command-Help.md create mode 100644 docs/source/Default-Commands.md rename docs/source/api/{evennia-api.rst => evennia-api.md} (76%) rename docs/source/api/{evennia.accounts.accounts.rst => evennia.accounts.accounts.md} (89%) rename docs/source/api/{evennia.accounts.admin.rst => evennia.accounts.admin.md} (89%) rename docs/source/api/{evennia.accounts.bots.rst => evennia.accounts.bots.md} (88%) rename docs/source/api/{evennia.accounts.manager.rst => evennia.accounts.manager.md} (89%) rename docs/source/api/{evennia.accounts.rst => evennia.accounts.md} (94%) rename docs/source/api/{evennia.accounts.models.rst => evennia.accounts.models.md} (89%) rename docs/source/api/{evennia.commands.cmdhandler.rst => evennia.commands.cmdhandler.md} (90%) rename docs/source/api/{evennia.commands.cmdparser.rst => evennia.commands.cmdparser.md} (89%) rename docs/source/api/{evennia.commands.cmdset.rst => evennia.commands.cmdset.md} (89%) rename docs/source/api/{evennia.commands.cmdsethandler.rst => evennia.commands.cmdsethandler.md} (90%) rename docs/source/api/{evennia.commands.command.rst => evennia.commands.command.md} (89%) rename docs/source/api/{evennia.commands.default.account.rst => evennia.commands.default.account.md} (90%) rename docs/source/api/{evennia.commands.default.admin.rst => evennia.commands.default.admin.md} (90%) rename docs/source/api/{evennia.commands.default.batchprocess.rst => evennia.commands.default.batchprocess.md} (91%) rename docs/source/api/{evennia.commands.default.building.rst => evennia.commands.default.building.md} (90%) rename docs/source/api/{evennia.commands.default.cmdset_account.rst => evennia.commands.default.cmdset_account.md} (91%) rename docs/source/api/{evennia.commands.default.cmdset_character.rst => evennia.commands.default.cmdset_character.md} (92%) rename docs/source/api/{evennia.commands.default.cmdset_session.rst => evennia.commands.default.cmdset_session.md} (91%) rename docs/source/api/{evennia.commands.default.cmdset_unloggedin.rst => evennia.commands.default.cmdset_unloggedin.md} (92%) rename docs/source/api/{evennia.commands.default.comms.rst => evennia.commands.default.comms.md} (90%) rename docs/source/api/{evennia.commands.default.general.rst => evennia.commands.default.general.md} (90%) rename docs/source/api/{evennia.commands.default.help.rst => evennia.commands.default.help.md} (90%) rename docs/source/api/{evennia.commands.default.rst => evennia.commands.default.md} (97%) rename docs/source/api/{evennia.commands.default.muxcommand.rst => evennia.commands.default.muxcommand.md} (91%) rename docs/source/api/{evennia.commands.default.syscommands.rst => evennia.commands.default.syscommands.md} (91%) rename docs/source/api/{evennia.commands.default.system.rst => evennia.commands.default.system.md} (90%) rename docs/source/api/{evennia.commands.default.tests.rst => evennia.commands.default.tests.md} (90%) rename docs/source/api/{evennia.commands.default.unloggedin.rst => evennia.commands.default.unloggedin.md} (91%) rename docs/source/api/{evennia.commands.rst => evennia.commands.md} (95%) rename docs/source/api/{evennia.comms.admin.rst => evennia.comms.admin.md} (88%) rename docs/source/api/{evennia.comms.channelhandler.rst => evennia.comms.channelhandler.md} (90%) rename docs/source/api/{evennia.comms.comms.rst => evennia.comms.comms.md} (88%) rename docs/source/api/{evennia.comms.managers.rst => evennia.comms.managers.md} (89%) rename docs/source/api/{evennia.comms.rst => evennia.comms.md} (94%) rename docs/source/api/{evennia.comms.models.rst => evennia.comms.models.md} (88%) rename docs/source/api/{evennia.contrib.barter.rst => evennia.contrib.barter.md} (89%) rename docs/source/api/{evennia.contrib.building_menu.rst => evennia.contrib.building_menu.md} (90%) rename docs/source/api/{evennia.contrib.chargen.rst => evennia.contrib.chargen.md} (89%) rename docs/source/api/{evennia.contrib.clothing.rst => evennia.contrib.clothing.md} (89%) rename docs/source/api/{evennia.contrib.color_markups.rst => evennia.contrib.color_markups.md} (90%) rename docs/source/api/{evennia.contrib.custom_gametime.rst => evennia.contrib.custom_gametime.md} (90%) rename docs/source/api/{evennia.contrib.dice.rst => evennia.contrib.dice.md} (88%) rename docs/source/api/{evennia.contrib.email_login.rst => evennia.contrib.email_login.md} (90%) rename docs/source/api/{evennia.contrib.extended_room.rst => evennia.contrib.extended_room.md} (90%) rename docs/source/api/{evennia.contrib.fieldfill.rst => evennia.contrib.fieldfill.md} (89%) rename docs/source/api/{evennia.contrib.gendersub.rst => evennia.contrib.gendersub.md} (89%) rename docs/source/api/{evennia.contrib.health_bar.rst => evennia.contrib.health_bar.md} (90%) rename docs/source/api/{evennia.contrib.ingame_python.callbackhandler.rst => evennia.contrib.ingame_python.callbackhandler.md} (92%) rename docs/source/api/{evennia.contrib.ingame_python.commands.rst => evennia.contrib.ingame_python.commands.md} (91%) rename docs/source/api/{evennia.contrib.ingame_python.eventfuncs.rst => evennia.contrib.ingame_python.eventfuncs.md} (91%) rename docs/source/api/{evennia.contrib.ingame_python.rst => evennia.contrib.ingame_python.md} (96%) rename docs/source/api/{evennia.contrib.ingame_python.scripts.rst => evennia.contrib.ingame_python.scripts.md} (91%) rename docs/source/api/{evennia.contrib.ingame_python.tests.rst => evennia.contrib.ingame_python.tests.md} (91%) rename docs/source/api/{evennia.contrib.ingame_python.typeclasses.rst => evennia.contrib.ingame_python.typeclasses.md} (92%) rename docs/source/api/{evennia.contrib.ingame_python.utils.rst => evennia.contrib.ingame_python.utils.md} (91%) rename docs/source/api/{evennia.contrib.mail.rst => evennia.contrib.mail.md} (88%) rename docs/source/api/{evennia.contrib.mapbuilder.rst => evennia.contrib.mapbuilder.md} (89%) rename docs/source/api/{evennia.contrib.rst => evennia.contrib.md} (98%) rename docs/source/api/{evennia.contrib.menu_login.rst => evennia.contrib.menu_login.md} (90%) rename docs/source/api/{evennia.contrib.multidescer.rst => evennia.contrib.multidescer.md} (90%) rename docs/source/api/{evennia.contrib.puzzles.rst => evennia.contrib.puzzles.md} (89%) rename docs/source/api/{evennia.contrib.random_string_generator.rst => evennia.contrib.random_string_generator.md} (91%) rename docs/source/api/{evennia.contrib.rplanguage.rst => evennia.contrib.rplanguage.md} (89%) rename docs/source/api/{evennia.contrib.rpsystem.rst => evennia.contrib.rpsystem.md} (89%) rename docs/source/api/{evennia.contrib.security.auditing.rst => evennia.contrib.security.auditing.md} (95%) rename docs/source/api/{evennia.contrib.security.auditing.outputs.rst => evennia.contrib.security.auditing.outputs.md} (91%) rename docs/source/api/{evennia.contrib.security.auditing.server.rst => evennia.contrib.security.auditing.server.md} (91%) rename docs/source/api/{evennia.contrib.security.auditing.tests.rst => evennia.contrib.security.auditing.tests.md} (91%) rename docs/source/api/{evennia.contrib.security.rst => evennia.contrib.security.md} (92%) rename docs/source/api/{evennia.contrib.simpledoor.rst => evennia.contrib.simpledoor.md} (89%) rename docs/source/api/{evennia.contrib.slow_exit.rst => evennia.contrib.slow_exit.md} (89%) rename docs/source/api/{evennia.contrib.talking_npc.rst => evennia.contrib.talking_npc.md} (90%) rename docs/source/api/{evennia.contrib.tree_select.rst => evennia.contrib.tree_select.md} (90%) rename docs/source/api/{evennia.contrib.turnbattle.rst => evennia.contrib.turnbattle.md} (95%) rename docs/source/api/{evennia.contrib.turnbattle.tb_basic.rst => evennia.contrib.turnbattle.tb_basic.md} (91%) rename docs/source/api/{evennia.contrib.turnbattle.tb_equip.rst => evennia.contrib.turnbattle.tb_equip.md} (91%) rename docs/source/api/{evennia.contrib.turnbattle.tb_items.rst => evennia.contrib.turnbattle.tb_items.md} (91%) rename docs/source/api/{evennia.contrib.turnbattle.tb_magic.rst => evennia.contrib.turnbattle.tb_magic.md} (91%) rename docs/source/api/{evennia.contrib.turnbattle.tb_range.rst => evennia.contrib.turnbattle.tb_range.md} (91%) rename docs/source/api/{evennia.contrib.tutorial_examples.bodyfunctions.rst => evennia.contrib.tutorial_examples.bodyfunctions.md} (92%) rename docs/source/api/{evennia.contrib.tutorial_examples.cmdset_red_button.rst => evennia.contrib.tutorial_examples.cmdset_red_button.md} (93%) rename docs/source/api/{evennia.contrib.tutorial_examples.example_batch_code.rst => evennia.contrib.tutorial_examples.example_batch_code.md} (93%) rename docs/source/api/{evennia.contrib.tutorial_examples.rst => evennia.contrib.tutorial_examples.md} (96%) rename docs/source/api/{evennia.contrib.tutorial_examples.red_button.rst => evennia.contrib.tutorial_examples.red_button.md} (92%) rename docs/source/api/{evennia.contrib.tutorial_examples.red_button_scripts.rst => evennia.contrib.tutorial_examples.red_button_scripts.md} (93%) rename docs/source/api/{evennia.contrib.tutorial_examples.tests.rst => evennia.contrib.tutorial_examples.tests.md} (91%) rename docs/source/api/{evennia.contrib.tutorial_world.intro_menu.rst => evennia.contrib.tutorial_world.intro_menu.md} (92%) rename docs/source/api/{evennia.contrib.tutorial_world.rst => evennia.contrib.tutorial_world.md} (95%) rename docs/source/api/{evennia.contrib.tutorial_world.mob.rst => evennia.contrib.tutorial_world.mob.md} (91%) rename docs/source/api/{evennia.contrib.tutorial_world.objects.rst => evennia.contrib.tutorial_world.objects.md} (91%) rename docs/source/api/{evennia.contrib.tutorial_world.rooms.rst => evennia.contrib.tutorial_world.rooms.md} (91%) rename docs/source/api/{evennia.contrib.unixcommand.rst => evennia.contrib.unixcommand.md} (90%) rename docs/source/api/{evennia.contrib.wilderness.rst => evennia.contrib.wilderness.md} (89%) rename docs/source/api/{evennia.help.admin.rst => evennia.help.admin.md} (88%) rename docs/source/api/{evennia.help.manager.rst => evennia.help.manager.md} (88%) rename docs/source/api/{evennia.help.rst => evennia.help.md} (92%) rename docs/source/api/{evennia.help.models.rst => evennia.help.models.md} (88%) rename docs/source/api/{evennia.locks.lockfuncs.rst => evennia.locks.lockfuncs.md} (89%) rename docs/source/api/{evennia.locks.lockhandler.rst => evennia.locks.lockhandler.md} (89%) rename docs/source/api/{evennia.locks.rst => evennia.locks.md} (92%) rename docs/source/api/{evennia.rst => evennia.md} (96%) rename docs/source/api/{evennia.objects.admin.rst => evennia.objects.admin.md} (88%) rename docs/source/api/{evennia.objects.manager.rst => evennia.objects.manager.md} (89%) rename docs/source/api/{evennia.objects.rst => evennia.objects.md} (93%) rename docs/source/api/{evennia.objects.models.rst => evennia.objects.models.md} (89%) rename docs/source/api/{evennia.objects.objects.rst => evennia.objects.objects.md} (89%) rename docs/source/api/{evennia.prototypes.rst => evennia.prototypes.md} (94%) rename docs/source/api/{evennia.prototypes.menus.rst => evennia.prototypes.menus.md} (89%) rename docs/source/api/{evennia.prototypes.protfuncs.rst => evennia.prototypes.protfuncs.md} (90%) rename docs/source/api/{evennia.prototypes.prototypes.rst => evennia.prototypes.prototypes.md} (90%) rename docs/source/api/{evennia.prototypes.spawner.rst => evennia.prototypes.spawner.md} (89%) rename docs/source/api/{evennia.scripts.admin.rst => evennia.scripts.admin.md} (88%) rename docs/source/api/{evennia.scripts.manager.rst => evennia.scripts.manager.md} (89%) rename docs/source/api/{evennia.scripts.rst => evennia.scripts.md} (95%) rename docs/source/api/{evennia.scripts.models.rst => evennia.scripts.models.md} (89%) rename docs/source/api/{evennia.scripts.monitorhandler.rst => evennia.scripts.monitorhandler.md} (90%) rename docs/source/api/{evennia.scripts.scripthandler.rst => evennia.scripts.scripthandler.md} (90%) rename docs/source/api/{evennia.scripts.scripts.rst => evennia.scripts.scripts.md} (89%) rename docs/source/api/{evennia.scripts.taskhandler.rst => evennia.scripts.taskhandler.md} (90%) rename docs/source/api/{evennia.scripts.tickerhandler.rst => evennia.scripts.tickerhandler.md} (90%) rename docs/source/api/{evennia.server.admin.rst => evennia.server.admin.md} (88%) rename docs/source/api/{evennia.server.amp_client.rst => evennia.server.amp_client.md} (89%) rename docs/source/api/{evennia.server.connection_wizard.rst => evennia.server.connection_wizard.md} (90%) rename docs/source/api/{evennia.server.deprecations.rst => evennia.server.deprecations.md} (90%) rename docs/source/api/{evennia.server.evennia_launcher.rst => evennia.server.evennia_launcher.md} (90%) rename docs/source/api/{evennia.server.game_index_client.client.rst => evennia.server.game_index_client.client.md} (91%) rename docs/source/api/{evennia.server.game_index_client.rst => evennia.server.game_index_client.md} (94%) rename docs/source/api/{evennia.server.game_index_client.service.rst => evennia.server.game_index_client.service.md} (91%) rename docs/source/api/{evennia.server.initial_setup.rst => evennia.server.initial_setup.md} (90%) rename docs/source/api/{evennia.server.inputfuncs.rst => evennia.server.inputfuncs.md} (89%) rename docs/source/api/{evennia.server.manager.rst => evennia.server.manager.md} (89%) rename docs/source/api/{evennia.server.rst => evennia.server.md} (97%) rename docs/source/api/{evennia.server.models.rst => evennia.server.models.md} (88%) rename docs/source/api/{evennia.server.portal.amp.rst => evennia.server.portal.amp.md} (89%) rename docs/source/api/{evennia.server.portal.amp_server.rst => evennia.server.portal.amp_server.md} (90%) rename docs/source/api/{evennia.server.portal.grapevine.rst => evennia.server.portal.grapevine.md} (90%) rename docs/source/api/{evennia.server.portal.irc.rst => evennia.server.portal.irc.md} (89%) rename docs/source/api/{evennia.server.portal.mccp.rst => evennia.server.portal.mccp.md} (89%) rename docs/source/api/{evennia.server.portal.rst => evennia.server.portal.md} (97%) rename docs/source/api/{evennia.server.portal.mssp.rst => evennia.server.portal.mssp.md} (89%) rename docs/source/api/{evennia.server.portal.mxp.rst => evennia.server.portal.mxp.md} (89%) rename docs/source/api/{evennia.server.portal.naws.rst => evennia.server.portal.naws.md} (89%) rename docs/source/api/{evennia.server.portal.portal.rst => evennia.server.portal.portal.md} (90%) rename docs/source/api/{evennia.server.portal.portalsessionhandler.rst => evennia.server.portal.portalsessionhandler.md} (92%) rename docs/source/api/{evennia.server.portal.rss.rst => evennia.server.portal.rss.md} (89%) rename docs/source/api/{evennia.server.portal.ssh.rst => evennia.server.portal.ssh.md} (89%) rename docs/source/api/{evennia.server.portal.ssl.rst => evennia.server.portal.ssl.md} (89%) rename docs/source/api/{evennia.server.portal.suppress_ga.rst => evennia.server.portal.suppress_ga.md} (91%) rename docs/source/api/{evennia.server.portal.telnet.rst => evennia.server.portal.telnet.md} (90%) rename docs/source/api/{evennia.server.portal.telnet_oob.rst => evennia.server.portal.telnet_oob.md} (90%) rename docs/source/api/{evennia.server.portal.telnet_ssl.rst => evennia.server.portal.telnet_ssl.md} (90%) rename docs/source/api/{evennia.server.portal.tests.rst => evennia.server.portal.tests.md} (90%) rename docs/source/api/{evennia.server.portal.ttype.rst => evennia.server.portal.ttype.md} (90%) rename docs/source/api/{evennia.server.portal.webclient.rst => evennia.server.portal.webclient.md} (90%) rename docs/source/api/{evennia.server.portal.webclient_ajax.rst => evennia.server.portal.webclient_ajax.md} (91%) rename docs/source/api/{evennia.server.profiling.dummyrunner.rst => evennia.server.profiling.dummyrunner.md} (91%) rename docs/source/api/{evennia.server.profiling.dummyrunner_settings.rst => evennia.server.profiling.dummyrunner_settings.md} (92%) rename docs/source/api/{evennia.server.profiling.rst => evennia.server.profiling.md} (96%) rename docs/source/api/{evennia.server.profiling.memplot.rst => evennia.server.profiling.memplot.md} (90%) rename docs/source/api/{evennia.server.profiling.settings_mixin.rst => evennia.server.profiling.settings_mixin.md} (91%) rename docs/source/api/{evennia.server.profiling.test_queries.rst => evennia.server.profiling.test_queries.md} (91%) rename docs/source/api/{evennia.server.profiling.tests.rst => evennia.server.profiling.tests.md} (90%) rename docs/source/api/{evennia.server.profiling.timetrace.rst => evennia.server.profiling.timetrace.md} (91%) rename docs/source/api/{evennia.server.server.rst => evennia.server.server.md} (88%) rename docs/source/api/{evennia.server.serversession.rst => evennia.server.serversession.md} (90%) rename docs/source/api/{evennia.server.session.rst => evennia.server.session.md} (89%) rename docs/source/api/{evennia.server.sessionhandler.rst => evennia.server.sessionhandler.md} (90%) rename docs/source/api/{evennia.server.signals.rst => evennia.server.signals.md} (89%) rename docs/source/api/{evennia.server.throttle.rst => evennia.server.throttle.md} (89%) rename docs/source/api/{evennia.server.validators.rst => evennia.server.validators.md} (89%) rename docs/source/api/{evennia.server.webserver.rst => evennia.server.webserver.md} (89%) rename docs/source/api/{evennia.settings_default.rst => evennia.settings_default.md} (89%) rename docs/source/api/{evennia.typeclasses.admin.rst => evennia.typeclasses.admin.md} (89%) rename docs/source/api/{evennia.typeclasses.attributes.rst => evennia.typeclasses.attributes.md} (90%) rename docs/source/api/{evennia.typeclasses.managers.rst => evennia.typeclasses.managers.md} (90%) rename docs/source/api/{evennia.typeclasses.rst => evennia.typeclasses.md} (94%) rename docs/source/api/{evennia.typeclasses.models.rst => evennia.typeclasses.models.md} (89%) rename docs/source/api/{evennia.typeclasses.tags.rst => evennia.typeclasses.tags.md} (89%) rename docs/source/api/{evennia.utils.ansi.rst => evennia.utils.ansi.md} (88%) rename docs/source/api/{evennia.utils.batchprocessors.rst => evennia.utils.batchprocessors.md} (90%) rename docs/source/api/{evennia.utils.containers.rst => evennia.utils.containers.md} (89%) rename docs/source/api/{evennia.utils.create.rst => evennia.utils.create.md} (88%) rename docs/source/api/{evennia.utils.dbserialize.rst => evennia.utils.dbserialize.md} (89%) rename docs/source/api/{evennia.utils.eveditor.rst => evennia.utils.eveditor.md} (89%) rename docs/source/api/{evennia.utils.evform.rst => evennia.utils.evform.md} (88%) rename docs/source/api/{evennia.utils.evmenu.rst => evennia.utils.evmenu.md} (88%) rename docs/source/api/{evennia.utils.evmore.rst => evennia.utils.evmore.md} (88%) rename docs/source/api/{evennia.utils.evtable.rst => evennia.utils.evtable.md} (88%) rename docs/source/api/{evennia.utils.gametime.rst => evennia.utils.gametime.md} (89%) rename docs/source/api/{evennia.utils.idmapper.manager.rst => evennia.utils.idmapper.manager.md} (90%) rename docs/source/api/{evennia.utils.idmapper.rst => evennia.utils.idmapper.md} (93%) rename docs/source/api/{evennia.utils.idmapper.models.rst => evennia.utils.idmapper.models.md} (90%) rename docs/source/api/{evennia.utils.idmapper.tests.rst => evennia.utils.idmapper.tests.md} (90%) rename docs/source/api/{evennia.utils.inlinefuncs.rst => evennia.utils.inlinefuncs.md} (89%) rename docs/source/api/{evennia.utils.logger.rst => evennia.utils.logger.md} (88%) rename docs/source/api/{evennia.utils.rst => evennia.utils.md} (97%) rename docs/source/api/{evennia.utils.optionclasses.rst => evennia.utils.optionclasses.md} (90%) rename docs/source/api/{evennia.utils.optionhandler.rst => evennia.utils.optionhandler.md} (90%) rename docs/source/api/{evennia.utils.picklefield.rst => evennia.utils.picklefield.md} (89%) rename docs/source/api/{evennia.utils.search.rst => evennia.utils.search.md} (88%) rename docs/source/api/{evennia.utils.test_resources.rst => evennia.utils.test_resources.md} (90%) rename docs/source/api/{evennia.utils.text2html.rst => evennia.utils.text2html.md} (89%) rename docs/source/api/{evennia.utils.utils.rst => evennia.utils.utils.md} (88%) rename docs/source/api/{evennia.utils.validatorfuncs.rst => evennia.utils.validatorfuncs.md} (90%) rename docs/source/api/{evennia.web.rst => evennia.web.md} (93%) rename docs/source/api/{evennia.web.urls.rst => evennia.web.urls.md} (87%) rename docs/source/api/{evennia.web.utils.backends.rst => evennia.web.utils.backends.md} (89%) rename docs/source/api/{evennia.web.utils.general_context.rst => evennia.web.utils.general_context.md} (91%) rename docs/source/api/{evennia.web.utils.rst => evennia.web.utils.md} (94%) rename docs/source/api/{evennia.web.utils.middleware.rst => evennia.web.utils.middleware.md} (90%) rename docs/source/api/{evennia.web.utils.tests.rst => evennia.web.utils.tests.md} (89%) rename docs/source/api/{evennia.web.webclient.rst => evennia.web.webclient.md} (93%) rename docs/source/api/{evennia.web.webclient.urls.rst => evennia.web.webclient.urls.md} (89%) rename docs/source/api/{evennia.web.webclient.views.rst => evennia.web.webclient.views.md} (90%) rename docs/source/api/{evennia.web.website.forms.rst => evennia.web.website.forms.md} (89%) rename docs/source/api/{evennia.web.website.rst => evennia.web.website.md} (95%) rename docs/source/api/{evennia.web.website.templatetags.addclass.rst => evennia.web.website.templatetags.addclass.md} (91%) rename docs/source/api/{evennia.web.website.templatetags.rst => evennia.web.website.templatetags.md} (93%) rename docs/source/api/{evennia.web.website.tests.rst => evennia.web.website.tests.md} (89%) rename docs/source/api/{evennia.web.website.urls.rst => evennia.web.website.urls.md} (89%) rename docs/source/api/{evennia.web.website.views.rst => evennia.web.website.views.md} (89%) diff --git a/docs/Makefile b/docs/Makefile index 0cc01b83ed..0493d9760c 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -11,6 +11,7 @@ SPHINXBUILD ?= sphinx-build SPHINXMULTIVERSION ?= sphinx-multiversion SPHINXAPIDOC ?= sphinx-apidoc SPHINXAPIDOCOPTS = --tocfile evennia-api --module-first --force -d 6 --separate --templatedir=$(SOURCEDIR)/_templates/ +SPHINXAPIDOCOPTSQUICK = --tocfile evennia-api --module-first -d 6 --separate --templatedir=$(SOURCEDIR)/_templates/ SPHINXAPIDOCENV = members,undoc-members,show-inheritance SPHINXAPIDOCEXCLUDE = ../*/migrations/* ../evennia/game_template/* ../evennia/*/tests/* ../evennia/*/tests.py @@ -28,9 +29,12 @@ QUICKFILES= help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @echo "Evennia-specific: " - @echo " $(cblue)install$(cnorm) to get build requirements" + @echo " $(cblue)install$(cnorm) to get doc build requirements" @echo " $(cblue)clean$(cnorm) to remove remnants of a previous build" + @echo " $(cblue)quick$(cnorm) to build local docs but skip the autodocs (for quick testing)" + @echo " $(cblue)quickstrict$(cnorm) to build like 'quick' but abort immediately on any error" @echo " $(cblue)local$(cnorm) to build local html docs of the current branch (no multiversion)." + @echo " $(cblue)localupdate$(cnorm) to build local html docs (only update changes) @echo " $(cblue)mv-local$(cnorm) to build multiversion html docs, without deploying (req: local git commit first)" @echo " $(cblue)deploy$(cnorm) to deploy previously built multiversion docs online (req: commit and github push access)" @echo " $(cblue)release$(cnorm) to build + deploy multiversion docs online (req: commit and github push access)" @@ -66,6 +70,10 @@ _autodoc-index: make _clean_api_index @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) SPHINX_APIDOC_OPTIONS=$(SPHINXAPIDOCENV) $(SPHINXAPIDOC) $(SPHINXAPIDOCOPTS) -o $(SOURCEDIR)/api/ $(EVDIR) $(SPHINXAPIDOCEXCLUDE) make _reformat_apidoc_headers + pylib/api_rst2md.py + +_quick_autodoc-index: + @EVDIR=$(EVDIR) EVGAMEDIR=$(EVGAMEDIR) SPHINX_APIDOC_OPTIONS=$(SPHINXAPIDOCENV) $(SPHINXAPIDOC) $(SPHINXAPIDOCOPTSQUICK) -o $(SOURCEDIR)/api/ $(EVDIR) $(SPHINXAPIDOCEXCLUDE) _multiversion-autodoc-index: make _autodoc-index @@ -102,6 +110,7 @@ pdf: @echo "To see result, open evennia/docs/build/latex/evennia.pdf in a PDF reader." quick: + make _quick_autodoc-index make _quick-html-build $(FILES) @echo "" @echo "Documentation built (single version, no autodocs)." @@ -121,6 +130,14 @@ local: @echo "Documentation built (single version)." @echo "To see result, open evennia/docs/build/html/index.html in a browser." +# build only that which updated since last run (no clean or index-creation) +localupdate: + make _check-env + make _html-build + @echo "" + @echo "Documentation built (single version, only updates, no auto-index)." + @echo "To see result, open evennia/docs/build/html/index.html in a browser." + # note that this should be done for each relevant multiversion branch. mv-index: make _multiversion-autodoc-index diff --git a/docs/README.md b/docs/README.md index 4912dc921e..2a9b3e6a01 100644 --- a/docs/README.md +++ b/docs/README.md @@ -348,7 +348,7 @@ See below for examples of this. This will display a one-line note that will pop even more than a normal `> note`. ```` -```important:: +```{important} This is important because it is! ``` ```` @@ -359,7 +359,7 @@ A warning block is used to draw attention to particularly dangerous things, or f mess up. ```` -```warning:: +```{warning} Be careful about this ... ```` diff --git a/docs/pylib/api_rst2md.py b/docs/pylib/api_rst2md.py new file mode 100755 index 0000000000..5bb3bc00f6 --- /dev/null +++ b/docs/pylib/api_rst2md.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +""" +Remap autodoc API rst files to md files and wrap their contents. + +""" + +from glob import glob +from os.path import abspath, join as pathjoin, dirname +from os import rename + + +def _rst2md(filename_rst): + + with open(filename_rst, 'r') as fil: + # read rst file, reformat and save + txt = fil.read() + with open(filename_rst, 'w') as fil: + txt = "```{eval-rst}\n" + txt + "\n```" + fil.write(txt) + + # rename .rst file to .md file + filename, _ = filename_rst.rsplit('.', 1) + filename_md = filename + ".md" + rename(filename_rst, filename_md) + + +if __name__ == "__main__": + apidir = pathjoin(dirname(dirname(abspath(__file__))), "source", "api") + for filename_rst in glob(pathjoin(apidir, "*.rst")): + _rst2md(filename_rst) + print(" Converted {apidir}/*.rst files to .md files".format(apidir=apidir)) diff --git a/docs/pylib/auto_link_remapper.py b/docs/pylib/auto_link_remapper.py index 56b72c5631..7f3205daeb 100644 --- a/docs/pylib/auto_link_remapper.py +++ b/docs/pylib/auto_link_remapper.py @@ -9,7 +9,7 @@ import re from collections import defaultdict from sphinx.errors import DocumentError from pathlib import Path -from os.path import abspath, dirname, join as pathjoin, sep, relpath +from os.path import abspath, dirname, join as pathjoin, relpath _IGNORE_FILES = [] _SOURCEDIR_NAME = "source" @@ -19,15 +19,54 @@ _NO_REMAP_STARTSWITH = [ "http://", "https://", "github:", - "api:", "feature-request", "report-bug", "issue", "bug-report", ] - +# remove these prefixes from the url +_STRIP_PREFIX = [ + "../../api/", + "../api/", + "./api/", + "api/", + "api:", +] TXT_REMAPS = {} -URL_REMAPS = {} +# "Developer Central": "Evennia Components overview", +# "Getting Started": "Setup Quickstart", +# } +URL_REMAPS = { + "Default-Command-Help": "Default-Commands", + "./Default-Command-Help.md": "Default-Commands.md" +} +# "Developer-Central": "Components/Components-Overview", +# "Tutorials": "Howto/Howto-Overview", +# "../Howto/Starting/Directory-Overview": "Gamedir-Overview", +# "Howto/Starting/Directory-Overview": "Gamedir-Overview", +# "Starting/Directory-Overview": "Gamedir-Overview", +# "Directory-Overview": "Gamedir-Overview", +# "../Setup/Getting-Started": "Setup-Quickstart", +# "Setup/Getting-Started": "Setup-Quickstart", +# "Setup-Quickstart": "Setup-Quickstart", +# "Setup-Quickstart": "Getting-Started", # back again +# "First-Steps-Coding": "Starting-Part1", +# "../Howto/Starting/Adding-Command-Tutorial": "Adding-Commands", +# "Howto/Starting/Adding-Command-Tutorial": "Adding-Commands", +# "Starting/Adding-Command-Tutorial": "Adding-Commands", +# "Adding-Command-Tutorial": "Adding-Commands", +# "CmdSet": "Command-Sets", +# "Spawner": "Prototypes", +# "issue": "github:issue", +# "issues": "github:issue", +# "bug": "github:issue", +# "bug-report": "github:issue", +# "./Default-Command-Help": "api:evennia.commands.default#modules", +# "../Components/Default-Command-Help": "api:evennia.commands.default#modules", +# "../../../Components/Default-Command-Help": "api:evennia.commands.default#modules", +# "./Locks.md#permissions": "Permissions", +# "Permissions": "./Locks.md#permissions", # back again +# } _USED_REFS = {} @@ -96,11 +135,11 @@ def auto_link_remapper(no_autodoc=False): # normal reference-links [txt](urls) ref_regex = re.compile( - r"\[(?P[\w -\[\]\`]+?)\]\((?P.+?)\)", re.I + re.S + re.U + re.M + r"\[(?P[\n\w -\[\]\`]+?)\]\((?P.+?)\)", re.I + re.S + re.U + re.M ) # in document references ref_doc_regex = re.compile( - r"\[(?P[\w -\`]+?)\]:\s+?(?P.+?)(?=$|\n)", re.I + re.S + re.U + re.M + r"\[(?P[\n\w -\`]+?)\]:\s+?(?P.+?)(?=$|\n)", re.I + re.S + re.U + re.M ) def _sub(match): @@ -112,27 +151,38 @@ def auto_link_remapper(no_autodoc=False): txt = TXT_REMAPS.get(txt, txt) url = URL_REMAPS.get(url, url) + for strip_prefix in _STRIP_PREFIX: + if url.startswith(strip_prefix): + url = url[len(strip_prefix):] + if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH): + # skip regular http/s urls etc return f"[{txt}]({url})" - if "http" in url and "://" in url: - urlout = url - else: - fname, *part = url.rsplit("/", 1) - fname = part[0] if part else fname + if url.startswith("evennia."): + # api link - we want to remove legacy #reference and remove .md + if '#' in url: + _, url = url.rsplit('#', 1) + if url.endswith(".md"): + url, _ = url.rsplit('.', 1) + return f"[{txt}]({url})" + + fname, *part = url.rsplit("/", 1) + fname = part[0] if part else fname + fname, *anchor = fname.rsplit("#", 1) + if ".md" in fname: fname = fname.rsplit(".", 1)[0] - fname, *anchor = fname.rsplit("#", 1) - if not _CURRFILE.endswith("toc.md"): - _USED_REFS[fname] = url + if not _CURRFILE.endswith("toc.md"): + _USED_REFS[fname] = url - if _CURRFILE in docref_map and fname in docref_map[_CURRFILE]: - cfilename = _CURRFILE.rsplit("/", 1)[-1] - urlout = docref_map[_CURRFILE][fname] + ("#" + anchor[0] if anchor else "") - if urlout != url: - print(f" {cfilename}: [{txt}]({url}) -> [{txt}]({urlout})") - else: - urlout = url + if _CURRFILE in docref_map and fname in docref_map[_CURRFILE]: + cfilename = _CURRFILE.rsplit("/", 1)[-1] + urlout = docref_map[_CURRFILE][fname] + ".md" + ("#" + anchor[0].lower() if anchor else "") + if urlout != url: + print(f" {cfilename}: [{txt}]({url}) -> [{txt}]({urlout})") + else: + urlout = url return f"[{txt}]({urlout})" @@ -145,11 +195,21 @@ def auto_link_remapper(no_autodoc=False): txt = TXT_REMAPS.get(txt, txt) url = URL_REMAPS.get(url, url) + for strip_prefix in _STRIP_PREFIX: + if url.startswith(strip_prefix): + url = url[len(strip_prefix):] + if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH): return f"[{txt}]: {url}" + urlout = url + if "http" in url and "://" in url: urlout = url + elif url.startswith("evennia."): + # api link - we want to remove legacy #reference + if '#' in url: + _, urlout = url.rsplit('#', 1) else: fname, *part = url.rsplit("/", 1) fname = part[0] if part else fname @@ -190,12 +250,12 @@ def auto_link_remapper(no_autodoc=False): print(f" -- Auto-corrected links in {count} documents.") for (fname, src_url) in sorted(toc_map.items(), key=lambda tup: tup[0]): - if fname not in _USED_REFS: + if fname not in _USED_REFS and not src_url.startswith("api/"): print(f" ORPHANED DOC: no refs found to {src_url}.md") # write tocfile with open(_TOC_FILE, "w") as fil: - fil.write("# Toc\n") + fil.write("```{toctree}\n") if not no_autodoc: fil.write("- [API root](api/evennia-api.rst)") @@ -205,17 +265,14 @@ def auto_link_remapper(no_autodoc=False): if ref == "toc": continue - if "Part1/" in ref: - continue + # if not "/" in ref: + # ref = "./" + ref - if not "/" in ref: - ref = "./" + ref - - linkname = ref.replace("-", " ") - fil.write(f"\n- [{linkname}]({ref})") + # linkname = ref.replace("-", " ") + fil.write(f"\n{ref}") # - [{linkname}]({ref})") # we add a self-reference so the toc itself is also a part of a toctree - fil.write("\n\n```toctree::\n :hidden:\n\n toc\n```") + fil.write("\n```\n\n```{toctree}\n :hidden:\n\ntoc\n```") print(" -- Auto-Remapper finished.") diff --git a/docs/pylib/copy_from_wiki.py b/docs/pylib/copy_from_wiki.py index caf81efa96..07ab6dd8e1 100644 --- a/docs/pylib/copy_from_wiki.py +++ b/docs/pylib/copy_from_wiki.py @@ -16,7 +16,6 @@ We also need to build the toc-tree and should do so automatically for now. import glob import re import datetime -import textwrap _RE_MD_LINK = re.compile(r"\[(?P[\w -\[\]]+?)\]\((?P.+?)\)", re.I + re.S + re.U) _RE_REF_LINK = re.compile(r"\[[\w -\[\]]*?\]\(.+?\)", re.I + re.S + re.U) @@ -33,7 +32,7 @@ _INDEX_PREFIX = f""" # VERSION WARNING -> This is the experimental static v0.95 documentation of Evennia, _automatically_ generated from the +> This is the experimental static v0.9 documentation of Evennia, _automatically_ generated from the > [evennia wiki](https://github.com/evennia/evennia/wiki/) at {datetime.datetime.now()}. > There are known conversion issues which will _not_ be addressed in this version - refer to > the original wiki if you have trouble. @@ -214,7 +213,6 @@ def _sub_link(match): def create_toctree(files): - with open("../source/toc.md", "w") as fil: fil.write("# Toc\n") @@ -266,25 +264,16 @@ def convert_links(files, outdir): if text.split("\n")[0].strip().startswith("[]") else text.split("\n") ) - - # wrap text - formatted_lines = [] - for line in text: - if line.strip(): - formatted_lines.append(textwrap.fill(line, width=100)) - else: - formatted_lines.append(line) - text = "\n".join(formatted_lines) + text = "\n".join(text) if not is_index: text = f"# {title}\n\n{text}" - with open(outfile, "w") as fil: fil.write(text) if __name__ == "__main__": - - create_toctree(_INFILES) - convert_links(_INFILES, _OUTDIR) + print("This should not be run on develop files, it would overwrite changes.") + # create_toctree(_INFILES) + # convert_links(_INFILES, _OUTDIR) diff --git a/docs/pylib/fmtwidth.py b/docs/pylib/fmtwidth.py new file mode 100644 index 0000000000..4c30c5a185 --- /dev/null +++ b/docs/pylib/fmtwidth.py @@ -0,0 +1,45 @@ +#!/usr/bin python +# -*- coding: utf-8 -*- + +""" +Format given files to a max width. + +Usage: + python fmtwidth.py --width 79 ../source/**.md + +""" +import glob +import textwrap +import argparse + +_DEFAULT_WIDTH = 100 + +if __name__ == "__main__": + + parser = argparse.ArgumentParser() + + parser.add_argument("files") + parser.add_argument("-w", "--width", dest="width", type=int, default=_DEFAULT_WIDTH) + + args = parser.parse_args() + + filepaths = glob.glob(args.files, recursive=True) + width = args.width + + wrapper = textwrap.TextWrapper(width=width, break_long_words=False, expand_tabs=True,) + + count = 0 + for filepath in filepaths: + with open(filepath, "r") as fil: + lines = fil.readlines() + + outlines = [ + "\n".join(wrapper.wrap(line)) if len(line) > width else line.strip("\n") + for line in lines + ] + txt = "\n".join(outlines) + with open(filepath, "w") as fil: + fil.write(txt) + count += 1 + + print(f"Wrapped {count} files.") diff --git a/docs/pylib/update_default_cmd_index.py b/docs/pylib/update_default_cmd_index.py new file mode 100644 index 0000000000..be9316c623 --- /dev/null +++ b/docs/pylib/update_default_cmd_index.py @@ -0,0 +1,111 @@ +# +# This creates a Google wiki page for all default commands with __doc__ strings. +# +# Import this from a Django-aware shell, then call run_update. +# +# + +from os.path import dirname, abspath, join as pathjoin +from evennia.utils.utils import ( + mod_import, variable_from_module, callables_from_module +) + +__all__ = ("run_update") + + +PAGE = """ + +# Default Commands + +The full set of default Evennia commands currently contains {ncommands} commands in {nfiles} source +files. Our policy for adding default commands is outlined [here](Using-MUX-as-a-Standard). The +[Commands](Commands) documentation explains how Commands work as well as make new or customize +existing ones. Note that this page is auto-generated. Report problems to the [issue +tracker](github:issues). + +```{{note}} +Some game-states adds their own Commands which are not listed here. Examples include editing a text +with [EvEditor](EvEditor), flipping pages in [EvMore](EvMore) or using the +[Batch-Processor](Batch-Processors)'s interactive mode. +``` + +{alphabetical} + +""" + +def run_update(no_autodoc=False): + + if no_autodoc: + return + + cmdsets = ( + ("evennia.commands.default.cmdset_character", "CharacterCmdSet"), + ("evennia.commands.default.cmdset_account", "AccountCmdSet"), + ("evennia.commands.default.cmdset_unloggedin", "UnloggedinCmdSet"), + ("evennia.commands.default.cmdset_session", "SessionCmdSet"), + ) + cmd_modules = ( + "evennia.commands.default.account", + "evennia.commands.default.batchprocess", + "evennia.commands.default.building", + "evennia.commands.default.comms", + "evennia.commands.default.general", + "evennia.commands.default.help", + "evennia.commands.default.syscommandsyyp", + "evennia.commands.default.system", + "evennia.commands.default.unloggedin", + ) + + cmds_per_cmdset = {} + cmd_to_cmdset_map = {} + for modname, cmdsetname in cmdsets: + cmdset = variable_from_module(modname, variable=cmdsetname)() + cmdset.at_cmdset_creation() + cmds_per_cmdset[cmdsetname] = cmdset.commands + for cmd in cmdset.commands: + cmd_to_cmdset_map[f"{cmd.__module__}.{cmd.__class__.__name__}"] = cmdset + + cmds_per_module = {} + cmd_to_module_map = {} + cmds_alphabetically = [] + for modname in cmd_modules: + module = mod_import(modname) + cmds_per_module[module] = [ + cmd for cmd in callables_from_module(module).values() if cmd.__name__.startswith("Cmd")] + for cmd in cmds_per_module[module]: + cmd_to_module_map[cmd] = module + cmds_alphabetically.append(cmd) + cmds_alphabetically = list(sorted(cmds_alphabetically, key=lambda c: c.key)) + + cmd_infos = [] + for cmd in cmds_alphabetically: + aliases = f" [{', '.join(cmd.aliases)}]" if cmd.aliases else "" + cmdlink = f"[**{cmd.key}**{aliases}]({cmd.__module__}.{cmd.__name__})" + category = f"help-category: _{cmd.help_category.capitalize()}_" + cmdset = cmd_to_cmdset_map.get(f"{cmd.__module__}.{cmd.__name__}", None) + if cmdset: + cmodule = cmdset.__module__ + cname = cmdset.__class__.__name__ + cmdsetlink = f"cmdset: [{cname}]({cmodule}.{cname}), " + else: + # we skip commands not in the default cmdsets + continue + + cmd_infos.append(f"{cmdlink} ({cmdsetlink}{category})") + + txt = PAGE.format( + ncommands=len(cmd_to_cmdset_map), + nfiles=len(cmds_per_module), + alphabetical="\n".join(f"- {info}" for info in cmd_infos)) + + outdir = pathjoin(dirname(dirname(abspath(__file__))), "source") + fname = pathjoin(outdir, "Default-Commands.md") + + with open(fname, 'w') as fil: + fil.write(txt) + + print(" -- Updated Default Command index.") + + +if __name__ == "__main__": + run_update() diff --git a/docs/source/A-voice-operated-elevator-using-events.md b/docs/source/A-voice-operated-elevator-using-events.md index 4a38d40e20..24f6bdef33 100644 --- a/docs/source/A-voice-operated-elevator-using-events.md +++ b/docs/source/A-voice-operated-elevator-using-events.md @@ -1,7 +1,7 @@ # A voice operated elevator using events -- Previous tutorial: [Adding dialogues in events](./Dialogues-in-events) +- Previous tutorial: [Adding dialogues in events](./Dialogues-in-events.md) This tutorial will walk you through the steps to create a voice-operated elevator, using the [in- game Python @@ -97,7 +97,7 @@ things to decorate it a bit. But what we want now is to be able to say "1", "2" or "3" and have the elevator move in that direction. -If you have read [the previous tutorial about adding dialogues in events](./Dialogues-in-events), you +If you have read [the previous tutorial about adding dialogues in events](./Dialogues-in-events.md), you may remember what we need to do. If not, here's a summary: we need to run some code when somebody speaks in the room. So we need to create a callback (the callback will contain our lines of code). We just need to know on which event this should be set. You can enter `call here` to see the @@ -433,4 +433,4 @@ to consider adding the code in the source itself. Another possibility is to cal with the expected behavior, which makes porting code very easy. This side of chained events will be shown in the next tutorial. -- Previous tutorial: [Adding dialogues in events](./Dialogues-in-events) +- Previous tutorial: [Adding dialogues in events](./Dialogues-in-events.md) diff --git a/docs/source/Accounts.md b/docs/source/Accounts.md index 04b0c2d27a..6d61136583 100644 --- a/docs/source/Accounts.md +++ b/docs/source/Accounts.md @@ -1,27 +1,27 @@ # Accounts -All *users* (real people) that starts a game [Session](./Sessions) on Evennia are doing so through an +All *users* (real people) that starts a game [Session](./Sessions.md) on Evennia are doing so through an object called *Account*. The Account object has no in-game representation, it represents a unique -game account. In order to actually get on the game the Account must *puppet* an [Object](./Objects) -(normally a [Character](./Objects#Character)). +game account. In order to actually get on the game the Account must *puppet* an [Object](./Objects.md) +(normally a [Character](./Objects.md#characters)). Exactly how many Sessions can interact with an Account and its Puppets at once is determined by -Evennia's [MULTISESSION_MODE](./Sessions#Multisession-mode) setting. +Evennia's [MULTISESSION_MODE](./Sessions.md#multisession-mode) setting. Apart from storing login information and other account-specific data, the Account object is what is -chatting on [Channels](./Communications). It is also a good place to store [Permissions](./Locks) to be +chatting on [Channels](./Communications.md). It is also a good place to store [Permissions](./Locks.md) to be consistent between different in-game characters as well as configuration options. The Account -object also has its own [CmdSet](./Command-Sets), the `AccountCmdSet`. +object also has its own [CmdSet](./Command-Sets.md), the `AccountCmdSet`. Logged into default evennia, you can use the `ooc` command to leave your current -[character](./Objects) and go into OOC mode. You are quite limited in this mode, basically it works +[character](./Objects.md) and go into OOC mode. You are quite limited in this mode, basically it works like a simple chat program. It acts as a staging area for switching between Characters (if your game supports that) or as a safety mode if your Character gets deleted. Use `ic` to attempt to (re)puppet a Character. Note that the Account object can have, and often does have, a different set of -[Permissions](./Locks#Permissions) from the Character they control. Normally you should put your +[Permissions](./Locks.md#permissions) from the Character they control. Normally you should put your permissions on the Account level - this will overrule permissions set on the Character level. For the permissions of the Character to come into play the default `quell` command can be used. This allows for exploring the game using a different permission set (but you can't escalate your @@ -77,7 +77,7 @@ You should now see the Attributes on yourself. ## Properties on Accounts -Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses)), the +Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), the Account also has the following custom properties: - `user` - a unique link to a `User` Django object, representing the logged-in user. @@ -92,11 +92,11 @@ as - `is_superuser` (bool: True/False) - if this account is a superuser. Special handlers: -- `cmdset` - This holds all the current [Commands](./Commands) of this Account. By default these are +- `cmdset` - This holds all the current [Commands](./Commands.md) of this Account. By default these are the commands found in the cmdset defined by `settings.CMDSET_ACCOUNT`. -- `nicks` - This stores and handles [Nicks](./Nicks), in the same way as nicks it works on Objects. +- `nicks` - This stores and handles [Nicks](./Nicks.md), in the same way as nicks it works on Objects. For Accounts, nicks are primarily used to store custom aliases for -[Channels](./Communications#Channels). +[Channels](./Communications.md#channels). Selection of special methods (see `evennia.DefaultAccount` for details): - `get_puppet` - get a currently puppeted object connected to the Account and a given session id, if diff --git a/docs/source/Add-a-wiki-on-your-website.md b/docs/source/Add-a-wiki-on-your-website.md index ab81e08bef..a8f476d28e 100644 --- a/docs/source/Add-a-wiki-on-your-website.md +++ b/docs/source/Add-a-wiki-on-your-website.md @@ -2,7 +2,7 @@ **Before doing this tutorial you will probably want to read the intro in -[Basic Web tutorial](./Web-Tutorial).** Reading the three first parts of the +[Basic Web tutorial](./Web-Tutorial.md).** Reading the three first parts of the [Django tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/) might help as well. This tutorial will provide a step-by-step process to installing a wiki on your website. diff --git a/docs/source/Adding-Command-Tutorial.md b/docs/source/Adding-Command-Tutorial.md index 36e1600542..569c12252d 100644 --- a/docs/source/Adding-Command-Tutorial.md +++ b/docs/source/Adding-Command-Tutorial.md @@ -1,6 +1,6 @@ # Adding Command Tutorial -This is a quick first-time tutorial expanding on the [Commands](./Commands) documentation. +This is a quick first-time tutorial expanding on the [Commands](./Commands.md) documentation. Let's assume you have just downloaded Evennia, installed it and created your game folder (let's call it just `mygame` here). Now you want to try to add a new command. This is the fastest way to do it. @@ -16,7 +16,7 @@ example code. 1. Give your class a useful _docstring_. A docstring is the string at the very top of a class or function/method. The docstring at the top of the command class is read by Evennia to become the help entry for the Command (see - [Command Auto-help](./Help-System#command-auto-help-system)). + [Command Auto-help](./Help-System.md#command-auto-help-system)). 1. Define a class method `func(self)` that echoes your input back to you. Below is an example how this all could look for the echo command: @@ -47,7 +47,7 @@ Below is an example how this all could look for the echo command: ## Step 2: Adding the Command to a default Cmdset -The command is not available to use until it is part of a [Command Set](./Command-Sets). In this +The command is not available to use until it is part of a [Command Set](./Command-Sets.md). In this example we will go the easiest route and add it to the default Character commandset that already exists. @@ -87,13 +87,13 @@ your command definition). by its argument `test` (which will end up in `self.args). To change this behavior, you can add the `arg_regex` property alongside `key`, `help_category` etc. [See the arg_regex -documentation](Commands#on-arg_regex) for more info. +documentation](./Commands.md#on-arg_regex) for more info. If you want to overload existing default commands (such as `look` or `get`), just add your new command with the same key as the old one - it will then replace it. Just remember that you must use `@reload` to see any changes. -See [Commands](./Commands) for many more details and possibilities when defining Commands and using +See [Commands](./Commands.md) for many more details and possibilities when defining Commands and using Cmdsets in various ways. @@ -102,7 +102,7 @@ Cmdsets in various ways. Adding your Command to the `CharacterCmdSet` is just one easy exapmple. The cmdset system is very generic. You can create your own cmdsets (let's say in a module `mycmdsets.py`) and add them to objects as you please (how to control their merging is described in detail in the [Command Set -documentation](Command-Sets)). +documentation](./Command-Sets.md)). ```python # file mygame/commands/mycmdsets.py @@ -131,7 +131,7 @@ only make the new merged cmdset permanent on that *single* object. Often you wan this particular class to have this cmdset. To make sure all new created objects get your new merged set, put the `cmdset.add` call in your -custom [Typeclasses](./Typeclasses)' `at_object_creation` method: +custom [Typeclasses](./Typeclasses.md)' `at_object_creation` method: ```python # e.g. in mygame/typeclasses/objects.py diff --git a/docs/source/Adding-Object-Typeclass-Tutorial.md b/docs/source/Adding-Object-Typeclass-Tutorial.md index 4e532a5c75..4e4e0b66bb 100644 --- a/docs/source/Adding-Object-Typeclass-Tutorial.md +++ b/docs/source/Adding-Object-Typeclass-Tutorial.md @@ -13,9 +13,9 @@ When you create a new Evennia game (with for example `evennia --init mygame`) Ev automatically create empty child classes `Object`, `Character`, `Room` and `Exit` respectively. They are found `mygame/typeclasses/objects.py`, `mygame/typeclasses/rooms.py` etc. -> Technically these are all [Typeclassed](./Typeclasses), which can be ignored for now. In +> Technically these are all [Typeclassed](./Typeclasses.md), which can be ignored for now. In > `mygame/typeclasses` are also base typeclasses for out-of-character things, notably -> [Channels](./Communications), [Accounts](./Accounts) and [Scripts](./Scripts). We don't cover those in +> [Channels](./Communications.md), [Accounts](./Accounts.md) and [Scripts](./Scripts.md). We don't cover those in > this tutorial. For your own game you will most likely want to expand on these very simple beginnings. It's normal @@ -62,13 +62,13 @@ up. you will find the traceback. The most common error is that you have some sort of syntax error in your class. -Note that the [Locks](./Locks) and [Attribute](./Attributes) which are set in the typeclass could just +Note that the [Locks](./Locks.md) and [Attribute](./Attributes.md) which are set in the typeclass could just as well have been set using commands in-game, so this is a *very* simple example. ## Storing data on initialization The `at_object_creation` is only called once, when the object is first created. This makes it ideal -for database-bound things like [Attributes](./Attributes). But sometimes you want to create temporary +for database-bound things like [Attributes](./Attributes.md). But sometimes you want to create temporary properties (things that are not to be stored in the database but still always exist every time the object is created). Such properties can be initialized in the `at_init` method on the object. `at_init` is called every time the object is loaded into memory. @@ -86,7 +86,7 @@ def at_init(self): self.ndb.mylist = [] ``` -> Note: As mentioned in the [Typeclasses](./Typeclasses) documentation, `at_init` replaces the use of +> Note: As mentioned in the [Typeclasses](./Typeclasses.md) documentation, `at_init` replaces the use of > the standard `__init__` method of typeclasses due to how the latter may be called in situations > other than you'd expect. So use `at_init` where you would normally use `__init__`. diff --git a/docs/source/Administrative-Docs.md b/docs/source/Administrative-Docs.md index c37c64a7cd..154fbd83ec 100644 --- a/docs/source/Administrative-Docs.md +++ b/docs/source/Administrative-Docs.md @@ -5,48 +5,48 @@ are responsible for managing the game. ### Installation and Early Life -- [Choosing (and installing) an SQL Server](./Choosing-An-SQL-Server) -- [Getting Started - Installing Evennia](./Getting-Started) -- [Running Evennia in Docker Containers](./Running-Evennia-in-Docker) -- [Starting, stopping, reloading and resetting Evennia](./Start-Stop-Reload) -- [Keeping your game up to date](./Updating-Your-Game) - - [Resetting your database](./Updating-Your-Game#resetting-your-database) -- [Making your game available online](./Online-Setup) - - [Hosting options](./Online-Setup#hosting-options) - - [Securing your server with SSL/Let's Encrypt](./Online-Setup#ssl) -- [Listing your game](./Evennia-Game-Index) at the online [Evennia game +- [Choosing (and installing) an SQL Server](./Choosing-An-SQL-Server.md) +- [Getting Started - Installing Evennia](./Getting-Started.md) +- [Running Evennia in Docker Containers](./Running-Evennia-in-Docker.md) +- [Starting, stopping, reloading and resetting Evennia](./Start-Stop-Reload.md) +- [Keeping your game up to date](./Updating-Your-Game.md) + - [Resetting your database](./Updating-Your-Game.md#resetting-your-database) +- [Making your game available online](./Online-Setup.md) + - [Hosting options](./Online-Setup.md#hosting-options) + - [Securing your server with SSL/Let's Encrypt](./Online-Setup.md#ssl) +- [Listing your game](./Evennia-Game-Index.md) at the online [Evennia game index](http://games.evennia.com) ### Customizing the server -- [Changing the Settings](./Server-Conf#Settings-file) +- [Changing the Settings](./Server-Conf.md#settings-file) - [Available Master Settings](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py) -- [Change Evennia's language](./Internationalization) (internationalization) -- [Apache webserver configuration](./Apache-Config) (optional) -- [Changing text encodings used by the server](./Text-Encodings) -- [The Connection Screen](./Connection-Screen) -- [Guest Logins](./Guest-Logins) -- [How to connect Evennia to IRC channels](./IRC) -- [How to connect Evennia to RSS feeds](./RSS) -- [How to connect Evennia to Grapevine](./Grapevine) -- [How to connect Evennia to Twitter](./How-to-connect-Evennia-to-Twitter) +- [Change Evennia's language](./Internationalization.md) (internationalization) +- [Apache webserver configuration](./Apache-Config.md) (optional) +- [Changing text encodings used by the server](./Text-Encodings.md) +- [The Connection Screen](./Connection-Screen.md) +- [Guest Logins](./Guest-Logins.md) +- [How to connect Evennia to IRC channels](./IRC.md) +- [How to connect Evennia to RSS feeds](./RSS.md) +- [How to connect Evennia to Grapevine](./Grapevine.md) +- [How to connect Evennia to Twitter](./How-to-connect-Evennia-to-Twitter.md) ### Administrating the running game -- [Supported clients](./Client-Support-Grid) (grid of known client issues) -- [Changing Permissions](./Building-Permissions) of users -- [Banning](./Banning) and deleting users - - [Summary of abuse-handling tools](./Banning#summary-of-abuse-handling-tools) in the default cmdset +- [Supported clients](./Client-Support-Grid.md) (grid of known client issues) +- [Changing Permissions](./Building-Permissions.md) of users +- [Banning](./Banning.md) and deleting users + - [Summary of abuse-handling tools](./Banning.md#summary-of-abuse-handling-tools) in the default cmdset ### Working with Evennia -- [Setting up your work environment with version control](./Version-Control) -- [First steps coding with Evennia](./First-Steps-Coding) -- [Setting up a continuous integration build environment](./Continuous-Integration) +- [Setting up your work environment with version control](./Version-Control.md) +- [First steps coding with Evennia](./First-Steps-Coding.md) +- [Setting up a continuous integration build environment](./Continuous-Integration.md) -```toctree:: +```{toctree} :hidden: Choosing-An-SQL-Server diff --git a/docs/source/Arxcode-installing-help.md b/docs/source/Arxcode-installing-help.md index 3876b841f9..e7efdfae28 100644 --- a/docs/source/Arxcode-installing-help.md +++ b/docs/source/Arxcode-installing-help.md @@ -11,7 +11,7 @@ to pick ideas or even get a starting game to build on. These instructions are ba released as of *Aug 12, 2018*. If you are not familiar with what Evennia is, you can read -[an introduction here](./Evennia-Introduction). +[an introduction here](./Evennia-Introduction.md). It's not too hard to run Arx from the sources (of course you'll start with an empty database) but since part of Arx has grown organically, it doesn't follow standard Evennia paradigms everywhere. @@ -24,7 +24,7 @@ Firstly, set aside a folder/directory on your drive for everything to follow. You need to start by installing [Evennia](http://www.evennia.com) by following most of the [Getting Started -Instructions](Getting-Started) for your OS. The difference is that you need to `git clone +Instructions](./Getting-Started.md) for your OS. The difference is that you need to `git clone https://github.com/TehomCD/evennia.git` instead of Evennia's repo because Arx uses TehomCD's older Evennia 0.8 [fork](https://github.com/TehomCD/evennia), notably still using Python2. This detail is important if referring to newer Evennia documentation. @@ -32,7 +32,7 @@ important if referring to newer Evennia documentation. If you are new to Evennia it's *highly* recommended that you run through the instructions in full - including initializing and starting a new empty game and connecting to it. That way you can be sure Evennia works correctly as a base line. If you have trouble, make sure to -read the [Troubleshooting instructions](./Getting-Started#troubleshooting) for your +read the [Troubleshooting instructions](./Getting-Started.md#troubleshooting) for your operating system. You can also drop into our [forums](https://groups.google.com/forum/#%21forum/evennia), join `#evennia` on `irc.freenode.net` or chat from the linked [Discord Server](https://discord.gg/NecFePw). @@ -64,7 +64,7 @@ A new folder `myarx` should appear next to the ones you already had. You could r something else if you want. Cd into `myarx`. If you wonder about the structure of the game dir, you can [read more about it -here](Directory-Overview). +here](./Directory-Overview.md). ### Clean up settings diff --git a/docs/source/Async-Process.md b/docs/source/Async-Process.md index ae18635738..e44e9330a2 100644 --- a/docs/source/Async-Process.md +++ b/docs/source/Async-Process.md @@ -90,7 +90,7 @@ line quite pointless for processing any data from the function. Instead one has - `at_err_kwargs` - an optional dictionary that will be fed as keyword arguments to the `at_err` errback. -An example of making an asynchronous call from inside a [Command](./Commands) definition: +An example of making an asynchronous call from inside a [Command](./Commands.md) definition: ```python from evennia import utils, Command @@ -139,7 +139,7 @@ sleep. ``` This will delay the execution of the callback for 10 seconds. This function is explored much more in -the [Command Duration Tutorial](./Command-Duration). +the [Command Duration Tutorial](./Command-Duration.md). You can also try the following snippet just see how it works: diff --git a/docs/source/Attributes.md b/docs/source/Attributes.md index 4ca0e82566..b04d18f654 100644 --- a/docs/source/Attributes.md +++ b/docs/source/Attributes.md @@ -7,8 +7,8 @@ can give correct subsequent commands. If you are writing a combat system, you mi combattant's next roll get easier dependent on if their opponent failed. Your characters will probably need to store roleplaying-attributes like strength and agility. And so on. -[Typeclassed](./Typeclasses) game entities ([Accounts](./Accounts), [Objects](./Objects), -[Scripts](./Scripts) and [Channels](./Communications)) always have *Attributes* associated with them. +[Typeclassed](./Typeclasses.md) game entities ([Accounts](./Accounts.md), [Objects](./Objects.md), +[Scripts](./Scripts.md) and [Channels](./Communications.md)) always have *Attributes* associated with them. Attributes are used to store any type of data 'on' such entities. This is different from storing data in properties already defined on entities (such as `key` or `location`) - these have very specific names and require very specific types of data (for example you couldn't assign a python @@ -16,12 +16,12 @@ specific names and require very specific types of data (for example you couldn't want to assign arbitrary data to arbitrary names. **Attributes are _not_ secure by default and any player may be able to change them unless you -[prevent this behavior](./Attributes#locking-and-checking-attributes).** +[prevent this behavior](./Attributes.md#locking-and-checking-attributes).** ## The .db and .ndb shortcuts To save persistent data on a Typeclassed object you normally use the `db` (DataBase) operator. Let's -try to save some data to a *Rose* (an [Object](./Objects)): +try to save some data to a *Rose* (an [Object](./Objects.md)): ```python # saving @@ -87,13 +87,13 @@ The handlers have normal access methods that allow you to manage and retrieve `A returned, but the method takes keywords for returning the Attribute object itself. By supplying an `accessing_object` to the call one can also make sure to check permissions before modifying anything. -- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks) can be +- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be supplied here to restrict future access and also the call itself may be checked against locks. - `remove(...)` - Remove the given Attribute. This can optionally be made to check for permission before performing the deletion. - `clear(...)` - removes all Attributes from object. - `all(...)` - returns all Attributes (of the given category) attached to this object. -See [this section](./Attributes#locking-and-checking-attributes) for more about locking down Attribute +See [this section](./Attributes.md#locking-and-checking-attributes) for more about locking down Attribute access and editing. The `Nattribute` offers no concept of access control. Some examples: @@ -118,23 +118,23 @@ An Attribute object is stored in the database. It has the following properties: to `attrname`. - `value` - this is the value of the Attribute. This value can be anything which can be pickled - objects, lists, numbers or what have you (see - [this section](./Attributes#What_types_of_data_can_I_save_in_an_Attribute) for more info). In the + [this section](./Attributes.md#what-types-of-data-can-i-save-in-an-attribute) for more info). In the example `obj.db.attrname = value`, the `value` is stored here. - `category` - this is an optional property that is set to None for most Attributes. Setting this allows to use Attributes for different functionality. This is usually not needed unless you want - to use Attributes for very different functionality ([Nicks](./Nicks) is an example of using + to use Attributes for very different functionality ([Nicks](./Nicks.md) is an example of using Attributes in this way). To modify this property you need to use the [Attribute -Handler](Attributes#The_Attribute_Handler). +Handler](#the-attributehandler). - `strvalue` - this is a separate value field that only accepts strings. This severely limits the data possible to store, but allows for easier database lookups. This property is usually not used - except when re-using Attributes for some other purpose ([Nicks](./Nicks) use it). It is only - accessible via the [Attribute Handler](./Attributes#The_Attribute_Handler). + except when re-using Attributes for some other purpose ([Nicks](./Nicks.md) use it). It is only + accessible via the [Attribute Handler](./Attributes.md#the-attributehandler). There are also two special properties: -- `attrtype` - this is used internally by Evennia to separate [Nicks](./Nicks), from Attributes (Nicks +- `attrtype` - this is used internally by Evennia to separate [Nicks](./Nicks.md), from Attributes (Nicks use Attributes behind the scenes). - `model` - this is a *natural-key* describing the model this Attribute is attached to. This is on the form *appname.modelclass*, like `objects.objectdb`. It is used by the Attribute and @@ -162,7 +162,7 @@ default during heavy loads. - A more valid reason for using non-persistent data is if you *want* to lose your state when logging off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you - are implementing some caching of your own. Or maybe you are testing a buggy [Script](./Scripts) that + are implementing some caching of your own. Or maybe you are testing a buggy [Script](./Scripts.md) that does potentially harmful stuff to your character object. With non-persistent storage you can be sure that whatever is messed up, it's nothing a server reboot can't clear up. @@ -192,7 +192,7 @@ not a big deal. But if you are accessing the Attribute as part of some big loop amount of reads/writes you should first extract it to a temporary variable, operate on *that* and then save the result back to the Attribute. If you are storing a more complex structure like a `dict` or a `list` you should make sure to "disconnect" it from the database before looping over it, -as mentioned in the [Retrieving Mutable Objects](./Attributes#retrieving-mutable-objects) section +as mentioned in the [Retrieving Mutable Objects](./Attributes.md#retrieving-mutable-objects) section below. ### Storing single objects @@ -246,7 +246,7 @@ containing dicts, etc. Since you can use any combination of the above iterables, this is generally not much of a limitation. -Any entity listed in the [Single object](./Attributes#Storing-Single-Objects) section above can be +Any entity listed in the [Single object](./Attributes.md#storing-single-objects) section above can be stored in the iterable. > As mentioned in the previous section, database entities (aka typeclasses) are not possible to @@ -353,7 +353,7 @@ already disconnected from the database from the onset. Attributes are normally not locked down by default, but you can easily change that for individual Attributes (like those that may be game-sensitive in games with user-level building). -First you need to set a *lock string* on your Attribute. Lock strings are specified [Locks](./Locks). +First you need to set a *lock string* on your Attribute. Lock strings are specified [Locks](./Locks.md). The relevant lock types are - `attrread` - limits who may read the value of the Attribute diff --git a/docs/source/Banning.md b/docs/source/Banning.md index afb17874d8..09baae2b95 100644 --- a/docs/source/Banning.md +++ b/docs/source/Banning.md @@ -114,14 +114,14 @@ is not what you want in this case. - **cboot mychannel = thomas** -- Boot a subscriber from a channel you control - **clock mychannel = control:perm(Admin);listen:all();send:all()** -- Fine control of access to -your channel using [lock definitions](./Locks). +your channel using [lock definitions](./Locks.md). Locking a specific command (like `page`) is accomplished like so: 1. Examine the source of the command. [The default `page` command class]( https://github.com/evennia/evennia/blob/master/evennia/commands/default/comms.py#L686) has the lock string **"cmd:not pperm(page_banned)"**. This means that unless the player has the 'permission' "page_banned" they can use this command. You can assign any lock string to allow finer customization -in your commands. You might look for the value of an [Attribute](./Attributes) or [Tag](./Tags), your +in your commands. You might look for the value of an [Attribute](./Attributes.md) or [Tag](./Tags.md), your current location etc. 2. **perm/account thomas = page_banned** -- Give the account the 'permission' which causes (in this case) the lock to fail. diff --git a/docs/source/Batch-Code-Processor.md b/docs/source/Batch-Code-Processor.md index eeb82b8879..df8eaff1ff 100644 --- a/docs/source/Batch-Code-Processor.md +++ b/docs/source/Batch-Code-Processor.md @@ -1,7 +1,7 @@ # Batch Code Processor -For an introduction and motivation to using batch processors, see [here](./Batch-Processors). This +For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command- Processor). @@ -192,7 +192,7 @@ connect that room with a room you built in the current block. There are two ways - Perform a database search for the name of the room you created (since you cannot know in advance which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of "A -dark forest" rooms). There is an easy way to handle this though - use [Tags](./Tags) or *Aliases*. You +dark forest" rooms). There is an easy way to handle this though - use [Tags](./Tags.md) or *Aliases*. You can assign any number of tags and/or aliases to any object. Make sure that one of those tags or aliases is unique to the room (like "room56") and you will henceforth be able to always uniquely search and find it later. diff --git a/docs/source/Batch-Command-Processor.md b/docs/source/Batch-Command-Processor.md index 9999e7e8f5..9278b4be7f 100644 --- a/docs/source/Batch-Command-Processor.md +++ b/docs/source/Batch-Command-Processor.md @@ -1,7 +1,7 @@ # Batch Command Processor -For an introduction and motivation to using batch processors, see [here](./Batch-Processors). This +For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This page describes the Batch-*command* processor. The Batch-*code* one is covered [here](Batch-Code- Processor). @@ -152,7 +152,7 @@ when creating the file, so that you can 'walk' (or teleport) to the right places This also means there are several pitfalls when designing and adding certain types of objects. Here are some examples: -- *Rooms that change your [Command Set](./Command-Sets)*: Imagine that you build a 'dark' room, which +- *Rooms that change your [Command Set](./Command-Sets.md)*: Imagine that you build a 'dark' room, which severely limits the cmdsets of those entering it (maybe you have to find the light switch to proceed). In your batch script you would create this room, then teleport to it - and promptly be shifted into the dark state where none of your normal build commands work ... @@ -172,7 +172,7 @@ The fact that you build as 'yourself' can also be considered an advantage howeve decide to change the default command to allow others than superusers to call the processor. Since normal access-checks are still performed, a malevolent builder with access to the processor should not be able to do all that much damage (this is the main drawback of the [Batch Code -Processor](Batch-Code-Processor)) +Processor](./Batch-Code-Processor.md)) - [GNU Emacs](https://www.gnu.org/software/emacs/) users might find it interesting to use emacs' *evennia mode*. This is an Emacs major mode found in `evennia/utils/evennia-mode.el`. It offers diff --git a/docs/source/Batch-Processors.md b/docs/source/Batch-Processors.md index ff634d7ce7..85a4c8a30f 100644 --- a/docs/source/Batch-Processors.md +++ b/docs/source/Batch-Processors.md @@ -35,8 +35,8 @@ just list in-game commands in a text file. The code-processor on the other hand powerful but also more complex - it lets you use Evennia's API to code your world in full-fledged Python code. -- The [Batch Command Processor](./Batch-Command-Processor) -- The [Batch Code Processor](./Batch-Code-Processor) +- The [Batch Command Processor](./Batch-Command-Processor.md) +- The [Batch Code Processor](./Batch-Code-Processor.md) If you plan to use international characters in your batchfiles you are wise to read about *file encodings* below. @@ -73,7 +73,7 @@ need to add the editor's encoding to Evennia's `ENCODINGS` list. If you are unsu file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works as it should. -More help with encodings can be found in the entry [Text Encodings](./Text-Encodings) and also in the +More help with encodings can be found in the entry [Text Encodings](./Text-Encodings.md) and also in the Wikipedia article [here](http://en.wikipedia.org/wiki/Text_encodings). **A footnote for the batch-code processor**: Just because *Evennia* can parse your file and your diff --git a/docs/source/Bootstrap-Components-and-Utilities.md b/docs/source/Bootstrap-Components-and-Utilities.md index f8590fcc7e..5e46964766 100644 --- a/docs/source/Bootstrap-Components-and-Utilities.md +++ b/docs/source/Bootstrap-Components-and-Utilities.md @@ -2,8 +2,8 @@ Bootstrap provides many utilities and components you can use when customizing Evennia's web presence. We'll go over a few examples here that you might find useful. -> Please take a look at either [the basic web tutorial](./Add-a-simple-new-web-page) or [the web -character view tutorial](Web-Character-View-Tutorial) +> Please take a look at either [the basic web tutorial](./Add-a-simple-new-web-page.md) or [the web +character view tutorial](./Web-Character-View-Tutorial.md) > to get a feel for how to add pages to Evennia's website to test these examples. ## General Styling @@ -79,4 +79,4 @@ width of the page - Evennia's base site uses the former. ### Forms [Forms](https://getbootstrap.com/docs/4.0/components/forms/) are highly customizable with Bootstrap. For a more in-depth look at how to use forms and their styles in your own Evennia site, please read -over [the web character gen tutorial.](./Web-Character-Generation) \ No newline at end of file +over [the web character gen tutorial.](./Web-Character-Generation.md) \ No newline at end of file diff --git a/docs/source/Builder-Docs.md b/docs/source/Builder-Docs.md index 48331464ab..9bd7a11a05 100644 --- a/docs/source/Builder-Docs.md +++ b/docs/source/Builder-Docs.md @@ -4,29 +4,29 @@ This section contains information useful to world builders. ### Building basics -- [Default in-game commands](api:evennia.commands.default) -- [Building Quick-start](./Building-Quickstart) -- [Giving build permissions to others](./Building-Permissions) -- [Adding text tags](./TextTags) - - [Colored text](./TextTags#coloured-text) - - [Clickable links](./TextTags#clickable-links) - - [Inline functions](./TextTags#inline-functions) -- [Customizing the connection screen](./Connection-Screen) +- [Default in-game commands](evennia.commands.default) +- [Building Quick-start](./Building-Quickstart.md) +- [Giving build permissions to others](./Building-Permissions.md) +- [Adding text tags](./TextTags.md) + - [Colored text](./TextTags.md#coloured-text) + - [Clickable links](./TextTags.md#clickable-links) + - [Inline functions](./TextTags.md#inline-functions) +- [Customizing the connection screen](./Connection-Screen.md) ### Advanced building and World building -- [Overview of batch processors](./Batch-Processors) - - [Batch-command processor](./Batch-Command-Processor) - - [Batch-code processor](./Batch-Code-Processor) -- [Using the Spawner for individualizing objects](./Spawner-and-Prototypes) -- [Adding Zones](./Zones) +- [Overview of batch processors](./Batch-Processors.md) + - [Batch-command processor](./Batch-Command-Processor.md) + - [Batch-code processor](./Batch-Code-Processor.md) +- [Using the Spawner for individualizing objects](./Spawner-and-Prototypes.md) +- [Adding Zones](./Zones.md) ### The Tutorial world -- [Introduction and setup](./Tutorial-World-Introduction) +- [Introduction and setup](./Tutorial-World-Introduction.md) -```toctree:: +```{toctree} :hidden: Building-Quickstart diff --git a/docs/source/Building-Permissions.md b/docs/source/Building-Permissions.md index 38e7e80c8d..2f41d9c95b 100644 --- a/docs/source/Building-Permissions.md +++ b/docs/source/Building-Permissions.md @@ -2,7 +2,7 @@ *OBS: This gives only a brief introduction to the access system. Locks and permissions are fully -detailed* [here](./Locks). +detailed* [here](./Locks.md). ## The super user @@ -17,7 +17,7 @@ but one superuser. Whereas permissions can be used for anything, those put in `settings.PERMISSION_HIERARCHY` will have a ranking relative each other as well. We refer to these types of permissions as *hierarchical -permissions*. When building locks to check these permissions, the `perm()` [lock function](./Locks) is +permissions*. When building locks to check these permissions, the `perm()` [lock function](./Locks.md) is used. By default Evennia creates the following hierarchy (spelled exactly like this): 1. **Developers** basically have the same access as superusers except that they do *not* sidestep diff --git a/docs/source/Building-Quickstart.md b/docs/source/Building-Quickstart.md index be4ad9d290..64fa277a0f 100644 --- a/docs/source/Building-Quickstart.md +++ b/docs/source/Building-Quickstart.md @@ -1,8 +1,8 @@ # Building Quickstart -The [default command](./Default-Command-Help) definitions coming with Evennia -follows a style [similar](./Using-MUX-as-a-Standard) to that of MUX, so the +The [default command](./Default-Commands.md) definitions coming with Evennia +follows a style [similar](./Using-MUX-as-a-Standard.md) to that of MUX, so the commands should be familiar if you used any such code bases before. > Throughout the larger documentation you may come across commands prefixed @@ -85,14 +85,14 @@ dropped in the room, then try this: lock box = get:false() -Locks represent a rather [big topic](./Locks), but for now that will do what we want. This will lock +Locks represent a rather [big topic](./Locks.md), but for now that will do what we want. This will lock the box so noone can lift it. The exception is superusers, they override all locks and will pick it up anyway. Make sure you are quelling your superuser powers and try to get the box now: > get box You can't get that. -Think thís default error message looks dull? The `get` command looks for an [Attribute](./Attributes) +Think thís default error message looks dull? The `get` command looks for an [Attribute](./Attributes.md) named `get_err_msg` for returning a nicer error message (we just happen to know this, you would need to peek into the [code](https://github.com/evennia/evennia/blob/master/evennia/commands/default/general.py#L235) for @@ -110,11 +110,11 @@ the raw description of your current room (including color codes), so that you ca set its description to something else. You create new Commands (or modify existing ones) in Python outside the game. See the [Adding -Commands tutorial](Adding-Command-Tutorial) for help with creating your first own Command. +Commands tutorial](./Adding-Command-Tutorial.md) for help with creating your first own Command. ## Get a Personality -[Scripts](./Scripts) are powerful out-of-character objects useful for many "under the hood" things. +[Scripts](./Scripts.md) are powerful out-of-character objects useful for many "under the hood" things. One of their optional abilities is to do things on a timer. To try out a first script, let's put one on ourselves. There is an example script in `evennia/contrib/tutorial_examples/bodyfunctions.py` that is called `BodyFunctions`. To add this to us we will use the `script` command: @@ -137,14 +137,14 @@ When you are tired of your character's "insights", kill the script with script/stop self = tutorial_examples.bodyfunctions.BodyFunctions You create your own scripts in Python, outside the game; the path you give to `script` is literally -the Python path to your script file. The [Scripts](./Scripts) page explains more details. +the Python path to your script file. The [Scripts](./Scripts.md) page explains more details. ## Pushing Your Buttons If we get back to the box we made, there is only so much fun you can do with it at this point. It's just a dumb generic object. If you renamed it to `stone` and changed its description noone would be -the wiser. However, with the combined use of custom [Typeclasses](./Typeclasses), [Scripts](./Scripts) -and object-based [Commands](./Commands), you could expand it and other items to be as unique, complex +the wiser. However, with the combined use of custom [Typeclasses](./Typeclasses.md), [Scripts](./Scripts.md) +and object-based [Commands](./Commands.md), you could expand it and other items to be as unique, complex and interactive as you want. Let's take an example. So far we have only created objects that use the default object typeclass @@ -161,7 +161,7 @@ sure to look in`evennia/contrib/` so you don't have to write the full path every - one red button. The RedButton is an example object intended to show off a few of Evennia's features. You will find -that the [Typeclass](./Typeclasses) and [Commands](./Commands) controlling it are inside +that the [Typeclass](./Typeclasses.md) and [Commands](./Commands.md) controlling it are inside `evennia/contrib/tutorial_examples/`. If you wait for a while (make sure you dropped it!) the button will blink invitingly. Why don't you diff --git a/docs/source/Building-a-mech-tutorial.md b/docs/source/Building-a-mech-tutorial.md index 48601ebcdf..9d14bcbfc3 100644 --- a/docs/source/Building-a-mech-tutorial.md +++ b/docs/source/Building-a-mech-tutorial.md @@ -28,9 +28,9 @@ Before we continue, let’s make a brief detour. Evennia is very flexible about more flexible about using and adding commands to those objects. Here are some ground rules well worth remembering for the remainder of this article: -- The [Account](./Accounts) represents the real person logging in and has no game-world existence. -- Any [Object](./Objects) can be puppeted by an Account (with proper permissions). -- [Characters](./Objects#characters), [Rooms](./Objects#rooms), and [Exits](./Objects#exits) are just +- The [Account](./Accounts.md) represents the real person logging in and has no game-world existence. +- Any [Object](./Objects.md) can be puppeted by an Account (with proper permissions). +- [Characters](./Objects.md#characters), [Rooms](./Objects.md#rooms), and [Exits](./Objects.md#exits) are just children of normal Objects. - Any Object can be inside another (except if it creates a loop). - Any Object can store custom sets of commands on it. Those commands can: @@ -120,7 +120,7 @@ about the missiles being fired and has different `key` and `aliases`. We leave that up to you to create as an exercise. You could have it print "WOOSH! The mech launches missiles against !", for example. -Now we shove our commands into a command set. A [Command Set](./Command-Sets) (CmdSet) is a container +Now we shove our commands into a command set. A [Command Set](./Command-Sets.md) (CmdSet) is a container holding any number of commands. The command set is what we will store on the mech. ```python @@ -173,7 +173,7 @@ This is great for testing. The way we added it, the MechCmdSet will even go away server. Now we want to make the mech an actual object “type” so we can create mechs without those extra steps. For this we need to create a new Typeclass. -A [Typeclass](./Typeclasses) is a near-normal Python class that stores its existence to the database +A [Typeclass](./Typeclasses.md) is a near-normal Python class that stores its existence to the database behind the scenes. A Typeclass is created in a normal Python source file: ```python diff --git a/docs/source/Client-Support-Grid.md b/docs/source/Client-Support-Grid.md index 1a9fc0ef61..1acd9e257c 100644 --- a/docs/source/Client-Support-Grid.md +++ b/docs/source/Client-Support-Grid.md @@ -1,96 +1,73 @@ # Client Support Grid This grid tries to gather info about different MU clients when used with Evennia. -If you want to report a problem, update an entry or add a client, make a +If you want to report a problem, update an entry or add a client, make a new [documentation issue](github:issue) for it. Everyone's encouraged to report their findings. -##### Legend: +## Client Grid + +Legend: - **Name**: The name of the client. Also note if it's OS-specific. - **Version**: Which version or range of client versions were tested. - **Comments**: Any quirks on using this client with Evennia should be added here. -## Client Grid -```eval_rst +| Name | Version tested | Comments | +| --- | --- | --- | +| [Evennia Webclient][1] | 1.0+ | Evennia-specific | +| [tintin++][2] | 2.0+ | No MXP support | +| [tinyfugue][3] | 5.0+ | No UTF-8 support | +| [MUSHclient][4] (Win) | 4.94 | NAWS reports full text area | +| [Zmud][5] (Win) | 7.21 | *UNTESTED* | +| [Cmud][6] (Win) | v3 | *UNTESTED* | +| [Potato][7]_ | 2.0.0b16 | No MXP, MCCP support. Win 32bit does not understand | +| | | "localhost", must use `127.0.0.1`. | +| [Mudlet][8] | 3.4+ | No known issues. Some older versions showed <> as html | +| | | under MXP. | +| [SimpleMU][9] (Win) | full | Discontinued. NAWS reports pixel size. | +| [Atlantis][10] (Mac) | 0.9.9.4 | No known issues. | +| [GMUD][11] | 0.0.1 | Can't handle any telnet handshakes. Not recommended. | +| [BeipMU][12] (Win) | 3.0.255 | No MXP support. Best to enable "MUD prompt handling", disable | +| | | "Handle HTML tags". | +| [MudRammer][13] (IOS) | 1.8.7 | Bad Telnet Protocol compliance: displays spurious characters. | +| [MUDMaster][14] | 1.3.1 | *UNTESTED* | +| [BlowTorch][15] (Andr) | 1.1.3 | Telnet NOP displays as spurious character. | +| [Mukluk][16] (Andr) | 2015.11.20| Telnet NOP displays as spurious character. Has UTF-8/Emoji | +| | | support. | +| [Gnome-MUD][17] (Unix) | 0.11.2 | Telnet handshake errors. First (only) attempt at logging in | +| | | fails. | +| [Spyrit][18] | 0.4 | No MXP, OOB support. | +| [JamochaMUD][19] | 5.2 | Does not support ANSI within MXP text. | +| [DuckClient][20] (Chrome) | 4.2 | No MXP support. Displays Telnet Go-Ahead and | +| | | WILL SUPPRESS-GO-AHEAD as ù character. Also seems to run | +| | | the `version` command on connection, which will not work in | +| | | `MULTISESSION_MODES` above 1. | +| [KildClient][21] | 2.11.1 | No known issues. | -+----------------------------+-----------+----------------------------------------------------------------+ -| Name | Version | Comments | -+============================+===========+================================================================+ -| `Evennia Webclient`_ | 0.9 | Evennia-specific | -+----------------------------+-----------+----------------------------------------------------------------+ -| `tintin++`_ | 2.0+ | No MXP support | -+----------------------------+-----------+----------------------------------------------------------------+ -| tinyfugue_ | 5.0+ | No UTF-8 support | -+----------------------------+-----------+----------------------------------------------------------------+ -| MUSHclient_ (Win) | 4.94 | NAWS reports full text area | -+----------------------------+-----------+----------------------------------------------------------------+ -| Zmud_ (Win) | 7.21 | *UNTESTED* | -+----------------------------+-----------+----------------------------------------------------------------+ -| Cmud_ (Win) | v3 | *UNTESTED* | -+----------------------------+-----------+----------------------------------------------------------------+ -| Potato_ | 2.0.0b16 | No MXP, MCCP support. Win 32bit does not understand | -| | | "localhost", must use `127.0.0.1`. | -+----------------------------+-----------+----------------------------------------------------------------+ -| Mudlet_ | 3.4+ | No known issues. Some older versions showed <> as html | -| | | under MXP. | -+----------------------------+-----------+----------------------------------------------------------------+ -| SimpleMU_ (Win) | full | Discontinued. NAWS reports pixel size. | -+----------------------------+-----------+----------------------------------------------------------------+ -| Atlantis_ (Mac) | 0.9.9.4 | No known issues. | -+----------------------------+-----------+----------------------------------------------------------------+ -| GMUD_ | 0.0.1 | Can't handle any telnet handshakes. Not recommended. | -+----------------------------+-----------+----------------------------------------------------------------+ -| BeipMU_ (Win) | 3.0.255 | No MXP support. Best to enable "MUD prompt handling", disable | -| | | "Handle HTML tags". | -+----------------------------+-----------+----------------------------------------------------------------+ -| MudRammer_ (IOS) | 1.8.7 | Bad Telnet Protocol compliance: displays spurious characters. | -+----------------------------+-----------+----------------------------------------------------------------+ -| MUDMaster_ | 1.3.1 | *UNTESTED* | -+----------------------------+-----------+----------------------------------------------------------------+ -| BlowTorch_ (Andr) | 1.1.3 | Telnet NOP displays as spurious character. | -+----------------------------+-----------+----------------------------------------------------------------+ -| Mukluk_ (Andr) | 2015.11.20| Telnet NOP displays as spurious character. Has UTF-8/Emoji | -| | | support. | -+----------------------------+-----------+----------------------------------------------------------------+ -| Gnome-MUD_ (Unix) | 0.11.2 | Telnet handshake errors. First (only) attempt at logging in | -| | | fails. | -+----------------------------+-----------+----------------------------------------------------------------+ -| Spyrit_ | 0.4 | No MXP, OOB support. | -+----------------------------+-----------+----------------------------------------------------------------+ -| JamochaMUD_ | 5.2 | Does not support ANSI within MXP text. | -+----------------------------+-----------+----------------------------------------------------------------+ -| DuckClient_ (Chrome) | 4.2 | No MXP support. Displays Telnet Go-Ahead and | -| | | WILL SUPPRESS-GO-AHEAD as ù character. Also seems to run | -| | | the `version` command on connection, which will not work in | -| | | `MULTISESSION_MODES` above 1. | -+----------------------------+-----------+----------------------------------------------------------------+ -| KildClient_ | 2.11.1 | No known issues. | -+----------------------------+-----------+----------------------------------------------------------------+ -.. _Evennia Webclient: ../Components/Webclient.html -.. _tintin++: http://tintin.sourceforge.net/ -.. _tinyfugue: http://tinyfugue.sourceforge.net/ -.. _MUSHclient: http://mushclient.com/ -.. _Zmud: http://forums.zuggsoft.com/index.php?page=4&action=file&file_id=65 -.. _Cmud: http://forums.zuggsoft.com/index.php?page=4&action=category&cat_id=11 -.. _Potato: http://www.potatomushclient.com/ -.. _Mudlet: http://www.mudlet.org/ -.. _SimpleMU: https://archive.org/details/tucows_196173_SimpleMU_MU_Client -.. _Atlantis: http://www.riverdark.net/atlantis/ -.. _GMUD: https://sourceforge.net/projects/g-mud/ -.. _BeipMU: http://www.beipmu.com/ -.. _MudRammer: https://itunes.apple.com/us/app/mudrammer-a-modern-mud-client/id597157072 -.. _MUDMaster: https://itunes.apple.com/us/app/mudmaster/id341160033 -.. _BlowTorch: http://bt.happygoatstudios.com/ -.. _Mukluk: https://play.google.com/store/apps/details?id=com.crap.mukluk -.. _Gnome-MUD: https://github.com/GNOME/gnome-mud -.. _Spyrit: https://spyrit.ierne.eu.org/ -.. _JamochaMUD: http://jamochamud.org/ -.. _DuckClient: http://duckclient.com/ -.. _KildClient: https://www.kildclient.org/ +[1]: ./Webclient +[2]: http://tintin.sourceforge.net/ +[3]: http://tinyfugue.sourceforge.net/ +[4]: https://mushclient.com/ +[5]: http://forums.zuggsoft.com/index.php?page=4&action=file&file_id=65 +[6]: http://forums.zuggsoft.com/index.php?page=4&action=category&cat_id=11 +[7]: https://www.potatomushclient.com/ +[8]: https://www.mudlet.org/ +[9]: https://archive.org/details/tucows_196173_SimpleMU_MU_Client +[10]: https://www.riverdark.net/atlantis/ +[11]: https://sourceforge.net/projects/g-mud/ +[12]: http://www.beipmu.com/ +[13]: https://itunes.apple.com/us/app/mudrammer-a-modern-mud-client/id597157072 +[14]: https://itunes.apple.com/us/app/mudmaster/id341160033 +[15]: https://bt.happygoatstudios.com/ +[16]: https://play.google.com/store/apps/details?id=com.crap.mukluk +[17]: https://github.com/GNOME/gnome-mud +[18]: https://spyrit.ierne.eu.org/ +[19]: https://jamochamud.org/ +[20]: http://duckclient.com/ +[21]: https://www.kildclient.org/ -``` ## Workarounds for client issues: ### Issue: Telnet NOP displays as spurious character. @@ -105,5 +82,3 @@ Workaround: * In-game: Use `@option NOPKEEPALIVE=off` for the session, or use the `/save` parameter to disable it for that Evennia account permanently. * Client-side: Set a gag-type trigger on the NOP character to make it invisible to the client. - - diff --git a/docs/source/Coding-FAQ.md b/docs/source/Coding-FAQ.md index fe5b569266..8b61ca1a18 100644 --- a/docs/source/Coding-FAQ.md +++ b/docs/source/Coding-FAQ.md @@ -7,21 +7,21 @@ exists before answering - maybe you can clarify that answer rather than to make ## Table of Contents -- [Will I run out of dbrefs?](./Coding-FAQ#will-i-run-out-of-dbrefs) -- [Removing default commands](./Coding-FAQ#removing-default-commands) -- [Preventing character from moving based on a condition](./Coding-FAQ#preventing-character-from- +- [Will I run out of dbrefs?](./Coding-FAQ.md#will-i-run-out-of-dbrefs) +- [Removing default commands](./Coding-FAQ.md#removing-default-commands) +- [Preventing character from moving based on a condition](./Coding-FAQ.md#preventing-character-from- moving-based-on-a-condition) -- [Reference initiating object in an EvMenu command](./Coding-FAQ#reference-initiating-object-in-an- +- [Reference initiating object in an EvMenu command](./Coding-FAQ.md#reference-initiating-object-in-an- evmenu-command) -- [Adding color to default Evennia Channels](./Coding-FAQ#adding-color-to-default-evennia-channels) -- [Selectively turn off commands in a room](./Coding-FAQ#selectively-turn-off-commands-in-a-room) -- [Select Command based on a condition](./Coding-FAQ#select-command-based-on-a-condition) -- [Automatically updating code when reloading](./Coding-FAQ#automatically-updating-code-when- +- [Adding color to default Evennia Channels](./Coding-FAQ.md#adding-color-to-default-evennia-channels) +- [Selectively turn off commands in a room](./Coding-FAQ.md#selectively-turn-off-commands-in-a-room) +- [Select Command based on a condition](./Coding-FAQ.md#select-command-based-on-a-condition) +- [Automatically updating code when reloading](./Coding-FAQ.md#automatically-updating-code-when- reloading) -- [Changing all exit messages](./Coding-FAQ#changing-all-exit-messages) -- [Add parsing with the "to" delimiter](./Coding-FAQ#add-parsing-with-the-to-delimiter) -- [Store last used session IP address](./Coding-FAQ#store-last-used-session-ip-address) -- [Use wide characters with EvTable](./Coding-FAQ#non-latin-characters-in-evtable) +- [Changing all exit messages](./Coding-FAQ.md#changing-all-exit-messages) +- [Add parsing with the "to" delimiter](./Coding-FAQ.md#add-parsing-with-the-to-delimiter) +- [Store last used session IP address](./Coding-FAQ.md#store-last-used-session-ip-address) +- [Use wide characters with EvTable](./Coding-FAQ.md#non-latin-characters-in-evtable) ## Will I run out of dbrefs? **Q:** The `#dbref` of a database object is ever-increasing. Evennia doesn't allow you to change or @@ -34,12 +34,12 @@ still using Evennia at that point and has this concern, get back to us and we ca dbref reuse then. ## Removing default commands -**Q:** How does one *remove* (not replace) e.g. the default `get` [Command](./Commands) from the -Character [Command Set](./Command-Sets)? +**Q:** How does one *remove* (not replace) e.g. the default `get` [Command](./Commands.md) from the +Character [Command Set](./Command-Sets.md)? **A:** Go to `mygame/commands/default_cmdsets.py`. Find the `CharacterCmdSet` class. It has one method named `at_cmdset_creation`. At the end of that method, add the following line: -`self.remove(default_cmds.CmdGet())`. See the [Adding Commands Tutorial](./Adding-Command-Tutorial) +`self.remove(default_cmds.CmdGet())`. See the [Adding Commands Tutorial](./Adding-Command-Tutorial.md) for more info. ## Preventing character from moving based on a condition @@ -47,7 +47,7 @@ for more info. combat, immobilized, etc.) **A:** The `at_before_move` hook is called by Evennia just before performing any move. If it returns -`False`, the move is aborted. Let's say we want to check for an [Attribute](./Attributes) `cantmove`. +`False`, the move is aborted. Let's say we want to check for an [Attribute](./Attributes.md) `cantmove`. Add the following code to the `Character` class: ```python @@ -63,7 +63,7 @@ def at_before_move(self, destination): **Q:** An object has a Command on it starts up an EvMenu instance. How do I capture a reference to that object for use in the menu? -**A:** When an [EvMenu](./EvMenu) is started, the menu object is stored as `caller.ndb._menutree`. +**A:** When an [EvMenu](./EvMenu.md) is started, the menu object is stored as `caller.ndb._menutree`. This is a good place to store menu-specific things since it will clean itself up when the menu closes. When initiating the menu, any additional keywords you give will be available for you as properties on this menu object: @@ -112,7 +112,7 @@ CHANNEL_COLORS`. **Q:** I want certain commands to turn off in a given room. They should still work normally for staff. -**A:** This is done using a custom cmdset on a room [locked with the 'call' lock type](./Locks). Only +**A:** This is done using a custom cmdset on a room [locked with the 'call' lock type](./Locks.md). Only if this lock is passed will the commands on the room be made available to an object inside it. Here is an example of a room where certain commands are disabled for non-staff: @@ -153,7 +153,7 @@ superusers). command to only be available on a full moon, from midnight to three in-game time. **A:** This is easiest accomplished by putting the "werewolf" command on the Character as normal, -but to [lock](./Locks) it with the "cmd" type lock. Only if the "cmd" lock type is passed will the +but to [lock](./Locks.md) it with the "cmd" type lock. Only if the "cmd" lock type is passed will the command be available. ```python @@ -168,8 +168,8 @@ class CmdWerewolf(Command): def func(self): # ... ``` -Add this to the [default cmdset as usual](./Adding-Command-Tutorial). The `is_full_moon` [lock -function](Locks#lock-functions) does not yet exist. We must create that: +Add this to the [default cmdset as usual](./Adding-Command-Tutorial.md). The `is_full_moon` [lock +function](./Locks.md#lock-functions) does not yet exist. We must create that: ```python # in mygame/server/conf/lockfuncs.py diff --git a/docs/source/Coding-Introduction.md b/docs/source/Coding-Introduction.md index 6a072b1516..88232b591f 100644 --- a/docs/source/Coding-Introduction.md +++ b/docs/source/Coding-Introduction.md @@ -10,7 +10,7 @@ Here are some pointers to get you going. Evennia is developed using Python. Even if you are more of a designer than a coder, it is wise to learn how to read and understand basic Python code. If you are new to Python, or need a refresher, -take a look at our two-part [Python introduction](./Python-basic-introduction). +take a look at our two-part [Python introduction](./Python-basic-introduction.md). ### Explore Evennia interactively @@ -31,11 +31,11 @@ This will open an Evennia-aware python shell (using ipython). From within this s evennia. That is, enter `evennia.` and press the `` key. This will show you all the resources made -available at the top level of Evennia's "flat API". See the [flat API](./Evennia-API) page for more +available at the top level of Evennia's "flat API". See the [flat API](./Evennia-API.md) page for more info on how to explore it efficiently. You can complement your exploration by peeking at the sections of the much more detailed -[Developer Central](./Developer-Central). The [Tutorials](./Tutorials) section also contains a growing collection +[Developer Central](./Developer-Central.md). The [Tutorials](./Tutorials.md) section also contains a growing collection of system- or implementation-specific help. ### Use a python syntax checker @@ -52,7 +52,7 @@ using such a checker can be a good start to weed out the simple problems. ### Plan before you code -Before you start coding away at your dream game, take a look at our [Game Planning](./Game-Planning) +Before you start coding away at your dream game, take a look at our [Game Planning](./Game-Planning.md) page. It might hopefully help you avoid some common pitfalls and time sinks. ### Code in your game folder, not in the evennia/ repository @@ -64,7 +64,7 @@ it out into your game folder and edit it there. If you find that Evennia doesn't support some functionality you need, make a [Feature Request](github:issue) about it. Same goes for [bugs](github:issue). If you add features or fix bugs -yourself, please consider [Contributing](./Contributing) your changes upstream! +yourself, please consider [Contributing](./Contributing.md) your changes upstream! ### Learn to read tracebacks diff --git a/docs/source/Coding-Utils.md b/docs/source/Coding-Utils.md index 7ec359aecd..e4f1862dbd 100644 --- a/docs/source/Coding-Utils.md +++ b/docs/source/Coding-Utils.md @@ -29,13 +29,13 @@ If you need to search for objects in a code module you can use the functions in obj = search_object(objname) ``` -- [evennia.search_account](../wiki/evennia.accounts.manager#accountdbmanagersearch_account) -- [evennia.search_object](../wiki/evennia.objects.manager#objectdbmanagersearch_object) -- [evennia.search_object_by_tag](../wiki/evennia.utils.search#search_object_by_tag) -- [evennia.search_script](../wiki/evennia.scripts.manager#scriptdbmanagersearch_script) -- [evennia.search_channel](../wiki/evennia.comms.managers#channeldbmanagersearch_channel) -- [evennia.search_message](../wiki/evennia.comms.managers#msgmanagersearch_message) -- [evennia.search_help](../wiki/evennia.help.manager#helpentrymanagersearch_help) +- [evennia.search_account](evennia.accounts.manager.AccountDBManager.search_account) +- [evennia.search_object](evennia.objects.manager.ObjectDBManager.search_object) +- [evennia.search_tag](evennia.utils.search.search_tag) +- [evennia.search_script](evennia.scripts.manager.ScriptDBManager.search_script) +- [evennia.search_channel](evennia.comms.managers.ChannelDBManager.search_channel) +- [evennia.search_message](evennia.comms.managers.MsgManager.search_message) +- [evennia.search_help](evennia.utils.search.search_help_entry) Note that these latter methods will always return a `list` of results, even if the list has one or zero entries. @@ -50,12 +50,12 @@ entities directly in code (for example when defining new create commands). myobj = evennia.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj") ``` -- [evennia.create_account](../wiki/evennia.utils.create#create_account) -- [evennia.create_object](../wiki/evennia.utils.create#create_object) -- [evennia.create_script](../wiki/evennia.utils.create#create_script) -- [evennia.create_channel](../wiki/evennia.utils.create#create_channel) -- [evennia.create_help_entry](../wiki/evennia.utils.create#create_help_entry) -- [evennia.create_message](../wiki/evennia.utils.create#create_message) +- [evennia.create_account](create_account) +- [evennia.create_object](create_object) +- [evennia.create_script](create_script) +- [evennia.create_channel](create_channel) +- [evennia.create_help_entry](create_help_entry) +- [evennia.create_message](create_message) Each of these create-functions have a host of arguments to further customize the created entity. See `evennia/utils/create.py` for more information. @@ -181,13 +181,13 @@ number of seconds. This is a very light wrapper over a Twisted non-persistently, which means that if the server is `@reload`ed before the delay is over, the callback will never run (the server forgets it). If setting `persistent` to True, the delay will be stored in the database and survive a `@reload` - but for this to work it is susceptible to the same -limitations incurred when saving to an [Attribute](./Attributes). +limitations incurred when saving to an [Attribute](./Attributes.md). The `deferred` return object can usually be ignored, but calling its `.cancel()` method will abort the delay prematurely. `utils.delay` is the lightest form of delayed call in Evennia. For other way to create time-bound -tasks, see the [TickerHandler](./TickerHandler) and [Scripts](./Scripts). +tasks, see the [TickerHandler](./TickerHandler.md) and [Scripts](./Scripts.md). > Note that many delayed effects can be achieved without any need for an active timer. For example if you have a trait that should recover a point every 5 seconds you might just need its value when @@ -203,7 +203,7 @@ classes, instances or python-paths-to-classes. Note that Python code should usually work with [duck typing](http://en.wikipedia.org/wiki/Duck_typing). But in Evennia's case it can sometimes be useful -to check if an object inherits from a given [Typeclass](./Typeclasses) as a way of identification. Say +to check if an object inherits from a given [Typeclass](./Typeclasses.md) as a way of identification. Say for example that we have a typeclass *Animal*. This has a subclass *Felines* which in turn has a subclass *HouseCat*. Maybe there are a bunch of other animal types too, like horses and dogs. Using `inherits_from` will allow you to check for all animals in one go: @@ -274,10 +274,10 @@ need to send byte-data over the wire, `to_str` is the only one you'll need. The difference from Python's in-built `str()` and `bytes()` operators are that the Evennia ones makes use of the `ENCODINGS` setting and will try very hard to never raise a traceback but instead echo errors through logging. See -[here](./Text-Encodings) for more info. +[here](./Text-Encodings.md) for more info. ### Ansi Coloring Tools -- [evennia.ansi](api:evennia.utils.ansi) +- [evennia.ansi](evennia.utils.ansi) ## Display utilities ### Making ascii tables diff --git a/docs/source/Command-Cooldown.md b/docs/source/Command-Cooldown.md index cd134c5eb1..c150a39c62 100644 --- a/docs/source/Command-Cooldown.md +++ b/docs/source/Command-Cooldown.md @@ -9,7 +9,7 @@ a while. Such effects are called *cooldowns*. This page exemplifies a very resource-efficient way to do cooldowns. A more 'active' way is to use asynchronous delays as in the [command duration -tutorial](Command-Duration#Blocking-Commands), the two might be useful to +tutorial](./Command-Duration.md#blocking-commands), the two might be useful to combine if you want to echo some message to the user after the cooldown ends. ## Non-persistent cooldown @@ -88,7 +88,7 @@ database, you need to use the caster for the storage. self.caller.db.firestorm_lastcast = now ``` -Since we are storing as an [Attribute](./Attributes), we need to identify the +Since we are storing as an [Attribute](./Attributes.md), we need to identify the variable as `firestorm_lastcast` so we are sure we get the right one (we'll likely have other skills with cooldowns after all). But this method of using cooldowns also has the advantage of working *between* commands - you can diff --git a/docs/source/Command-Duration.md b/docs/source/Command-Duration.md index 903fad30d1..8a6bee7732 100644 --- a/docs/source/Command-Duration.md +++ b/docs/source/Command-Duration.md @@ -2,7 +2,7 @@ Before reading this tutorial, if you haven't done so already, you might want to -read [the documentation on commands](./Commands) to get a basic understanding of +read [the documentation on commands](./Commands.md) to get a basic understanding of how commands work in Evennia. In some types of games a command should not start and finish immediately. @@ -40,7 +40,7 @@ class CmdTest(Command): > Important: The `yield` functionality will *only* work in the `func` method of > Commands. It only works because Evennia has especially > catered for it in Commands. If you want the same functionality elsewhere you -> must use the [interactive decorator](./Async-Process#The-@interactive-decorator). +> must use the [interactive decorator](./Async-Process.md#the-interactive-decorator). The important line is the `yield 10`. It tells Evennia to "pause" the command and to wait for 10 seconds to execute the rest. If you add this command and @@ -187,7 +187,7 @@ start crafting a shield at the same time, or if you just did a huge power-swing should not be able to do it again immediately. The simplest way of implementing blocking is to use the technique covered in the [Command -Cooldown](Command-Cooldown) tutorial. In that tutorial we implemented cooldowns by having the +Cooldown](./Command-Cooldown.md) tutorial. In that tutorial we implemented cooldowns by having the Command store the current time. Next time the Command was called, we compared the current time to the stored time to determine if enough time had passed for a renewed use. This is a *very* efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player diff --git a/docs/source/Command-Prompt.md b/docs/source/Command-Prompt.md index 47db5d62f4..bf99b5ddc6 100644 --- a/docs/source/Command-Prompt.md +++ b/docs/source/Command-Prompt.md @@ -21,7 +21,7 @@ You can combine the sending of normal text with the sending (updating of the pro self.msg("This is a text", prompt="This is a prompt") ``` -You can update the prompt on demand, this is normally done using [OOB](./OOB)-tracking of the relevant +You can update the prompt on demand, this is normally done using [OOB](./OOB.md)-tracking of the relevant Attributes (like the character's health). You could also make sure that attacking commands update the prompt when they cause a change in health, for example. diff --git a/docs/source/Command-Sets.md b/docs/source/Command-Sets.md index be5c452119..ab4d56a446 100644 --- a/docs/source/Command-Sets.md +++ b/docs/source/Command-Sets.md @@ -1,7 +1,7 @@ # Command Sets -Command Sets are intimately linked with [Commands](./Commands) and you should be familiar with +Command Sets are intimately linked with [Commands](./Commands.md) and you should be familiar with Commands before reading this page. The two pages were split for ease of reading. A *Command Set* (often referred to as a CmdSet or cmdset) is the basic unit for storing one or more @@ -11,7 +11,7 @@ classes in a command set is the way to make commands available to use in your ga When storing a CmdSet on an object, you will make the commands in that command set available to the object. An example is the default command set stored on new Characters. This command set contains all the useful commands, from `look` and `inventory` to `@dig` and `@reload` -([permissions](./Locks#Permissions) then limit which players may use them, but that's a separate +([permissions](./Locks.md#permissions) then limit which players may use them, but that's a separate topic). When an account enters a command, cmdsets from the Account, Character, its location, and elsewhere @@ -26,7 +26,7 @@ on. The tutorial world included with Evennia showcases a dark room that replaces commands with its own versions because the Character cannot see. If you want a quick start into defining your first commands and using them with command sets, you -can head over to the [Adding Command Tutorial](./Adding-Command-Tutorial) which steps through things +can head over to the [Adding Command Tutorial](./Adding-Command-Tutorial.md) which steps through things without the explanations. ## Defining Command Sets @@ -112,11 +112,11 @@ back even if all other cmdsets fail or are removed. It is always persistent and by `cmdset.delete()`. To remove a default cmdset you must explicitly call `cmdset.remove_default()`. Command sets are often added to an object in its `at_object_creation` method. For more examples of -adding commands, read the [Step by step tutorial](./Adding-Command-Tutorial). Generally you can +adding commands, read the [Step by step tutorial](./Adding-Command-Tutorial.md). Generally you can customize which command sets are added to your objects by using `self.cmdset.add()` or `self.cmdset.add_default()`. -> Important: Commands are identified uniquely by key *or* alias (see [Commands](./Commands)). If any +> Important: Commands are identified uniquely by key *or* alias (see [Commands](./Commands.md)). If any overlap exists, two commands are considered identical. Adding a Command to a command set that already has an identical command will *replace* the previous command. This is very important. You must take this behavior into account when attempting to overload any default Evennia commands with @@ -127,7 +127,7 @@ new one that has a matching alias. There are several extra flags that you can set on CmdSets in order to modify how they work. All are optional and will be set to defaults otherwise. Since many of these relate to *merging* cmdsets, -you might want to read the [Adding and Merging Command Sets](./Command-Sets#adding-and-merging- +you might want to read the [Adding and Merging Command Sets](./Command-Sets.md#adding-and-merging- command-sets) section for some of these to make sense. - `key` (string) - an identifier for the cmdset. This is optional, but should be unique. It is used @@ -138,7 +138,7 @@ dictionary below. - `priority` (int) - This defines the merge order of the merge stack - cmdsets will merge in rising order of priority with the highest priority set merging last. During a merger, the commands from the set with the higher priority will have precedence (just what happens depends on the [merge -type](Command-Sets#adding-and-merging-command-sets)). If priority is identical, the order in the +type](./Command-Sets.md#adding-and-merging-command-sets)). If priority is identical, the order in the merge stack determines preference. The priority value must be greater or equal to `-100`. Most in- game sets should usually have priorities between `0` and `100`. Evennia default sets have priorities as follows (these can be changed if you want a different distribution): @@ -154,7 +154,7 @@ arguments, there is no collision between exits named the same as a channel even differently with certain named cmdsets. If the cmdset to merge with has a `key` matching an entry in `key_mergetype`, it will not be merged according to the setting in `mergetype` but according to the mode in this dict. Please note that this is more complex than it may seem due to the [merge -order](Command-Sets#adding-and-merging-command-sets) of command sets. Please review that section +order](./Command-Sets.md#adding-and-merging-command-sets) of command sets. Please review that section before using `key_mergetype`. - `duplicates` (bool/None default `None`) - this determines what happens when merging same-priority cmdsets containing same-key commands together. The`dupicate` option will *only* apply when merging @@ -195,15 +195,15 @@ priority determines what is used. ## Command Sets Searched -When a user issues a command, it is matched against the [merged](./Command-Sets#adding-and-merging- +When a user issues a command, it is matched against the [merged](./Command-Sets.md#adding-and-merging- command-sets) command sets available to the player at the moment. Which those are may change at any time (such as when the player walks into the room with the `Window` object described earlier). The currently valid command sets are collected from the following sources: -- The cmdsets stored on the currently active [Session](./Sessions). Default is the empty +- The cmdsets stored on the currently active [Session](./Sessions.md). Default is the empty `SessionCmdSet` with merge priority `-20`. -- The cmdsets defined on the [Account](./Accounts). Default is the AccountCmdSet with merge priority +- The cmdsets defined on the [Account](./Accounts.md). Default is the AccountCmdSet with merge priority `-10`. - All cmdsets on the Character/Object (assuming the Account is currently puppeting such a Character/Object). Merge priority `0`. @@ -215,14 +215,14 @@ included if `no_objs` option is active in the merge stack. `no_objs` option is active in the merge stack. - The cmdsets of Exits in the location. Merge priority `+101`. Will not be included if `no_exits` *or* `no_objs` option is active in the merge stack. -- The [channel](./Communications) cmdset containing commands for posting to all channels the account +- The [channel](./Communications.md) cmdset containing commands for posting to all channels the account or character is currently connected to. Merge priority `+101`. Will not be included if `no_channels` option is active in the merge stack. Note that an object does not *have* to share its commands with its surroundings. A Character's cmdsets should not be shared for example, or all other Characters would get multi-match errors just by being in the same room. The ability of an object to share its cmdsets is managed by its `call` -[lock](./Locks). For example, [Character objects](./Objects) defaults to `call:false()` so that any +[lock](./Locks.md). For example, [Character objects](./Objects.md) defaults to `call:false()` so that any cmdsets on them can only be accessed by themselves, not by other objects around them. Another example might be to lock an object with `call:inside()` to only make their commands available to objects inside them, or `cmd:holds()` to make their commands available only if they are held. diff --git a/docs/source/Command-System.md b/docs/source/Command-System.md index 1288202ce5..364d6f09ef 100644 --- a/docs/source/Command-System.md +++ b/docs/source/Command-System.md @@ -1,9 +1,9 @@ # Command System -- [Commands](./Commands) -- [Command Sets](./Command-Sets) -- [Command Auto-help](./Help-System#command-auto-help-system) +- [Commands](./Commands.md) +- [Command Sets](./Command-Sets.md) +- [Command Auto-help](./Help-System.md#command-auto-help-system) See also: -- [Default Command Help](./Default-Command-Help) -- [Adding Command Tutorial](./Adding-Command-Tutorial) \ No newline at end of file +- [Default Command Help](./Default-Commands.md) +- [Adding Command Tutorial](./Adding-Command-Tutorial.md) \ No newline at end of file diff --git a/docs/source/Commands.md b/docs/source/Commands.md index d59ddc0f69..934579acb8 100644 --- a/docs/source/Commands.md +++ b/docs/source/Commands.md @@ -1,14 +1,14 @@ # Commands -Commands are intimately linked to [Command Sets](./Command-Sets) and you need to read that page too to +Commands are intimately linked to [Command Sets](./Command-Sets.md) and you need to read that page too to be familiar with how the command system works. The two pages were split for easy reading. The basic way for users to communicate with the game is through *Commands*. These can be commands directly related to the game world such as *look*, *get*, *drop* and so on, or administrative commands such as *examine* or *@dig*. -The [default commands](./Default-Command-Help) coming with Evennia are 'MUX-like' in that they use @ +The [default commands](./Default-Commands.md) coming with Evennia are 'MUX-like' in that they use @ for admin commands, support things like switches, syntax with the '=' symbol etc, but there is nothing that prevents you from implementing a completely different command scheme for your game. You can find the default commands in `evennia/commands/default`. You should not edit these directly - @@ -16,7 +16,7 @@ they will be updated by the Evennia team as new features are added. Rather you s for inspiration and inherit your own designs from them. There are two components to having a command running - the *Command* class and the [Command -Set](Command-Sets) (command sets were split into a separate wiki page for ease of reading). +Set](./Command-Sets.md) (command sets were split into a separate wiki page for ease of reading). 1. A *Command* is a python class containing all the functioning code for what a command does - for example, a *get* command would contain code for picking up objects. @@ -28,8 +28,8 @@ object in various ways. Consider a "Tree" object with a cmdset defining the comm *chop down*. Or a "Clock" with a cmdset containing the single command *check time*. This page goes into full detail about how to use Commands. To fully use them you must also read the -page detailing [Command Sets](./Command-Sets). There is also a step-by-step [Adding Command -Tutorial](Adding-Command-Tutorial) that will get you started quickly without the extra explanations. +page detailing [Command Sets](./Command-Sets.md). There is also a step-by-step [Adding Command +Tutorial](./Adding-Command-Tutorial.md) that will get you started quickly without the extra explanations. ## Defining Commands @@ -80,15 +80,15 @@ In Evennia there are three types of objects that may call the command. It is im of this since this will also assign appropriate `caller`, `session`, `sessid` and `account` properties on the command body at runtime. Most often the calling type is `Session`. -* A [Session](./Sessions). This is by far the most common case when a user is entering a command in +* A [Session](./Sessions.md). This is by far the most common case when a user is entering a command in their client. - * `caller` - this is set to the puppeted [Object](./Objects) if such an object exists. If no + * `caller` - this is set to the puppeted [Object](./Objects.md) if such an object exists. If no puppet is found, `caller` is set equal to `account`. Only if an Account is not found either (such as before being logged in) will this be set to the Session object itself. - * `session` - a reference to the [Session](./Sessions) object itself. + * `session` - a reference to the [Session](./Sessions.md) object itself. * `sessid` - `sessid.id`, a unique integer identifier of the session. - * `account` - the [Account](./Accounts) object connected to this Session. None if not logged in. -* An [Account](./Accounts). This only happens if `account.execute_cmd()` was used. No Session + * `account` - the [Account](./Accounts.md) object connected to this Session. None if not logged in. +* An [Account](./Accounts.md). This only happens if `account.execute_cmd()` was used. No Session information can be obtained in this case. * `caller` - this is set to the puppeted Object if such an object can be determined (without Session info this can only be determined in `MULTISESSION_MODE=0` or `1`). If no puppet is found, @@ -96,7 +96,7 @@ this is equal to `account`. * `session` - `None*` * `sessid` - `None*` * `account` - Set to the Account object. -* An [Object](./Objects). This only happens if `object.execute_cmd()` was used (for example by an +* An [Object](./Objects.md). This only happens if `object.execute_cmd()` was used (for example by an NPC). * `caller` - This is set to the calling Object in question. * `session` - `None*` @@ -119,10 +119,10 @@ it the following properties: - `caller` - The character BigGuy, in this example. This is a reference to the object executing the command. The value of this depends on what type of object is calling the command; see the previous section. -- `session` - the [Session](./Sessions) Bob uses to connect to the game and control BigGuy (see also +- `session` - the [Session](./Sessions.md) Bob uses to connect to the game and control BigGuy (see also previous section). - `sessid` - the unique id of `self.session`, for quick lookup. -- `account` - the [Account](./Accounts) Bob (see previous section). +- `account` - the [Account](./Accounts.md) Bob (see previous section). - `cmdstring` - the matched key for the command. This would be *look* in our example. - `args` - this is the rest of the string, except the command name. So if the string entered was *look at sword*, `args` would be " *at sword*". Note the space kept - Evennia would correctly @@ -130,7 +130,7 @@ interpret `lookat sword` too. This is useful for things like `/switches` that sh In the `MuxCommand` class used for default commands, this space is stripped. Also see the `arg_regex` property if you want to enforce a space to make `lookat sword` give a command-not-found error. -- `obj` - the game [Object](./Objects) on which this command is defined. This need not be the caller, +- `obj` - the game [Object](./Objects.md) on which this command is defined. This need not be the caller, but since `look` is a common (default) command, this is probably defined directly on *BigGuy* - so `obj` will point to BigGuy. Otherwise `obj` could be an Account or any interactive object with commands defined on it, like in the example of the "check time" command defined on a "Clock" object. @@ -149,7 +149,7 @@ not used, but they could be used to implement alternate help-display systems. - `.client_width()` - Shortcut for getting the client's screen-width. Note that not all clients will truthfully report this value - that case the `settings.DEFAULT_SCREEN_WIDTH` will be returned. -- `.styled_table(*args, **kwargs)` - This returns an [EvTable](api:evennia.utils#module- +- `.styled_table(*args, **kwargs)` - This returns an [EvTable](module- evennia.utils.evtable) styled based on the session calling this command. The args/kwargs are the same as for EvTable, except styling defaults are set. @@ -168,7 +168,7 @@ key can consist of more than one word, like "press button" or "pull left lever". either matches. This is important for merging cmdsets described below. - `aliases` (optional list) - a list of alternate names for the command (`["glance", "see", "l"]`). Same name rules as for `key` applies. -- `locks` (string) - a [lock definition](./Locks), usually on the form `cmd:`. Locks is a +- `locks` (string) - a [lock definition](./Locks.md), usually on the form `cmd:`. Locks is a rather big topic, so until you learn more about locks, stick to giving the lockstring `"cmd:all()"` to make the command available to everyone (if you don't provide a lock string, this will be assigned for you). @@ -180,9 +180,9 @@ by the next command by retrieving `self.caller.ndb.last_cmd`. The next run comma or replace the storage. - `arg_regex` (optional raw string): Used to force the parser to limit itself and tell it when the command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is -done with a regular expression. [See the arg_regex section](./Commands#on-arg_regex) for the details. +done with a regular expression. [See the arg_regex section](./Commands.md#on-arg_regex) for the details. - `auto_help` (optional boolean). Defaults to `True`. This allows for turning off the [auto-help -system](Help-System#command-auto-help-system) on a per-command basis. This could be useful if you +system](./Help-System.md#command-auto-help-system) on a per-command basis. This could be useful if you either want to write your help entries manually or hide the existence of a command from `help`'s generated list. - `is_exit` (bool) - this marks the command as being used for an in-game exit. This is, by default, @@ -218,7 +218,7 @@ from this method will be returned from the execution as a Twisted Deferred. Finally, you should always make an informative [doc string](http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring) (`__doc__`) at the top of your -class. This string is dynamically read by the [Help System](./Help-System) to create the help entry +class. This string is dynamically read by the [Help System](./Help-System.md) to create the help entry for this command. You should decide on a way to format your help and stick to that. Below is how you define a simple alternative "`smile`" command: @@ -276,7 +276,7 @@ default commands thus need to implement `parse()` at all, but can assume the incoming string is already split up and parsed in suitable ways by its parent. Before you can actually use the command in your game, you must now store it -within a *command set*. See the [Command Sets](./Command-Sets) page. +within a *command set*. See the [Command Sets](./Command-Sets.md) page. ### On arg_regex @@ -427,7 +427,7 @@ will show. > Note again that the `yield` keyword does not store state. If the game reloads while waiting for the user to answer, the user will have to start over. It is not a good idea to use `yield` for -important or complex choices, a persistent [EvMenu](./EvMenu) might be more appropriate in this case. +important or complex choices, a persistent [EvMenu](./EvMenu.md) might be more appropriate in this case. ## System commands @@ -457,7 +457,7 @@ display the "Huh?" error message. matches. - User is not allowed to execute the command (`syscmdkeys.CMD_NOPERM`) - Default is to display the "Huh?" error message. -- Channel (`syscmdkeys.CMD_CHANNEL`) - This is a [Channel](./Communications) name of a channel you are +- Channel (`syscmdkeys.CMD_CHANNEL`) - This is a [Channel](./Communications.md) name of a channel you are subscribing to - Default is to relay the command's argument to that channel. Such commands are created by the Comm system on the fly depending on your subscriptions. - New session connection (`syscmdkeys.CMD_LOGINSTART`). This command name should be put in the @@ -484,7 +484,7 @@ work. Normally Commands are created as fixed classes and used without modification. There are however situations when the exact key, alias or other properties is not possible (or impractical) to pre- -code ([Exits](./Commands#Exits) is an example of this). +code ([Exits](./Commands.md#exits) is an example of this). To create a command with a dynamic call signature, first define the command body normally in a class (set your `key`, `aliases` to default values), then use the following call (assuming the command @@ -508,10 +508,10 @@ make your command completely customized at run-time. *Note: This is an advanced topic.* -Exits are examples of the use of a [Dynamic Command](./Commands#Dynamic_Commands). +Exits are examples of the use of a [Dynamic Command](./Commands.md#dynamic-commands). -The functionality of [Exit](./Objects) objects in Evennia is not hard-coded in the engine. Instead -Exits are normal [typeclassed](./Typeclasses) objects that auto-create a [CmdSet](./Commands#CmdSets) on +The functionality of [Exit](./Objects.md) objects in Evennia is not hard-coded in the engine. Instead +Exits are normal [typeclassed](./Typeclasses.md) objects that auto-create a [CmdSet](./Command-Sets.md) on themselves when they load. This cmdset has a single dynamically created Command with the same properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit, this dynamic exit-command is triggered and (after access checks) moves the Character to the exit's @@ -609,9 +609,9 @@ cmdset, ignore. - CmdSets defined on the current account, if caller is a puppeted object. - CmdSets defined on the Session itself. - The active CmdSets of eventual objects in the same location (if any). This includes commands -on [Exits](./Objects#Exits). +on [Exits](./Objects.md#exits). - Sets of dynamically created *System commands* representing available -[Communications](./Communications#Channels). +[Communications](./Communications.md#channels). 7. All CmdSets *of the same priority* are merged together in groups. Grouping avoids order- dependent issues of merging multiple same-prio sets onto lower ones. 8. All the grouped CmdSets are *merged* in reverse priority into one combined CmdSet according to diff --git a/docs/source/Communications.md b/docs/source/Communications.md index dba169650a..3c3056c54f 100644 --- a/docs/source/Communications.md +++ b/docs/source/Communications.md @@ -12,7 +12,7 @@ mimics the API of a `Msg` but has no connection to the database. ## Msg The `Msg` object is the basic unit of communication in Evennia. A message works a little like an -e-mail; it always has a sender (a [Account](./Accounts)) and one or more recipients. The recipients +e-mail; it always has a sender (a [Account](./Accounts.md)) and one or more recipients. The recipients may be either other Accounts, or a *Channel* (see below). You can mix recipients to send the message to both Channels and Accounts if you like. @@ -22,16 +22,16 @@ have 'mailboxes' with the messages they want to keep. ### Properties defined on `Msg` -- `senders` - this is a reference to one or many [Account](./Accounts) or [Objects](./Objects) (normally +- `senders` - this is a reference to one or many [Account](./Accounts.md) or [Objects](./Objects.md) (normally *Characters*) sending the message. This could also be an *External Connection* such as a message coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be mixed in any combination. -- `receivers` - a list of target [Accounts](./Accounts), [Objects](./Objects) (usually *Characters*) or +- `receivers` - a list of target [Accounts](./Accounts.md), [Objects](./Objects.md) (usually *Characters*) or *Channels* to send the message to. The types of receivers can be mixed in any combination. - `header` - this is a text field for storing a title or header for the message. - `message` - the actual text being sent. - `date_sent` - when message was sent (auto-created). -- `locks` - a [lock definition](./Locks). +- `locks` - a [lock definition](./Locks.md). - `hide_from` - this can optionally hold a list of objects, accounts or channels to hide this `Msg` from. This relationship is stored in the database primarily for optimization reasons, allowing for quickly post-filter out messages not intended for a given target. There is no in-game methods for @@ -48,16 +48,16 @@ system expecting a `Msg` but when you don't actually want to save anything. ## Channels -Channels are [Typeclassed](./Typeclasses) entities, which mean they can be easily extended and their +Channels are [Typeclassed](./Typeclasses.md) entities, which mean they can be easily extended and their functionality modified. To change which channel typeclass Evennia uses, change settings.BASE_CHANNEL_TYPECLASS. Channels act as generic distributors of messages. Think of them as "switch boards" redistributing `Msg` or `TempMsg` objects. Internally they hold a list of "listening" objects and any `Msg` (or `TempMsg`) sent to the channel will be distributed out to all channel listeners. Channels have -[Locks](./Locks) to limit who may listen and/or send messages through them. +[Locks](./Locks.md) to limit who may listen and/or send messages through them. -The *sending* of text to a channel is handled by a dynamically created [Command](./Commands) that +The *sending* of text to a channel is handled by a dynamically created [Command](./Commands.md) that always have the same name as the channel. This is created for each channel by the global `ChannelHandler`. The Channel command is added to the Account's cmdset and normal command locks are used to determine which channels are possible to write to. When subscribing to a channel, you can @@ -109,5 +109,5 @@ for channel communication (since the default ChannelCommand instead logs to a fi - `aliases` - alternative native names for channels - `desc` - optional description of channel (seen in listings) - `keep_log` (bool) - if the channel should store messages (default) -- `locks` - A [lock definition](./Locks). Channels normally use the access_types `send, control` and +- `locks` - A [lock definition](./Locks.md). Channels normally use the access_types `send, control` and `listen`. \ No newline at end of file diff --git a/docs/source/Connection-Screen.md b/docs/source/Connection-Screen.md index d0b4f37275..bb15919f30 100644 --- a/docs/source/Connection-Screen.md +++ b/docs/source/Connection-Screen.md @@ -20,7 +20,7 @@ Effective, but not very exciting. You will most likely want to change this to be your game. This is simple: 1. Edit `mygame/server/conf/connection_screens.py`. -1. [Reload](./Start-Stop-Reload) Evennia. +1. [Reload](./Start-Stop-Reload.md) Evennia. Evennia will look into this module and locate all *globally defined strings* in it. These strings are used as the text in your connection screen and are shown to the user at startup. If more than @@ -29,8 +29,8 @@ available. ### Commands available at the Connection Screen -You can also customize the [Commands](./Commands) available to use while the connection screen is +You can also customize the [Commands](./Commands.md) available to use while the connection screen is shown (`connect`, `create` etc). These commands are a bit special since when the screen is running the account is not yet logged in. A command is made available at the login screen by adding them to -`UnloggedinCmdSet` in `mygame/commands/default_cmdset.py`. See [Commands](./Commands) and the +`UnloggedinCmdSet` in `mygame/commands/default_cmdset.py`. See [Commands](./Commands.md) and the tutorial section on how to add new commands to a default command set. diff --git a/docs/source/Continuous-Integration.md b/docs/source/Continuous-Integration.md index f6323cc9f3..f4bf7e5199 100644 --- a/docs/source/Continuous-Integration.md +++ b/docs/source/Continuous-Integration.md @@ -27,7 +27,7 @@ Among those you will need: * A Continuous Integration Environment. * I recommend [TeamCity](https://www.jetbrains.com/teamcity/) which has an in-depth [Setup Guide](https://confluence.jetbrains.com/display/TCD8/Installing+and+Configuring+the+TeamCity+Server) -* [Source Control](./Version-Control) +* [Source Control](./Version-Control.md) * This could be Git or SVN or any other available SC. ## Linux TeamCity Setup diff --git a/docs/source/Contributing-Docs.md b/docs/source/Contributing-Docs.md index 491c09eb51..a7236cee9c 100644 --- a/docs/source/Contributing-Docs.md +++ b/docs/source/Contributing-Docs.md @@ -1,34 +1,507 @@ # Contributing to Evennia Docs -```warning:: - The creation of docs pages is still WIP and we are still figuring things out here and there. +```{warning} +This system is still WIP and many things are bound to change! ``` -Contributing to the docs is is like [contributing to the rest of Evennia][contributing]: Check out the branch of Evennia you want to edit the documentation for. Create your -own work-branch, make your changes to files in `evennia/docs/source/` and make a PR for it! +Contributing to the docs is is like [contributing to the rest of Evennia][contributing]: Check out the branch of Evennia +you want to edit the documentation for. Create your own work-branch, make your changes to files +in `evennia/docs/source/` and make a PR for it! The documentation source files are `*.md` (Markdown) files found in `evennia/docs/source/`. Markdown files are simple text files that can be edited with a normal text editor. They can also -contain raw HTML directives (but that is very rarely needed). They primarly use -the [Markdown][commonmark] syntax. See [the syntax section below](#Editing-syntax) for more help. +contain raw HTML directives (but that is very rarely needed). They use +the [Markdown][commonmark] syntax with [MyST extensions][MyST]. -## Source file structure +```{important} +You do _not_ need to be able to test/build the docs locally to contribute a documentation PR. +We'll resolve any issues when we merge and build documentation. If you still want to build +the docs for yourself, instructions are [at the end of this document](#building-the-docs-locally). +``` -For v 0.9.5, the sources are all together under `evennia/docs/source/`. The main files are all -Markdown (`.md`) files. +## Source file structure -Other files and folders: +The sources are organized into several rough categories, with only a few administrative documents +at the root of `evennia/docs/source/`. The folders are named in singular form since they will +primarily be accessed as link refs (e.g. `Component/Accounts`) + +- `source/Components/` are docs describing separate Evennia building blocks, that is, things + that you can import and use. This extends and elaborates on what can be found out by reading + the api docs themselves. Example are documentation for `Accounts`, `Objects` and `Commands`. +- `source/Concepts/` describes how larger-scale features of Evennia hang together - things that + can't easily be broken down into one isolated component. This can be general descriptions of + how Models and Typeclasses interact to the path a message takes from the client to the server + and back. +- `source/Setup/` holds detailed docs on installing, running and maintaining the Evennia server and + the infrastructure around it. +- `source/Coding/` has help on how to interact with, use and navigate the Evennia codebase itself. + This also has non-Evennia-specific help on general development concepts and how to set up a sane + development environment. +- `source/Contribs/` holds documentation specifically for packages in the `evennia/contribs/` folder. + Any contrib-specific tutorials will be found here instead of in `Howtos` +- `source/Howtos/` holds docs that describe how to achieve a specific goal, effect or + result in Evennia. This is often on a tutorial or FAQ form and will refer to the rest of the + documentation for further reading. + - `source/Howtos/Starting/` holds all documents part of the initial tutorial sequence. + + + Other files and folders: - `source/api/` contains the auto-generated API documentation as `.rst` files. Don't edit these - files manually, your changes will be lost. To refer to these files, use `api:` followed by - the Python path, for example `[rpsystem contrib](api:evennia.contrib.rpsystem)`. - - `source/_templates` and `source/_static` should not be modified unless adding a new doc-page + files manually, your changes will be lost. To refer to these files, use `api:` followed by + the Python path, for example `[rpsystem contrib](evennia.contrib.rpsystem)`. + - `source/_templates` and `source/_static` should not be modified unless adding a new doc-page feature or changing the look of the HTML documentation. - `conf.py` holds the Sphinx configuration. It should usually not be modified except to update the Evennia version on a new branch. - -## Building the docs locally + +# Editing syntax + +The format used for Evennia's docs is [Markdown][commonmark-help] (Commonmark). While markdown +supports a few alternative forms for some of these, we try to stick to the below forms for consistency. + +## Italic/Bold + +We generally use underscores for italics and double-asterisks for bold: + +- `_Italic text_` - _Italic text_ +- `**Bold Text**` - **Bold text** + +## Headings + +We use `#` to indicate sections/headings. The more `#` the more of a sub-heading it is (will get +smaller and smaller font). + +- `# Heading` +- `## SubHeading` +- `### SubSubHeading` +- `#### SubSubSubHeading` + +> Don't use the same heading/subheading name more than once in one page. While Markdown +does not prevent it, it will make it impossible to refer to that heading uniquely. +The Evennia documentation preparser will detect this and give you an error. + +## Lists + +One can create both bullet-point lists and numbered lists: + +``` +- first bulletpoint +- second bulletpoint +- third bulletpoint +``` + +- first bulletpoint +- second bulletpoint +- third bulletpoint + +``` +1. Numbered point one +2. Numbered point two +3. Numbered point three +``` + +1. Numbered point one +2. Numbered point two +3. Numbered point three + +## Blockquotes + +A blockquote will create an indented block. It's useful for emphasis and is +added by starting one or more lines with `>`. For 'notes' you can also use +an explicit [Note](#note). + +``` +> This is an important +> thing to remember. +``` + +> Note: This is an important +> thing to remember. + +## Links + +The link syntax is `[linktext](url_or_ref)` - this gives a clickable link [linktext](#links). + +### Internal links + +Most links will be to other pages of the documentation or to Evennia's API docs. Each document +heading can be referenced. The reference always starts with `#`. The heading-name is always +given in lowercase and ignores any non-letters. Spaces in the heading title are replaced with +a single dash `-`. + +As an example, let's assume the following is the contents of a file `Menu-stuff.md`: + +``` +# Menu items + +Some text... + +## A yes/no? example + +Some more text... +``` + +- From _inside the same file_ you can refer to each heading as + + [menus](#menu-items) + [example](#a-yesno-example) + +- From _another file_, you reference them as as + + [menus](Menu-Stuff.md#menu-items) + [example](Menu-Stuff.md#a-yesno-example) + +> It's fine to not include the `.md` file ending in the reference. The Evennia doc-build process +> will correct for this (and also insert any needed relative paths in the reference). + +### API links + +The documentation contains auto-generated documentation for all of Evennia's source code. You +can direct the reader to the sources by just giving the python-path to the location of the +resource under the `evennia/` repository: + + [DefaultObject](evennia.objects.objects.DefaultObject) + +[DefaultObject](evennia.objects.objects.DefaultObject) <- like this! + +Note that you can't refer to files in the `mygame` folder this way. The game folder is generated +dynamically and is not part of the api docs. Refer to the parent classes in `evennia` where possible. + +### External links + +These are links to resources outside of the documentation. We also provide some convenient shortcuts. + +- `[linkname](https://evennia.com)` - link to an external website. +- `[linkname](github:evennia/objects/objects.py)` - this is a shortcut to point to a location in the + official Evennia repository on Github. Note that you must use `/` and give the full file name. By + default this is code in the `master` branch. +- `[linkname](github:develop/evennia/objects.objects.py` - this points to code in the `develop` branch. +- `[make an issue](github:issue)` - this is a shortcut to the Evennia github issue-creation page. + +> Note that if you want to refer to code, it's usually better to [link to the API](#api-links) as +> described above. + +### Urls/References in one place + +Urls can get long and if you are using the same url/reference in many places it can get a +little cluttered. So you can also put the url as a 'footnote' at the end of your document. +You can then refer to it by putting your reference within square brackets `[ ]`. Here's an example: + +``` +This is a [clickable link][mylink]. This is [another link][1]. + +... + + +[mylink]: http://... +[1]: My-Document.md#this-is-a-long-ref + +``` + +This makes the main text a little shorter. + +## Tables + +A table is done like this: + +```` +| heading1 | heading2 | heading3 | +| --- | --- | --- | +| value1 | value2 | value3 | +| | value 4 | | +| value 5 | value 6 | | +```` + +| heading1 | heading2 | heading3 | +| --- | --- | --- | +| value1 | value2 | value3 | +| | value 4 | | +| value 5 | value 6 | | + +As seen, the Markdown syntax can be pretty sloppy (columns don't need to line up) as long as you +include the heading separators and make sure to add the correct number of `|` on every line. + + +## Verbatim text + +It's common to want to mark something to be displayed verbatim - just as written - without any +Markdown parsing. In running text, this is done using backticks (\`), like \`verbatim text\` becomes +`verbatim text`. + +If you want to put the verbatim text on its own line, you can do so easily by simply indenting +it 4 spaces (add empty lines on each side for readability too): + +``` +This is normal text + + This is verbatim text + +This is normal text +``` + +Another way is to use triple-backticks: + +```` +``` +Everything within these backticks will be verbatim. + +``` +```` + +### Code blocks + +A special 'verbatim' case is code examples - we want them to get code-highlighting for readability. +This is done by using the triple-backticks and specify which language we use: + +```` +```python +from evennia import Command +class CmdEcho(Command): + """ + Usage: echo + """ + key = "echo" + def func(self): + self.caller.msg(self.args.strip()) +``` +```` + +```python +from evennia import Command +class CmdEcho(Command): + """ + Usage: echo + """ + key = "echo" + def func(self): + self.caller.msg(self.args.strip()) +``` + +## MyST directives + +Markdown is easy to read and use. But while it does most of what we need, there are some things it's +not quite as expressive as it needs to be. For this we use extended [MyST][MyST] syntax. This is +on the form + +```` +```{directive} any_options_here + +content + +``` +```` + + +#### Note + +This kind of note may pop more than doing a `> Note: ...`. + +```` +```{note} + +This is some noteworthy content that stretches over more than one line to show how the content indents. +Also the important/warning notes indents like this. + +``` +```` + +```{note} + +This is some noteworthy content that stretches over more than one line to show how the content indents. +Also the important/warning notes indents like this. + +``` + +### Important + +This is for particularly important and visible notes. + +```` +```{important} + This is important because it is! +``` + +```` +```{important} + This is important because it is! +``` + +### Warning + +A warning block is used to draw attention to particularly dangerous things, or features easy to +mess up. + +```` +```{warning} + Be careful about this ... +``` +```` + +```{warning} + Be careful about this ... +``` + +### Version changes and deprecations + +These will show up as one-line warnings that suggest an added, changed or deprecated +feature beginning with particular version. + +```` +```{versionadded} 1.0 +``` +```` + +```{versionadded} 1.0 +``` + +```` +```{versionchanged} 1.0 + How the feature changed with this version. +``` +```` + +```{versionchanged} 1.0 + How the feature changed with this version. +``` + +```` +```{deprecated} 1.0 +``` +```` + +```{deprecated} 1.0 +``` + +### Sidebar + +This will display an informative sidebar that floats to the side of regular content. This is useful +for example to remind the reader of some concept relevant to the text. + +```` +```{sidebar} Things to remember + +- There can be bullet lists +- in here. + +Separate sections with + +an empty line. +``` +```` + +```{sidebar} Things to remember + +- There can be bullet lists +- in here. + +Separate sections with + +an empty line. +``` + +Hint: If wanting to make sure to have the next header appear on a row of its own (rather than +squeezed to the left of the sidebar), one can embed a plain HTML string in the markdown like so: + +```html +
+``` + +
+ +### A more flexible code block + +The regular Markdown Python codeblock is usually enough but for more direct control over the style, one +can also use the `{code-block}` directive that takes a set of additional `:options:`: + +```` +```{code-block} python +:linenos: +:emphasize-lines: 1-2,8 +:caption: An example code block +:name: A full code block example + +from evennia import Command +class CmdEcho(Command): + """ + Usage: echo + """ + key = "echo" + def func(self): + self.caller.msg(self.args.strip()) +``` +```` + +```{code-block} python +:linenos: +:emphasize-lines: 1-2,8 +:caption: An example code block +:name: A full code block example + +from evennia import Command +class CmdEcho(Command): + """ + Usage: echo + """ + key = "echo" + def func(self): + self.caller.msg(self.args.strip()) +``` +Here, `:linenos:` turns on line-numbers and `:emphasize-lines:` allows for emphasizing certain lines +in a different color. The `:caption:` shows an instructive text and `:name:` is used to reference +this +block through the link that will appear (so it should be unique for a given document). + + + +### eval-rst directive + +As a last resort, we can also fall back to writing [ReST][ReST] directives directly: + + +```` +```{eval-rst} + + This will be evaluated as ReST. + All content must be indented. + +``` +```` + +Within a ReST block, one must use Restructured Text syntax, which is not the +same as Markdown. + +- Single backticks around text makes it _italic_. +- Double backticks around text makes it `verbatim`. +- A link is written within back-ticks, with an underscore at the end: + + `python `_ + +[Here is a ReST formatting cheat sheet](https://thomas-cokelaer.info/tutorials/sphinx/rest_syntax.html). + +## Code docstrings + +The source code docstrings will be parsed as Markdown. When writing a module docstring, you can use Markdown formatting, +including header levels down to 4th level (`#### SubSubSubHeader`). After the module documentation it's +a good idea to end with four dashes `----`. This will create a visible line between the documentation and the +class/function docs to follow. + +All non-private classes, methods and functions must have a Google-style docstring, as per the +[Evennia coding style guidelines][github:evennia/CODING_STYLE.md]. This will then be correctly formatted +into pretty api docs. + +## Technical + +Evennia leverages [Sphinx][sphinx] with the [MyST][MyST] extension, which allows us +to write our docs in light-weight Markdown (more specifically [CommonMark][commonmark], like on github) +rather than Sphinx' normal ReST syntax. The `MyST` parser allows for some extra syntax to +make us able to express more complex displays than plain Markdown can. + +For [autodoc-generation][sphinx-autodoc] generation, we use the sphinx-[napoleon][sphinx-napoleon] +extension to understand our friendly Google-style docstrings used in classes and functions etc. + +# Building the docs locally The sources in `evennia/docs/source/` are built into a documentation using the [Sphinx][sphinx] static generator system. To do this locally you need to use a @@ -82,23 +555,23 @@ initialize a new game with a default database (you don't need to have any server running) - It's recommended that you use a virtualenv. Install your cloned version of Evennia into -by pointing to the repo folder (the one containing `/docs`): + by pointing to the repo folder (the one containing `/docs`): ``` - pip install -e evennia + pip install -e evennia ``` - + - Make sure you are in the parent folder _containing_ your `evennia/` repo (so _two_ levels up from `evennia/docs/`). - Create a new game folder called exactly `gamedir` at the same level as your `evennia` -repo with + repo with ``` evennia --init gamedir ``` -- Then `cd` into it and create a new, empty database. You don't need to start the -game or do any further changes after this. +- Then `cd` into it and create a new, empty database. You don't need to start the + game or do any further changes after this. ``` evennia migrate @@ -141,7 +614,7 @@ well. We won't touch that.) If you for some reason want to use another location of your `gamedir/`, or want it named something else (maybe you already use the name 'gamedir' for your development ...), you can do so by setting the `EVGAMEDIR` environment variable to the absolute path -of your alternative game dir. For example (bash): +of your alternative game dir. For example: ``` EVGAMEDIR=/my/path/to/mygamedir make local @@ -156,7 +629,7 @@ specific official Evennia branches will be built, so you can't use this to build your own testing branch. - All local changes must have been committed to git first, since the versioned -docs are built by looking at the git tree. + docs are built by looking at the git tree. - To build for local checking, run (`mv` stands for "multi-version"): ``` @@ -188,485 +661,14 @@ on `github`. So there is no risk of you releasing your local changes accidentall After deployment finishes, the updated live documentation will be available at https://evennia.github.io/evennia/latest/. -# Editing syntax - -The format used for Evennia's docs is [Markdown][commonmark-help] (Commonmark). While markdown -supports a -few alternative forms for some of these, we try to stick to the below forms for consistency. - -### Italic/Bold - -We generally use underscores for italics and double-asterisks for bold: - -- `_Italic text_` - _Italic text_ -- `**Bold Text**` - **Bold text** - -### Headings - -We use `#` to indicate sections/headings. The more `#` the more of a sub-heading it is (will get -smaller and smaller font). - -- `# Heading` -- `## SubHeading` -- `### SubSubHeading` -- `#### SubSubSubHeading` - -> Don't use the same heading/subheading name more than once in one page. While Markdown -does not prevent it, it will make it impossible to refer to that heading uniquely. -The Evennia documentation preparser will detect this and give you an error. - -### Lists - -One can create both bullet-point lists and numbered lists: - -``` -- first bulletpoint -- second bulletpoint -- third bulletpoint -``` - -- first bulletpoint -- second bulletpoint -- third bulletpoint - -``` -1. Numbered point one -2. Numbered point two -3. Numbered point three -``` - -1. Numbered point one -2. Numbered point two -3. Numbered point three - -### Blockquotes - -A blockquote will create an indented block. It's useful for emphasis and is -added by starting one or more lines with `>`. For 'notes' you can also use -an explicit [Note](#Note). - -``` -> This is an important -> thing to remember. -``` - -> Note: This is an important -> thing to remember. - -### Links - -- `[linktext](url_or_ref)` - gives a clickable link [linktext][linkdemo]. - -The `url_or_ref` can either be a full `http://...` url or an internal _reference_. For example, use -`[my document](My-Document)` to link to the document `evennia/docs/source/My-Document.md`. Avoid -using -full `http://` linking unless really referring to an external resource. - -- `[linktext](ref#heading-name)` - -You can point to sub-sections (headings) in a document by using a single `#` and the name of the -heading, replacing spaces with dashes. So to refer to a heading `## Cool Stuff` inside `My-Document` -would be a link `[cool stuff](My-Document#Cool-Stuff)`. This is why headings must be uniquely named -within on document. - -- `[linktext][linkref]` - refer to a reference defined later in the document. - -Urls can get long and if you are using the same url in many places it can get a little cluttered. So -you can also put the url as a 'footnote' at the end of your document -and refer to it by putting your reference within square brackets `[ ]`. Here's an example: - -``` -This is a [clickable link][mylink]. This is [another link][1]. - -... - - -[mylink]: http://... -[1]: My-Document - -``` - -#### Special references - -The Evennia documentation supports some special reference shortcuts in links: - -##### Github online repository - -- `github:` - a shortcut for the full path to the Evennia repository on github. This must be given -with forward-slashes and include the `.py` file ending. It will refer to the `master` branch by default: - - [link to objects.py](github:evennia/objects/objects.py) - - This will remap to https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py. -- To refer to the `develop` branch, start the url with `develop/`: - - [link to objects.py](github:develop/evennia/objects/objects.py) - -##### API - -- `api:` - references a path in the api documentation. This is specified as a Python-path: - - [link to api for objects.py](api:evennia.objects) - - This will create a link to the auto-generated `evennia/source/api/evennia.objects.rst` document. - - Since api-docs are generated alongside the documentation, this will always be the api docs for - the current version/branch of the docs. - -##### Bug reports/feature request - - -- `github:issue` - links to the github issue selection page, where the user can choose which type of - issue to create. - - If you find a problem, make a [bug report](github:issue)! - - This will generate a link to https://github.com/evennia/evennia/issues/new/choose. - -### Verbatim text - -It's common to want to mark something to be displayed verbatim - just as written - without any -Markdown parsing. In running text, this is done using backticks (\`), like \`verbatim text\` becomes -`verbatim text`. - -If you want to put the verbatim text on its own line, you can do so easily by simply indenting -it 4 spaces (add empty lines on each side for readability too): - -``` -This is normal text - - This is verbatim text - -This is normal text -``` - -Another way is to use triple-backticks: - -```` -``` -Everything within these backticks will be verbatim. - -``` -```` - -### Code blocks - -A special case is code examples - we want them to get code-highlighting for readability. This is -done by using -the triple-backticks and specify which language we use: - -```` -```python - -def a_python_func(x): - return x * x - -``` -```` - -```python - -def a_python_func(x): - return x * x - -``` - -### ReST blocks - -Markdown is easy to read and use. But while it does most of what we need, there are some things it's -not quite as expressive as it needs to be. For this we need to fall back to the [ReST][ReST] markup -language which the documentation system uses under the hood. This is done by specifying `eval_rst` -as the name of the `language` of a literal block: - -```` -```eval_rst - - This will be evaluated as ReST. - All content must be indented. - -``` -```` - -There is also a short-hand form for starting a [ReST directive][ReST-directives] without need for -`eval_rst`: - -```` -```directive:: possible-option - - Content *must* be indented for it to be included in the directive. - - New lines are ignored, empty lines starts a new paragraph. -``` -```` - -Within a ReST block, one must use Restructured Text syntax, which is not the same as Markdown. - -- Single backticks around text makes it _italic_. -- Double backticks around text makes it `verbatim`. -- A link is written within back-ticks, with an underscore at the end: - - `python `_ - -[Here is a ReST formatting cheat sheet](https://thomas-cokelaer.info/tutorials/sphinx/rest_syntax.html). - -Below are examples of ReST-block structures. - -#### Note - -This kind of note may pop more than doing a `> Note: ...`. Contrary to a -[blockquote](#Blockquotes), the end result will not be indented. - -```` -```note:: - - Remember that you have to indent this content for it to be part of the note. - -``` -```` - - -```note:: - - Remember that you have to indent this content for it to be part of the note. - -``` - -#### Important - -This is for particularly important and visible notes. - -```` -```important:: - This is important because it is! -``` - -```` -```important:: - This is important because it is! -``` - -#### Warning - -A warning block is used to draw attention to particularly dangerous things, or features easy to -mess up. - -```` -```warning:: - Be careful about this ... -``` -```` - -```warning:: - Be careful about this ... -``` - -#### Version changes and deprecations - -These will show up as one-line warnings that suggest an added, changed or deprecated -feature beginning with particular version. - -```` -```versionadded:: 1.0 -``` -```` - -```versionadded:: 1.0 -``` - -```` -```versionchanged:: 1.0 - How the feature changed with this version. -``` -```` - -```versionchanged:: 1.0 - How the feature changed with this version. -``` - -```` -```deprecated:: 1.0 -``` -```` - -```deprecated:: 1.0 -``` - -#### Sidebar - -This will display an informative sidebar that floats to the side of regular content. This is useful -for example to remind the reader of some concept relevant to the text. - -```` -```sidebar:: Things to remember - - - There can be bullet lists - - in here. - - Headers: - with indented blocks like this - Will end up: - as full sub-headings in the sidebar. -``` -```` - -```sidebar:: Things to remember - - - There can be bullet lists - - in here. - - Headers: - with indented blocks like this - Will end up: - as full sub-headings in the sidebar. -``` -Remember that for ReST-directives, the content within the triple-backticks _must_ be indented to -some degree or the content will just appear outside of the directive as regular text. - -If wanting to make sure to have the next header appear on a row of its own, one can embed -a plain HTML string in the markdown like so: - -```html -
-``` - -
- -#### Tables - -A table is specified using [ReST table syntax][ReST-tables] (they don't need to be indented): -```` -```eval_rst - -===== ===== ======= -A B A and B -===== ===== ======= -False False False -True False False -False True False -True True True -===== ===== ======= -``` -```` - -```eval_rst - -===== ===== ======= -A B A and B -===== ===== ======= -False False False -True False False -False True False -True True True -===== ===== ======= -``` - -or the more flexible but verbose - -```` -```eval_rst -+------------------------+------------+----------+----------+ -| Header row, column 3 | Header 2 | Header 3 | Header 4 | -| (header rows optional) | | | | -+========================+============+==========+==========+ -| body row 1, column 1 | column 2 | column 3 | column 4 | -+------------------------+------------+----------+----------+ -| body row 2 | ... | ... | | -+------------------------+------------+----------+----------+ -``` -```` - -```eval_rst -+------------------------+------------+----------+----------+ -| Header row, column 3 | Header 2 | Header 3 | Header 4 | -| (header rows optional) | | | | -+========================+============+==========+==========+ -| body row 1, column 1 | column 2 | column 3 | column 4 | -+------------------------+------------+----------+----------+ -| body row 2 | ... | ... | | -+------------------------+------------+----------+----------+ -``` - -#### A more flexible code block - -The regular Markdown Python codeblock is usually enough but for more direct control over the style, one -can also specify the code block explicitly in `ReST` for more flexibility. -It also provides a link to the code block, identified by its name. - - -```` -```code-block:: python - :linenos: - :emphasize-lines: 1-2,8 - :caption: An example code block - :name: A full code block example - - from evennia import Command - class CmdEcho(Command): - """ - Usage: echo - """ - key = "echo" - def func(self): - self.caller.msg(self.args.strip()) -``` -```` - -```code-block:: python - :linenos: - :emphasize-lines: 1-2,8 - :caption: An example code block - :name: A full code block example - - from evennia import Command - class CmdEcho(Command): - """ - Usage: echo - """ - key = "echo" - def func(self): - self.caller.msg(self.args.strip()) -``` -Here, `:linenos:` turns on line-numbers and `:emphasize-lines:` allows for emphasizing certain lines -in a different color. The `:caption:` shows an instructive text and `:name:` is used to reference -this -block through the link that will appear (so it should be unique for a give document). - -> The default markdown syntax will actually generate a code-block ReST instruction like this -> automatically for us behind the scenes. But the automatic generation can't know things like emphasize- -lines or captions since that's not a part of the Markdown specification. - -## Code documentation - -The source code docstrings will be parsed as Markdown. When writing a module docstring, you can use Markdown formatting, -including header levels down to 4th level (`#### SubSubSubHeader`). After the module documentation it's -a good idea to end with four dashes `----`. This will create a visible line between the documentation and the -class/function docs to follow. See for example [the Traits docs](api:evennia.contrib.traits). - -All non-private classes, methods and functions must have a Google-style docstring, as per the -[Evennia coding style guidelines](github:evennia/CODING_STYLE.md). This will then be correctly formatted -into pretty api docs. - -## Technical - -Evennia leverages [Sphinx][sphinx] with the [recommonmark][recommonmark] extension, which allows us -to write our -docs in light-weight Markdown (more specifically [CommonMark][commonmark], like on github) rather -than ReST. -The recommonmark extension however also allows us to use ReST selectively in the places were it is -more -expressive than the simpler (but much easier) Markdown. - -For [autodoc-generation][sphinx-autodoc] generation, we use the sphinx-[napoleon][sphinx-napoleon] -extension -to understand our friendly Google-style docstrings used in classes and functions etc. [sphinx]: https://www.sphinx-doc.org/en/master/ -[recommonmark]: https://recommonmark.readthedocs.io/en/latest/index.html +[MyST]: https://myst-parser.readthedocs.io/en/latest/syntax/reference.html [commonmark]: https://spec.commonmark.org/current/ [commonmark-help]: https://commonmark.org/help/ -[sphinx-autodoc]: http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc -[sphinx-napoleon]: http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html +[sphinx-autodoc]: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc +[sphinx-napoleon]: https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html [getting-started]: Setup/Setup-Quickstart [contributing]: ./Contributing [ReST]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html @@ -676,4 +678,4 @@ to understand our friendly Google-style docstrings used in classes and functions [linkdemo]: #Links [retext]: https://github.com/retext-project/retext [grip]: https://github.com/joeyespo/grip -[pycharm]: https://www.jetbrains.com/pycharm/ +[pycharm]: https://www.jetbrains.com/pycharm/ \ No newline at end of file diff --git a/docs/source/Contributing.md b/docs/source/Contributing.md index a26229fa42..e49f033c4d 100644 --- a/docs/source/Contributing.md +++ b/docs/source/Contributing.md @@ -90,9 +90,9 @@ a new `README.md` file within that directory. amount of game-style-specific code. Assume your code will be applied to a very different game than you had in mind when creating it. * To make the licensing situation clear we assume all contributions are released with the same -[license as Evennia](./Licensing). If this is not possible for some reason, talk to us and we'll +[license as Evennia](./Licensing.md). If this is not possible for some reason, talk to us and we'll handle it on a case-by-case basis. -* Your contribution must be covered by [unit tests](./Unit-Testing). Having unit tests will both help +* Your contribution must be covered by [unit tests](./Unit-Testing.md). Having unit tests will both help make your code more stable and make sure small changes does not break it without it being noticed, it will also help us test its functionality and merge it quicker. If your contribution is a single module, you can add your unit tests to `evennia/contribs/tests.py`. If your contribution is bigger diff --git a/docs/source/Coordinates.md b/docs/source/Coordinates.md index beed340e48..5c5653ffdd 100644 --- a/docs/source/Coordinates.md +++ b/docs/source/Coordinates.md @@ -23,7 +23,7 @@ instance. ## Coordinates as tags The first concept might be the most surprising at first glance: we will create coordinates as -[tags](./Tags). +[tags](./Tags.md). > Why not attributes, wouldn't that be easier? diff --git a/docs/source/Custom-Protocols.md b/docs/source/Custom-Protocols.md index 805e7c4f0c..93d17ca218 100644 --- a/docs/source/Custom-Protocols.md +++ b/docs/source/Custom-Protocols.md @@ -5,9 +5,9 @@ their own custom client protocol.* -A [PortalSession](./Sessions#Portal-and-Server-Sessions) is the basic data object representing an +A [PortalSession](./Sessions.md#portal-and-server-sessions) is the basic data object representing an external -connection to the Evennia [Portal](./Portal-And-Server) -- usually a human player running a mud client +connection to the Evennia [Portal](./Portal-And-Server.md) -- usually a human player running a mud client of some kind. The way they connect (the language the player's client and Evennia use to talk to each other) is called the connection *Protocol*. The most common such protocol for MUD:s is the *Telnet* protocol. All Portal Sessions are stored and managed by the Portal's *sessionhandler*. @@ -27,7 +27,7 @@ You <-> InputFunc ``` -(See the [Message Path](./Messagepath) for the bigger picture of how data flows through Evennia). The +(See the [Message Path](./Messagepath.md) for the bigger picture of how data flows through Evennia). The parts that needs to be customized to make your own custom protocol is the `Protocol + PortalSession` (which translates between data coming in/out over the wire to/from Evennia internal representation) as well as the `InputFunc` (which handles incoming data). @@ -219,11 +219,11 @@ in our case means sending "foo" across the network. ### Receiving data Just because the protocol is there, does not mean Evennia knows what to do with it. An -[Inputfunc](./Inputfuncs) must exist to receive it. In the case of the `text` input exemplified above, +[Inputfunc](./Inputfuncs.md) must exist to receive it. In the case of the `text` input exemplified above, Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So handle that you need to simply add a cmdset with commands on your receiving Session (and/or the Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the -[Inputfunc](./Inputfuncs) page for how to do this. +[Inputfunc](./Inputfuncs.md) page for how to do this. These might not be as clear-cut in all protocols, but the principle is there. These four basic components - however they are accessed - links to the *Portal Session*, which is the actual common diff --git a/docs/source/Customize-channels.md b/docs/source/Customize-channels.md index 9c755eec4e..67bc381750 100644 --- a/docs/source/Customize-channels.md +++ b/docs/source/Customize-channels.md @@ -106,7 +106,7 @@ Evennia is smart enough to understand that when we type `+something`, `+` is the `something` is the command argument. This will, of course, fail if you have a command beginning by `+` conflicting with the `CmdConnect` key. 4. We have altered some class attributes, like `auto_help`. If you want to know what they do and -why they have changed here, you can check the [documentation on commands](./Commands). +why they have changed here, you can check the [documentation on commands](./Commands.md). 5. In the command body, we begin by extracting the channel name. Remember that this name should be in the command arguments (that is, in `self.args`). Following the same example, if a player enters `+something`, `self.args` should contain `"something"`. We use `search_channel` to see if this diff --git a/docs/source/Default-Command-Help.md b/docs/source/Default-Command-Help.md deleted file mode 100644 index 42601a0a69..0000000000 --- a/docs/source/Default-Command-Help.md +++ /dev/null @@ -1,2538 +0,0 @@ -# Default Command Help - - -> *This page is auto-generated. Do not modify - your changes will be lost. Report problems to the -[issue tracker](https://github.com/evennia/evennia/issues).* - -The full set of default Evennia commands currently contains 92 commands in 9 source -files. Our policy for adding default commands is outlined [here](./Using-MUX-as-a-Standard). More -information about how commands work can be found in the documentation for [Commands](./Commands). - - - -## A-Z - -- [`__unloggedin_look_command`](https://github.com/evennia/evennia/wiki/Default-Command-Help#wiki-`--unloggedin-look-command`-cmdunconnectedlook) - look when in unlogged-in state -- [about](./Default-Command-Help#wiki-about-cmdabout) - show Evennia info -- [access](./Default-Command-Help#wiki-access-cmdaccess) - show your current game access -- [addcom](./Default-Command-Help#wiki-addcom-cmdaddcom) - add a channel alias and/or subscribe to a -channel -- [alias](./Default-Command-Help#wiki-alias-cmdsetobjalias) - adding permanent aliases for object -- [allcom](./Default-Command-Help#wiki-allcom-cmdallcom) - perform admin operations on all channels -- [ban](./Default-Command-Help#wiki-ban-cmdban) - ban an account from the server -- [batchcode](./Default-Command-Help#wiki-batchcode-cmdbatchcode) - build from batch-code file -- [batchcommands](./Default-Command-Help#wiki-batchcommands-cmdbatchcommands) - build from batch- -command file -- [boot](./Default-Command-Help#wiki-boot-cmdboot) - kick an account from the server. -- [cboot](./Default-Command-Help#wiki-cboot-cmdcboot) - kick an account from a channel you control -- [ccreate](./Default-Command-Help#wiki-ccreate-cmdchannelcreate) - create a new channel -- [cdesc](./Default-Command-Help#wiki-cdesc-cmdcdesc) - describe a channel you control -- [cdestroy](./Default-Command-Help#wiki-cdestroy-cmdcdestroy) - destroy a channel you created -- [cemit](./Default-Command-Help#wiki-cemit-cmdcemit) - send an admin message to a channel you control -- [channels](./Default-Command-Help#wiki-channels-cmdchannels) - list all channels available to you -- [charcreate](./Default-Command-Help#wiki-charcreate-cmdcharcreate) - create a new character -- [chardelete](./Default-Command-Help#wiki-chardelete-cmdchardelete) - delete a character - this -cannot be undone! -- [clock](./Default-Command-Help#wiki-clock-cmdclock) - change channel locks of a channel you control -- [cmdsets](./Default-Command-Help#wiki-cmdsets-cmdlistcmdsets) - list command sets defined on an -object -- [color](./Default-Command-Help#wiki-color-cmdcolortest) - testing which colors your client support -- [command](./Default-Command-Help#wiki-command-objmanipcommand) - This is a parent class for some of -the defining objmanip commands -- [connect](./Default-Command-Help#wiki-connect-cmdunconnectedconnect) - connect to the game -- [copy](./Default-Command-Help#wiki-copy-cmdcopy) - copy an object and its properties -- [cpattr](./Default-Command-Help#wiki-cpattr-cmdcpattr) - copy attributes between objects -- [create](./Default-Command-Help#wiki-create-cmdunconnectedcreate) - create a new account account -- [create](./Default-Command-Help#wiki-create-cmdcreate) - create new objects -- [cwho](./Default-Command-Help#wiki-cwho-cmdcwho) - show who is listening to a channel -- [delcom](./Default-Command-Help#wiki-delcom-cmddelcom) - remove a channel alias and/or unsubscribe -from channel -- [desc](./Default-Command-Help#wiki-desc-cmddesc) - describe an object or the current room. -- [destroy](./Default-Command-Help#wiki-destroy-cmddestroy) - permanently delete objects -- [dig](./Default-Command-Help#wiki-dig-cmddig) - build new rooms and connect them to the current -location -- [drop](./Default-Command-Help#wiki-drop-cmddrop) - drop something -- [emit](./Default-Command-Help#wiki-emit-cmdemit) - admin command for emitting message to multiple -objects -- [examine](./Default-Command-Help#wiki-examine-cmdexamine) - get detailed information about an object -- [find](./Default-Command-Help#wiki-find-cmdfind) - search the database for objects -- [force](./Default-Command-Help#wiki-force-cmdforce) - forces an object to execute a command -- [get](./Default-Command-Help#wiki-get-cmdget) - pick up something -- [give](./Default-Command-Help#wiki-give-cmdgive) - give away something to someone -- [help](./Default-Command-Help#wiki-help-cmdunconnectedhelp) - get help when in unconnected-in state -- [help](./Default-Command-Help#wiki-help-cmdhelp) - View help or a list of topics -- [home](./Default-Command-Help#wiki-home-cmdhome) - move to your character's home location -- [ic](./Default-Command-Help#wiki-ic-cmdic) - control an object you have permission to puppet -- [inventory](./Default-Command-Help#wiki-inventory-cmdinventory) - view inventory -- [irc2chan](./Default-Command-Help#wiki-irc2chan-cmdirc2chan) - Link an evennia channel to an -external IRC channel -- [link](./Default-Command-Help#wiki-link-cmdlink) - link existing rooms together with exits -- [lock](./Default-Command-Help#wiki-lock-cmdlock) - assign a lock definition to an object -- [look](./Default-Command-Help#wiki-look-cmdlook) - look at location or object -- [look](./Default-Command-Help#wiki-look-cmdooclook) - look while out-of-character -- [mvattr](./Default-Command-Help#wiki-mvattr-cmdmvattr) - move attributes between objects -- [name](./Default-Command-Help#wiki-name-cmdname) - change the name and/or aliases of an object -- [nick](./Default-Command-Help#wiki-nick-cmdnick) - define a personal alias/nick by defining a string -to -- [objects](./Default-Command-Help#wiki-objects-cmdobjects) - statistics on objects in the database -- [ooc](./Default-Command-Help#wiki-ooc-cmdooc) - stop puppeting and go ooc -- [open](./Default-Command-Help#wiki-open-cmdopen) - open a new exit from the current room -- [option](./Default-Command-Help#wiki-option-cmdoption) - Set an account option -- [page](./Default-Command-Help#wiki-page-cmdpage) - send a private message to another account -- [password](./Default-Command-Help#wiki-password-cmdpassword) - change your password -- [perm](./Default-Command-Help#wiki-perm-cmdperm) - set the permissions of an account/object -- [pose](./Default-Command-Help#wiki-pose-cmdpose) - strike a pose -- [py](./Default-Command-Help#wiki-py-cmdpy) - execute a snippet of python code -- [quell](./Default-Command-Help#wiki-quell-cmdquell) - use character's permissions instead of -account's -- [quit](./Default-Command-Help#wiki-quit-cmdunconnectedquit) - quit when in unlogged-in state -- [quit](./Default-Command-Help#wiki-quit-cmdquit) - quit the game -- [reload](./Default-Command-Help#wiki-reload-cmdreload) - reload the server -- [reset](./Default-Command-Help#wiki-reset-cmdreset) - reset and reboot the server -- [rss2chan](./Default-Command-Help#wiki-rss2chan-cmdrss2chan) - link an evennia channel to an -external RSS feed -- [say](./Default-Command-Help#wiki-say-cmdsay) - speak as your character -- [script](./Default-Command-Help#wiki-script-cmdscript) - attach a script to an object -- [scripts](./Default-Command-Help#wiki-scripts-cmdscripts) - list and manage all running scripts -- [server](./Default-Command-Help#wiki-server-cmdserverload) - show server load and memory statistics -- [service](./Default-Command-Help#wiki-service-cmdservice) - manage system services -- [sessions](./Default-Command-Help#wiki-sessions-cmdsessions) - check your connected session(s) -- [set](./Default-Command-Help#wiki-set-cmdsetattribute) - set attribute on an object or account -- [setdesc](./Default-Command-Help#wiki-setdesc-cmdsetdesc) - describe yourself -- [sethelp](./Default-Command-Help#wiki-sethelp-cmdsethelp) - Edit the help database. -- [sethome](./Default-Command-Help#wiki-sethome-cmdsethome) - set an object's home location -- [shutdown](./Default-Command-Help#wiki-shutdown-cmdshutdown) - stop the server completely -- [spawn](./Default-Command-Help#wiki-spawn-cmdspawn) - spawn objects from prototype -- [style](./Default-Command-Help#wiki-style-cmdstyle) - In-game style options -- [tag](./Default-Command-Help#wiki-tag-cmdtag) - handles the tags of an object -- [tel](./Default-Command-Help#wiki-tel-cmdteleport) - teleport object to another location -- [time](./Default-Command-Help#wiki-time-cmdtime) - show server time statistics -- [tunnel](./Default-Command-Help#wiki-tunnel-cmdtunnel) - create new rooms in cardinal directions -only -- [typeclass](./Default-Command-Help#wiki-typeclass-cmdtypeclass) - set or change an object's -typeclass -- [unban](./Default-Command-Help#wiki-unban-cmdunban) - remove a ban from an account -- [unlink](./Default-Command-Help#wiki-unlink-cmdunlink) - remove exit-connections between rooms -- [userpassword](./Default-Command-Help#wiki-userpassword-cmdnewpassword) - change the password of an -account -- [wall](./Default-Command-Help#wiki-wall-cmdwall) - make an announcement to all -- [whisper](./Default-Command-Help#wiki-whisper-cmdwhisper) - Speak privately as your character to -another -- [who](./Default-Command-Help#wiki-who-cmdwho) - list who is currently online -- [wipe](./Default-Command-Help#wiki-wipe-cmdwipe) - clear all attributes from an object - -## A-Z by source file - -- [account.py](./Default-Command-Help#accountpy) -- [admin.py](./Default-Command-Help#adminpy) -- [batchprocess.py](./Default-Command-Help#batchprocesspy) -- [building.py](./Default-Command-Help#buildingpy) -- [comms.py](./Default-Command-Help#commspy) -- [general.py](./Default-Command-Help#generalpy) -- [help.py](./Default-Command-Help#helppy) -- [system.py](./Default-Command-Help#systempy) -- [unloggedin.py](./Default-Command-Help#unloggedinpy) - -## Command details - -These are generated from the auto-documentation and are ordered by their source file location in -[evennia/commands/default/](https://github.com/evennia/evennia/tree/master/evennia/commands/default/) - - -### `account.py` - -[View account.py source](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py) - - -#### charcreate (CmdCharCreate) -``` - create a new character - - Usage: - charcreate [= desc] - - Create a new character, optionally giving it a description. You - may use upper-case letters in the name - you will nevertheless - always be able to access your character using lower-case letters - if you want. -``` -- **key:** *charcreate* -- **aliases:** -- **[locks](./Locks):** *"cmd:pperm(Player)"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdCharCreate` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### chardelete (CmdCharDelete) -``` - delete a character - this cannot be undone! - - Usage: - chardelete - - Permanently deletes one of your characters. -``` -- **key:** *chardelete* -- **aliases:** -- **[locks](./Locks):** *"cmd:pperm(Player)"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdCharDelete` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### color (CmdColorTest) -``` - testing which colors your client support - - Usage: - color ansi||xterm256 - - Prints a color map along with in-mud color codes to use to produce - them. It also tests what is supported in your client. Choices are - 16-color ansi (supported in most muds) or the 256-color xterm256 - standard. No checking is done to determine your client supports - color - if not you will see rubbish appear. -``` -- **key:** *color* -- **aliases:** -- **[locks](./Locks):** *"cmd:all()"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdColorTest` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### ic (CmdIC) -``` - control an object you have permission to puppet - - Usage: - ic - - Go in-character (IC) as a given Character. - - This will attempt to "become" a different object assuming you have - the right to do so. Note that it's the ACCOUNT character that puppets - characters/objects and which needs to have the correct permission! - - You cannot become an object that is already controlled by another - account. In principle can be any in-game object as long - as you the account have access right to puppet it. -``` -- **key:** *ic* -- **aliases:** *puppet* -- **[locks](./Locks):** *"cmd:all()"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdIC` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### look (CmdOOCLook) -``` - look while out-of-character - - Usage: - look - - Look in the ooc state. -``` -- **key:** *look* -- **aliases:** *l*, *ls* -- **[locks](./Locks):** *"cmd:all()"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdOOCLook` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### ooc (CmdOOC) -``` - stop puppeting and go ooc - - Usage: - ooc - - Go out-of-character (OOC). - - This will leave your current character and put you in a incorporeal OOC state. -``` -- **key:** *ooc* -- **aliases:** *unpuppet* -- **[locks](./Locks):** *"cmd:pperm(Player)"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdOOC` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### option (CmdOption) -``` - Set an account option - - Usage: - option[/save] [name = value] - - Switches: - save - Save the current option settings for future logins. - clear - Clear the saved options. - - This command allows for viewing and setting client interface - settings. Note that saved options may not be able to be used if - later connecting with a client with different capabilities. -``` -- **key:** *option* -- **aliases:** *options* -- **[locks](./Locks):** *"cmd:all()"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdOption` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### password (CmdPassword) -``` - change your password - - Usage: - password = - - Changes your password. Make sure to pick a safe one. -``` -- **key:** *password* -- **aliases:** -- **[locks](./Locks):** *"cmd:pperm(Player)"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdPassword` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### quell (CmdQuell) -``` - use character's permissions instead of account's - - Usage: - quell - unquell - - Normally the permission level of the Account is used when puppeting a - Character/Object to determine access. This command will switch the lock - system to make use of the puppeted Object's permissions instead. This is - useful mainly for testing. - Hierarchical permission quelling only work downwards, thus an Account cannot - use a higher-permission Character to escalate their permission level. - Use the unquell command to revert back to normal operation. -``` -- **key:** *quell* -- **aliases:** *unquell* -- **[locks](./Locks):** *"cmd:pperm(Player)"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdQuell` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### quit (CmdQuit) -``` - quit the game - - Usage: - quit - - Switch: - all - disconnect all connected sessions - - Gracefully disconnect your current session from the - game. Use the /all switch to disconnect from all sessions. -``` -- **key:** *quit* -- **aliases:** -- **[locks](./Locks):** *"cmd:all()"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdQuit` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultAccount'* of class `AccountCmdSet` in [cmdset_account.py](https://gi -thub.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_account.py). - - -#### sessions (CmdSessions) -``` - check your connected session(s) - - Usage: - sessions - - Lists the sessions currently connected to your account. -``` -- **key:** *sessions* -- **aliases:** -- **[locks](./Locks):** *"cmd:all()"* -- **[`help_category`](./Help-System):** *"General"* -- **Source:** class `CmdSessions` in -[account.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/account.py). -Belongs to command set *'DefaultSession'* of class `SessionCmdSet` in [cmdset_session.py](https://github.com/evennia/evennia/tree/master/evennia/commands/default/cmdset_session.py). - - -#### style (CmdStyle) -``` - In-game style options - - Usage: - style - style