From f47beb675154c75378b8b96998e8d9eacbc79e55 Mon Sep 17 00:00:00 2001 From: ChrisLR Date: Mon, 6 Jun 2022 16:23:36 -0400 Subject: [PATCH 01/26] Changed parser to escape properly using alternating " or ' --- evennia/utils/funcparser.py | 61 +++++++++++++++++++------- evennia/utils/tests/test_funcparser.py | 46 +++++++++++-------- 2 files changed, 72 insertions(+), 35 deletions(-) diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index c3455f6774..54fc64b0c8 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -84,8 +84,8 @@ class _ParsedFunc: # state storage fullstr: str = "" infuncstr: str = "" - single_quoted: bool = False - double_quoted: bool = False + single_quoted: int = -1 + double_quoted: int = -1 current_kwarg: str = "" open_lparens: int = 0 open_lsquate: int = 0 @@ -318,8 +318,8 @@ class FuncParser: # parsing state callstack = [] - single_quoted = False - double_quoted = False + single_quoted = -1 + double_quoted = -1 open_lparens = 0 # open ( open_lsquare = 0 # open [ open_lcurly = 0 # open { @@ -330,6 +330,7 @@ class FuncParser: curr_func = None fullstr = "" # final string infuncstr = "" # string parts inside the current level of $funcdef (including $) + literal_infuncstr = False for char in string: @@ -373,12 +374,13 @@ class FuncParser: curr_func.open_lcurly = open_lcurly current_kwarg = "" infuncstr = "" - single_quoted = False - double_quoted = False + single_quoted = -1 + double_quoted = -1 open_lparens = 0 open_lsquare = 0 open_lcurly = 0 exec_return = "" + literal_infuncstr = False callstack.append(curr_func) # start a new func @@ -401,19 +403,41 @@ class FuncParser: infuncstr += str(exec_return) exec_return = "" - if char == "'": # note that this is the same as "\'" + if char == "'" and double_quoted < 0: # note that this is the same as "\'" # a single quote - flip status - single_quoted = not single_quoted - infuncstr += char + if single_quoted == 0: + infuncstr = infuncstr[1:] + single_quoted = -1 + elif single_quoted > 0: + prefix = infuncstr[0:single_quoted] + infuncstr = prefix + infuncstr[single_quoted+1:] + single_quoted = -1 + else: + infuncstr += char + infuncstr = infuncstr.strip() + single_quoted = len(infuncstr) - 1 + literal_infuncstr = True + continue - if char == '"': # note that this is the same as '\"' + if char == '"' and single_quoted < 0: # note that this is the same as '\"' # a double quote = flip status - double_quoted = not double_quoted - infuncstr += char + if double_quoted == 0: + infuncstr = infuncstr[1:] + double_quoted = -1 + elif double_quoted > 0: + prefix = infuncstr[0:double_quoted] + infuncstr = prefix + infuncstr[double_quoted + 1:] + double_quoted = -1 + else: + infuncstr += char + infuncstr = infuncstr.strip() + double_quoted = len(infuncstr) - 1 + literal_infuncstr = True + continue - if double_quoted or single_quoted: + if double_quoted >= 0 or single_quoted >= 0: # inside a string definition - this escapes everything else infuncstr += char continue @@ -477,12 +501,15 @@ class FuncParser: else: curr_func.args.append(exec_return) else: + if not literal_infuncstr: + infuncstr = infuncstr.strip() + # store a string instead if current_kwarg: - curr_func.kwargs[current_kwarg] = infuncstr.strip() - elif infuncstr.strip(): + curr_func.kwargs[current_kwarg] = infuncstr + elif literal_infuncstr or infuncstr.strip(): # don't store the empty string - curr_func.args.append(infuncstr.strip()) + curr_func.args.append(infuncstr) # note that at this point either exec_return or infuncstr will # be empty. We need to store the full string so we can print @@ -493,6 +520,7 @@ class FuncParser: current_kwarg = "" exec_return = "" infuncstr = "" + literal_infuncstr = False if char == ")": # closing the function list - this means we have a @@ -536,6 +564,7 @@ class FuncParser: if return_str: exec_return = "" infuncstr = "" + literal_infuncstr = False continue infuncstr += char diff --git a/evennia/utils/tests/test_funcparser.py b/evennia/utils/tests/test_funcparser.py index c62928ff53..3f6e6eb2f9 100644 --- a/evennia/utils/tests/test_funcparser.py +++ b/evennia/utils/tests/test_funcparser.py @@ -44,6 +44,7 @@ def _double_callable(*args, **kwargs): def _eval_callable(*args, **kwargs): if args: return simple_eval(args[0]) + return "" @@ -113,25 +114,25 @@ class TestFuncParser(TestCase): ("$foo() Test noargs5", "_test() Test noargs5"), ("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"), ("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"), - ("Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, ' too')"), - ("Test args4 $foo('')", "Test args4 _test('')"), - ('Test args4 $foo("")', 'Test args4 _test("")'), + (r"Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, too)"), + ("Test args4 $foo('')", "Test args4 _test()"), + ('Test args4 $foo("")', 'Test args4 _test()'), ("Test args5 $foo(\(\))", "Test args5 _test(())"), ("Test args6 $foo(\()", "Test args6 _test(()"), ("Test args7 $foo(())", "Test args7 _test(())"), ("Test args8 $foo())", "Test args8 _test())"), ("Test args9 $foo(=)", "Test args9 _test(=)"), ("Test args10 $foo(\,)", "Test args10 _test(,)"), - ("Test args10 $foo(',')", "Test args10 _test(',')"), + ("Test args10 $foo(',')", "Test args10 _test(,)"), ("Test args11 $foo(()", "Test args11 $foo(()"), # invalid syntax ( "Test kwarg1 $bar(foo=1, bar='foo', too=ere)", - "Test kwarg1 _test(foo=1, bar='foo', too=ere)", + "Test kwarg1 _test(foo=1, bar=foo, too=ere)", ), ("Test kwarg2 $bar(foo,bar,too=ere)", "Test kwarg2 _test(foo, bar, too=ere)"), ("test kwarg3 $foo(foo = bar, bar = ere )", "test kwarg3 _test(foo=bar, bar=ere)"), ( - "test kwarg4 $foo(foo =' bar ',\" bar \"= ere )", + r"test kwarg4 $foo(foo =\' bar \',\" bar \"= ere )", "test kwarg4 _test(foo=' bar ', \" bar \"=ere)", ), ( @@ -180,22 +181,24 @@ class TestFuncParser(TestCase): ("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"), ("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"), ("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"), - ("Test eval3 $eval('21' + 'foo' + 'bar')", "Test eval3 21foobar"), - ("Test eval4 $eval('21' + '$repl()' + '' + str(10 // 2))", "Test eval4 21rr5"), - ("Test eval5 $eval('21' + '\$repl()' + '' + str(10 // 2))", "Test eval5 21$repl()5"), - ("Test eval6 $eval('$repl(a)' + '$repl(b)')", "Test eval6 rarrbr"), + ("Test eval3 $eval(\"'21' + 'foo' + 'bar'\")", "Test eval3 21foobar"), + (r"Test eval4 $eval(\'21\' + \'$repl()\' + \"''\" + str(10 // 2))", "Test eval4 21rr5"), + (r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))", "Test eval5 21$repl()5"), + ("Test eval6 $eval(\"'$repl(a)' + '$repl(b)'\")", "Test eval6 rarrbr"), ("Test type1 $typ([1,2,3,4])", "Test type1 "), ("Test type2 $typ((1,2,3,4))", "Test type2 "), ("Test type3 $typ({1,2,3,4})", "Test type3 "), ("Test type4 $typ({1:2,3:4})", "Test type4 "), ("Test type5 $typ(1), $typ(1.0)", "Test type5 , "), - ("Test type6 $typ('1'), $typ(\"1.0\")", "Test type6 , "), + ("Test type6 $typ(\"'1'\"), $typ('\"1.0\"')", "Test type6 , "), ("Test add1 $add(1, 2)", "Test add1 3"), ("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"), ("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"), ("Test literal2 $typ($lit(1))", "Test literal2 "), ("Test literal3 $typ($lit(1)aaa)", "Test literal3 "), ("Test literal4 $typ(aaa$lit(1))", "Test literal4 "), + ("Test spider's thread", "Test spider's thread"), + ] ) def test_parse(self, string, expected): @@ -258,7 +261,11 @@ class TestFuncParser(TestCase): self.assertEqual([1, 2, 3, 4], ret) self.assertTrue(isinstance(ret, list)) - ret = self.parser.parse_to_any("$lit('')") + ret = self.parser.parse_to_any("$lit(\"''\")") + self.assertEqual("", ret) + self.assertTrue(isinstance(ret, str)) + + ret = self.parser.parse_to_any(r"$lit(\'\')") self.assertEqual("", ret) self.assertTrue(isinstance(ret, str)) @@ -390,7 +397,8 @@ class TestDefaultCallables(TestCase): ("Some $rjust(Hello, 30)", "Some Hello"), ("Some $rjust(Hello, width=30)", "Some Hello"), ("Some $cjust(Hello, 30)", "Some Hello "), - ("Some $eval('-'*20)Hello", "Some --------------------Hello"), + ("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"), + ("$crop(\"spider's silk\", 5)", "spide"), ] ) def test_other_callables(self, string, expected): @@ -455,18 +463,18 @@ class TestDefaultCallables(TestCase): self.parser.parse( "this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)" ), - "this should be '''escaped,''' and '''instead,''' cropped with text. ", + "this should be escaped, and instead, cropped with text. ", ) def test_escaped2(self): + raw_str = 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)' + expected = 'this should be escaped, and instead, cropped with text. ' + result = self.parser.parse(raw_str) self.assertEqual( - self.parser.parse( - 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)' - ), - 'this should be """escaped,""" and """instead,""" cropped with text. ', + result, + expected, ) - class TestCallableSearch(test_resources.BaseEvenniaTest): """ Test the $search(query) callable From f676a7aac93885f47d5a5c22c6d427a48ccaa450 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 9 Jun 2022 15:50:16 -0600 Subject: [PATCH 02/26] fix ANSI_NORMAL override in text2html resets states on ANSI_NORMAL flag regardless of `clean` status --- evennia/utils/text2html.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/utils/text2html.py b/evennia/utils/text2html.py index ab3f2930d6..1a0b2bf6cc 100644 --- a/evennia/utils/text2html.py +++ b/evennia/utils/text2html.py @@ -234,9 +234,9 @@ class TextToHTMLparser(object): for i, substr in enumerate(str_list): # reset all current styling - if substr == ANSI_NORMAL and not clean: - # replace with close existing tag - str_list[i] = "" + if substr == ANSI_NORMAL: + # close any existing span if necessary + str_list[i] = "" if not clean else "" # reset to defaults classes = [] clean = True From ff877b967128316b3245280cfaedee1862f14b6a Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 12 Jun 2022 00:00:12 +0200 Subject: [PATCH 03/26] Fix bug in 'Attribute-with-hidden-object' deserializer --- evennia/utils/dbserialize.py | 14 +++--- evennia/utils/tests/test_dbserialize.py | 58 +++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/evennia/utils/dbserialize.py b/evennia/utils/dbserialize.py index a12f986e04..0fbec4c9e3 100644 --- a/evennia/utils/dbserialize.py +++ b/evennia/utils/dbserialize.py @@ -23,7 +23,7 @@ from collections import deque, OrderedDict, defaultdict from collections.abc import MutableSequence, MutableSet, MutableMapping try: - from pickle import dumps, loads + from pickle import dumps, loads, UnpicklingError except ImportError: from pickle import dumps, loads from django.core.exceptions import ObjectDoesNotExist @@ -633,12 +633,12 @@ def to_pickle(data): # not one of the base types if hasattr(item, "__serialize_dbobjs__"): # Allows custom serialization of any dbobjects embedded in - # the item that Evennia will otherwise not found (these would + # the item that Evennia will otherwise not find (these would # otherwise lead to an error). Use the dbserialize helper from # this method. try: item.__serialize_dbobjs__() - except TypeError: + except TypeError as err: # we catch typerrors so we can handle both classes (requiring # classmethods) and instances pass @@ -725,9 +725,13 @@ def from_pickle(data, db_obj=None): # use the dbunserialize helper in this module. try: item.__deserialize_dbobjs__() - except TypeError: + except (TypeError, UnpicklingError): # handle recoveries both of classes (requiring classmethods - # or instances + # or instances. Unpickling errors can happen when re-loading the + # data from cache (because the hidden entity was already + # deserialized and stored back on the object, unpickling it + # again fails). TODO: Maybe one could avoid this retry in a + # more graceful way? pass return item diff --git a/evennia/utils/tests/test_dbserialize.py b/evennia/utils/tests/test_dbserialize.py index 480893c466..5cd5ffceda 100644 --- a/evennia/utils/tests/test_dbserialize.py +++ b/evennia/utils/tests/test_dbserialize.py @@ -15,9 +15,7 @@ class TestDbSerialize(TestCase): """ def setUp(self): - self.obj = DefaultObject( - db_key="Tester", - ) + self.obj = DefaultObject(db_key="Tester") self.obj.save() def test_constants(self): @@ -117,3 +115,57 @@ class TestDbSerialize(TestCase): self.assertEqual(self.obj.db.test, {"a": [1, 2, 3]}) self.obj.db.test |= {"b": [5, 6]} self.assertEqual(self.obj.db.test, {"a": [1, 2, 3], "b": [5, 6]}) + + +class _InvalidContainer: + """Container not saveable in Attribute (if obj is dbobj, it 'hides' it)""" + def __init__(self, obj): + self.hidden_obj = obj + + +class _ValidContainer(_InvalidContainer): + """Container possible to save in Attribute (handles hidden dbobj explicitly)""" + def __serialize_dbobjs__(self): + self.hidden_obj = dbserialize.dbserialize(self.hidden_obj) + def __deserialize_dbobjs__(self): + self.hidden_obj = dbserialize.dbunserialize(self.hidden_obj) + + +class DbObjWrappers(TestCase): + """ + Test the `__serialize_dbobjs__` and `__deserialize_dbobjs__` methods. + + """ + def setUp(self): + super().setUp() + self.dbobj1 = DefaultObject(db_key="Tester1") + self.dbobj1.save() + self.dbobj2 = DefaultObject(db_key="Tester2") + self.dbobj2.save() + + def test_dbobj_hidden_obj__fail(self): + with self.assertRaises(TypeError): + self.dbobj1.db.testarg = _InvalidContainer(self.dbobj1) + + def test_consecutive_fetch(self): + con =_ValidContainer(self.dbobj2) + self.dbobj1.db.testarg = con + attrobj = self.dbobj1.attributes.get("testarg", return_obj=True) + + self.assertEqual(attrobj.value, con) + self.assertEqual(attrobj.value, con) + self.assertEqual(attrobj.value.hidden_obj, self.dbobj2) + + def test_dbobj_hidden_obj__success(self): + con =_ValidContainer(self.dbobj2) + self.dbobj1.db.testarg = con + + # accessing the same data twice + res1 = self.dbobj1.db.testarg + res2 = self.dbobj1.db.testarg + + self.assertEqual(res1, res2) + self.assertEqual(res1, con) + self.assertEqual(res2, con) + self.assertEqual(res1.hidden_obj, self.dbobj2) + self.assertEqual(res2.hidden_obj, self.dbobj2) From 517ec5d4d5ce150ea8ad723de49261d33a221c28 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 12 Jun 2022 00:40:36 +0200 Subject: [PATCH 04/26] Update FuncParser docs --- docs/Makefile | 4 +- docs/deploy.sh | 1 + docs/source/Components/FuncParser.md | 109 ++++++++++++++++++++------- 3 files changed, 85 insertions(+), 29 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index f7d7feae4b..c79a48bdad 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -152,8 +152,8 @@ mv-local: @echo "Documentation built (multiversion + autodocs)." @echo "To see result, open evennia/docs/build/html//index.html in a browser." -# note - don't run the following manually, the result will clash with the result -# of the github actions! +# note - don't run deploy/release manually, the result will clash with the +# result of the github actions! deploy: make _multiversion-deploy @echo "Documentation deployed." diff --git a/docs/deploy.sh b/docs/deploy.sh index 9ab8706804..e35af73b94 100644 --- a/docs/deploy.sh +++ b/docs/deploy.sh @@ -17,6 +17,7 @@ git checkout gh-pages # with the build/ directory available since this is not in git # remove all but the build dir +# TODO don't delete old branches after 1.0 release; they will get harder and harder to rebuild ls -Q | grep -v build | xargs rm -Rf cp -Rf build/html/* . diff --git a/docs/source/Components/FuncParser.md b/docs/source/Components/FuncParser.md index cb51d80c6b..3bc8260bd2 100644 --- a/docs/source/Components/FuncParser.md +++ b/docs/source/Components/FuncParser.md @@ -10,14 +10,15 @@ the return from the function. from evennia.utils.funcparser import FuncParser def _power_callable(*args, **kwargs): - """This will be callable as $square(number, power=) in string""" + """This will be callable as $pow(number, power=) in string""" pow = int(kwargs.get('power', 2)) return float(args[0]) ** pow +# create a parser and tell it that '$pow' means using _power_callable parser = FuncParser({"pow": _power_callable}) ``` -Next, just pass a string into the parser, optionally containing `$func(...)` markers: +Next, just pass a string into the parser, containing `$func(...)` markers: ```python parser.parse("We have that 4 x 4 x 4 is $pow(4, power=3).") @@ -71,7 +72,7 @@ You can apply inline function parsing to any string. The from evennia.utils import funcparser parser = FuncParser(callables, **default_kwargs) -parsed_string = parser.parser(input_string, raise_errors=False, +parsed_string = parser.parse(input_string, raise_errors=False, escape=False, strip=False, return_str=True, **reserved_kwargs) @@ -90,8 +91,12 @@ available to the parser as you parse strings with it. It can either be an underscore `_`) will be considered a suitable callable. The name of the function will be the `$funcname` by which it can be called. - A `list` of modules/paths. This allows you to pull in modules from many sources for your parsing. +- The `**default` kwargs are optional kwargs that will be passed to _all_ + callables every time this parser is used - unless the user overrides it explicitly in + their call. This is great for providing sensible standards that the user can + tweak as needed. -The other arguments to the parser: +`FuncParser.parse` takes further arguments, and can vary for every string parsed. - `raise_errors` - By default, any errors from a callable will be quietly ignored and the result will be that the failing function call will show verbatim. If `raise_errors` is set, @@ -102,12 +107,14 @@ The other arguments to the parser: - `return_str` - When `True` (default), `parser` always returns a string. If `False`, it may return the return value of a single function call in the string. This is the same as using the `.parse_to_any` method. -- The `**default/reserved_keywords` are optional and allow you to pass custom data into _every_ function - call. This is great for including things like the current session or config options. Defaults can be - replaced if the user gives the same-named kwarg in the string's function call. Reserved kwargs are always passed, - ignoring defaults or what the user passed. In addition, the `funcparser` and `raise_errors` - reserved kwargs are always passed - the first is a back-reference to the `FuncParser` instance and the second - is the `raise_errors` boolean passed into `FuncParser.parse`. +- The `**reserved_keywords` are _always_ passed to every callable in the string. + They override any `**defaults` given when instantiating the parser and cannot + be overridden by the user - if they enter the same kwarg it will be ignored. + This is great for providing the current session, settings etc. +- The `funcparser` and `raise_errors` + are always added as reserved keywords - the first is a + back-reference to the `FuncParser` instance and the second + is the `raise_errors` boolean given to `FuncParser.parse`. Here's an example of using the default/reserved keywords: @@ -158,7 +165,8 @@ created the parser. However, if you _nest_ functions, the return of the innermost function may be something other than a string. Let's introduce the `$eval` function, which evaluates simple expressions using -Python's `literal_eval` and/or `simple_eval`. +Python's `literal_eval` and/or `simple_eval`. It returns whatever data type it +evaluates to. "There's a $toint($eval(10 * 2.2))% chance of survival." @@ -177,23 +185,66 @@ will be a string: "There's a 22% chance of survival." ``` -However, if you use the `parse_to_any` (or `parse(..., return_str=True)`) and _don't add any extra string around the outermost function call_, +However, if you use the `parse_to_any` (or `parse(..., return_str=False)`) and +_don't add any extra string around the outermost function call_, you'll get the return type of the outermost callable back: ```python -parser.parse_to_any("$toint($eval(10 * 2.2)%") -"22%" parser.parse_to_any("$toint($eval(10 * 2.2)") 22 +parser.parse_to_any("the number $toint($eval(10 * 2.2).") +"the number 22" +parser.parse_to_any("$toint($eval(10 * 2.2)%") +"22%" ``` +### Escaping special character + +When entering funcparser callables in strings, it looks like a regular +function call inside a string: + +```python +"This is a $myfunc(arg1, arg2, kwarg=foo)." +``` + +Commas (`,`) and equal-signs (`=`) are considered to separate the arguments and +kwargs. In the same way, the right parenthesis (`)`) closes the argument list. +Sometimes you want to include commas in the argument without it breaking the +argument list. + +```python +"There is a $format(beautiful meadow, with dandelions) to the west." +``` + +You can escape in various ways. + +- Prepending with the escape character `\` + + ```python + "There is a $format(beautiful meadow\, with dandelions) to the west." + ``` +- Wrapping your strings in quotes. This works like Python, and you can nest + double and single quotes inside each other if so needed. The result will + be a verbatim string that contains everything but the outermost quotes. + + ```python + "There is a $format('beautiful meadow, with dandelions') to the west." + ``` +- If you want verbatim quotes in your string, you can escape them too. + + ```python + "There is a $format('beautiful meadow, with \'dandelions\'') to the west." + ``` + ### Safe convertion of inputs -Since you don't know in which order users may use your callables, they should always check the types -of its inputs and convert to the type the callable needs. Note also that when converting from strings, -there are limits what inputs you can support. This is because FunctionParser strings are often used by -non-developer players/builders and some things (such as complex classes/callables etc) are just not -safe/possible to convert from string representation. +Since you don't know in which order users may use your callables, they should +always check the types of its inputs and convert to the type the callable needs. +Note also that when converting from strings, there are limits what inputs you +can support. This is because FunctionParser strings can be used by +non-developer players/builders and some things (such as complex +classes/callables etc) are just not safe/possible to convert from string +representation. In `evennia.utils.utils` is a helper called [safe_convert_to_types](evennia.utils.utils.safe_convert_to_types). This function @@ -204,19 +255,24 @@ from evennia.utils.utils import safe_convert_to_types def _process_callable(*args, **kwargs): """ - A callable with a lot of custom options - - $process(expression, local, extra=34, extra2=foo) + $process(expression, local, extra1=34, extra2=foo) """ args, kwargs = safe_convert_to_type( - (('py', 'py'), {'extra1': int, 'extra2': str}), + (('py', str), {'extra1': int, 'extra2': str}), *args, **kwargs) # args/kwargs should be correct types now ``` +In other words, in the callable `$process(expression, local, extra1=.., +extra2=...)`, the first argument will be handled by the 'py' converter +(described below), the second will passed through regular Python `str`, +kwargs will be handled by `int` and `str` respectively. You can supply +your own converter function as long as it takes one argument and returns +the converted result. + In other words, ```python @@ -224,8 +280,7 @@ args, kwargs = safe_convert_to_type( (tuple_of_arg_converters, dict_of_kwarg_converters), *args, **kwargs) ``` -Each converter should be a callable taking one argument - this will be the arg/kwarg-value to convert. The -special converter `"py"` will try to convert a string argument to a Python structure with the help of the +The special converter `"py"` will try to convert a string argument to a Python structure with the help of the following tools (which you may also find useful to experiment with on your own): - [ast.literal_eval](https://docs.python.org/3.8/library/ast.html#ast.literal_eval) is an in-built Python @@ -339,12 +394,12 @@ references to other objects accessible via these callables. result of `you_obj.get_display_name(looker=receiver)`. This allows for a single string to echo differently depending on who sees it, and also to reference other people in the same way. - `$You([key])` - same as `$you` but always capitalized. -- `$conj(verb)` ([code](evennia.utils.funcparser.funcparser_callable_conjugate)) - conjugates a verb +- `$conj(verb)` ([code](evennia.utils.funcparser.funcparser_callable_conjugate)) - conjugates a verb between 2nd person presens to 3rd person presence depending on who sees the string. For example `"$You() $conj(smiles)".` will show as "You smile." and "Tom smiles." depending on who sees it. This makes use of the tools in [evennia.utils.verb_conjugation](evennia.utils.verb_conjugation) to do this, and only works for English verbs. -- `$pron(pronoun [,options])` ([code](evennia.utils.funcparser.funcparser_callable_pronoun)) - Dynamically +- `$pron(pronoun [,options])` ([code](evennia.utils.funcparser.funcparser_callable_pronoun)) - Dynamically map pronouns (like his, herself, you, its etc) between 1st/2nd person to 3rd person. ### Example From 9c09d3a8936ca9b40bdb6f59cbae2359eda91d91 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 12 Jun 2022 00:55:43 +0200 Subject: [PATCH 05/26] Update to readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 73bb35181c..81c8733a5d 100644 --- a/README.md +++ b/README.md @@ -60,14 +60,14 @@ introduction][introduction] to read. To learn how to get your hands on the code base, the [Getting started][gettingstarted] page is the way to go. Otherwise you could -browse the [Documentation][wiki] or why not come join the [Evennia +browse the [Documentation][docs] or why not come join the [Evennia Community forum][group] or join us in our [development chat][chat]. Welcome! -[homepage]: http://www.evennia.com +[homepage]: https://www.evennia.com [gettingstarted]: http://github.com/evennia/evennia/wiki/Getting-Started -[wiki]: https://github.com/evennia/evennia/wiki +[docs]: https://www.evennia.com/docs/latest/ [screenshot]: https://user-images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png [logo]: https://github.com/evennia/evennia/blob/master/evennia/web/website/static/website/images/evennia_logo.png [unittestciimg]: https://github.com/evennia/evennia/workflows/test-suite/badge.svg @@ -77,5 +77,5 @@ Welcome! [introduction]: https://github.com/evennia/evennia/wiki/Evennia-Introduction [license]: https://github.com/evennia/evennia/wiki/Licensing [group]: https://groups.google.com/forum/#!forum/evennia -[chat]: http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb +[chat]: https://discord.gg/AJJpcRUhtF [wikimudpage]: http://en.wikipedia.org/wiki/MUD From 45ed27c7f12ea52c78a803f4d3c333624c28b38f Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 12 Jun 2022 01:05:00 +0200 Subject: [PATCH 06/26] Another update to the readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 81c8733a5d..9b32773135 100644 --- a/README.md +++ b/README.md @@ -66,16 +66,16 @@ Welcome! [homepage]: https://www.evennia.com -[gettingstarted]: http://github.com/evennia/evennia/wiki/Getting-Started -[docs]: https://www.evennia.com/docs/latest/ +[gettingstarted]: https://www.evennia.com/docs/latest/Getting-Started.html +[docs]: https://www.evennia.com/docs/latest [screenshot]: https://user-images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png [logo]: https://github.com/evennia/evennia/blob/master/evennia/web/website/static/website/images/evennia_logo.png [unittestciimg]: https://github.com/evennia/evennia/workflows/test-suite/badge.svg [unittestcilink]: https://github.com/evennia/evennia/actions?query=workflow%3Atest-suite [coverimg]: https://coveralls.io/repos/github/evennia/evennia/badge.svg?branch=master [coverlink]: https://coveralls.io/github/evennia/evennia?branch=master -[introduction]: https://github.com/evennia/evennia/wiki/Evennia-Introduction -[license]: https://github.com/evennia/evennia/wiki/Licensing -[group]: https://groups.google.com/forum/#!forum/evennia +[introduction]: https://www.evennia.com/docs/latest/Evennia-Introduction.html +[license]: https://www.evennia.com/docs/latest/Licensing.html +[group]: https://github.com/evennia/evennia/discussions [chat]: https://discord.gg/AJJpcRUhtF [wikimudpage]: http://en.wikipedia.org/wiki/MUD From a83f21ef2f075f0cbb28a32e9d7f9be2c9deed12 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 12 Jun 2022 09:46:48 +0200 Subject: [PATCH 07/26] Apply black to cleanup code --- evennia/commands/cmdsethandler.py | 4 +- evennia/commands/default/building.py | 14 +-- evennia/commands/default/tests.py | 10 +- .../base_systems/components/__init__.py | 6 +- .../base_systems/components/component.py | 1 + .../base_systems/components/dbfield.py | 7 +- .../contrib/base_systems/components/holder.py | 10 +- .../base_systems/components/signals.py | 12 ++- .../contrib/base_systems/components/tests.py | 31 +++--- .../custom_gametime/custom_gametime.py | 2 +- evennia/contrib/rpg/rpsystem/rpsystem.py | 100 ++++++++++-------- evennia/contrib/rpg/rpsystem/tests.py | 18 ++-- evennia/contrib/rpg/traits/tests.py | 19 +++- evennia/contrib/rpg/traits/traits.py | 30 +++--- evennia/help/utils.py | 10 +- evennia/locks/lockfuncs.py | 6 +- evennia/prototypes/prototypes.py | 4 +- evennia/server/evennia_launcher.py | 8 +- evennia/server/server.py | 1 + evennia/server/tests/test_server.py | 1 - evennia/server/tests/testrunner.py | 4 + evennia/typeclasses/attributes.py | 16 +-- evennia/typeclasses/tags.py | 24 +++-- evennia/typeclasses/tests.py | 4 +- evennia/utils/evmenu.py | 6 +- evennia/utils/funcparser.py | 4 +- evennia/utils/gametime.py | 2 +- evennia/utils/logger.py | 14 ++- evennia/utils/test_resources.py | 1 - evennia/utils/tests/test_dbserialize.py | 8 +- evennia/utils/tests/test_funcparser.py | 18 ++-- evennia/utils/tests/test_text2html.py | 54 +++------- evennia/utils/utils.py | 1 + 33 files changed, 256 insertions(+), 194 deletions(-) diff --git a/evennia/commands/cmdsethandler.py b/evennia/commands/cmdsethandler.py index 1930aa3bce..d6338eac75 100644 --- a/evennia/commands/cmdsethandler.py +++ b/evennia/commands/cmdsethandler.py @@ -450,9 +450,7 @@ class CmdSetHandler(object): """ if "permanent" in kwargs: - logger.log_dep( - "obj.cmdset.add() kwarg 'permanent' has changed name to 'persistent'." - ) + logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed name to 'persistent'.") persistent = kwargs["permanent"] if persistent is False else persistent if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)): diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index e8d3b6a5b6..582c6434e4 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -2219,11 +2219,13 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): old_typeclass_path = obj.typeclass_path if reset: - answer = yield("|yNote that this will reset the object back to its typeclass' default state, " - "removing any custom locks/perms/attributes etc that may have been added " - "by an explicit create_object call. Use `update` or type/force instead in order " - "to keep such data. " - "Continue [Y]/N?|n") + answer = yield ( + "|yNote that this will reset the object back to its typeclass' default state, " + "removing any custom locks/perms/attributes etc that may have been added " + "by an explicit create_object call. Use `update` or type/force instead in order " + "to keep such data. " + "Continue [Y]/N?|n" + ) if answer.upper() in ("N", "NO"): caller.msg("Aborted.") return @@ -3473,7 +3475,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): caller.msg("\n".join(msgs)) if "delete" not in self.switches: if script and script.pk: - ScriptEvMore(caller, [script], session=self.session) + ScriptEvMore(caller, [script], session=self.session) else: caller.msg("Script was deleted automatically.") else: diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index cfcc622689..32f4a882f5 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -107,8 +107,7 @@ class TestGeneral(BaseEvenniaCommandTest): def test_nick_list(self): self.call(general.CmdNick(), "/list", "No nicks defined.") - self.call(general.CmdNick(), "test1 = Hello", - "Inputline-nick 'test1' mapped to 'Hello'.") + self.call(general.CmdNick(), "test1 = Hello", "Inputline-nick 'test1' mapped to 'Hello'.") self.call(general.CmdNick(), "/list", "Defined Nicks:") def test_get_and_drop(self): @@ -1295,7 +1294,8 @@ class TestBuilding(BaseEvenniaCommandTest): "Obj2 = evennia.objects.objects.DefaultExit", "Obj2 changed typeclass from evennia.objects.objects.DefaultObject " "to evennia.objects.objects.DefaultExit.", - cmdstring="swap", inputs=["yes"], + cmdstring="swap", + inputs=["yes"], ) self.call(building.CmdTypeclass(), "/list Obj", "Core typeclasses") self.call( @@ -1332,7 +1332,7 @@ class TestBuilding(BaseEvenniaCommandTest): "/reset/force Obj=evennia.objects.objects.DefaultObject", "Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n" "All object creation hooks were run. All old attributes where deleted before the swap.", - inputs=["yes"] + inputs=["yes"], ) from evennia.prototypes.prototypes import homogenize_prototype @@ -1359,7 +1359,7 @@ class TestBuilding(BaseEvenniaCommandTest): "typeclasses.objects.Object.\nOnly the at_object_creation hook was run " "(update mode). Attributes set before swap were not removed\n" "(use `swap` or `type/reset` to clear all). Prototype 'replaced_obj' was " - "successfully applied over the object type." + "successfully applied over the object type.", ) assert self.obj1.db.desc == "protdesc" diff --git a/evennia/contrib/base_systems/components/__init__.py b/evennia/contrib/base_systems/components/__init__.py index 1aa94a1df1..21bb6bf936 100644 --- a/evennia/contrib/base_systems/components/__init__.py +++ b/evennia/contrib/base_systems/components/__init__.py @@ -17,8 +17,10 @@ def get_component_class(component_name): subclasses = Component.__subclasses__() component_class = next((sc for sc in subclasses if sc.name == component_name), None) if component_class is None: - message = f"Component named {component_name} has not been found. " \ - f"Make sure it has been imported before being used." + message = ( + f"Component named {component_name} has not been found. " + f"Make sure it has been imported before being used." + ) raise Exception(message) return component_class diff --git a/evennia/contrib/base_systems/components/component.py b/evennia/contrib/base_systems/components/component.py index 4b1697da38..4be5b86d15 100644 --- a/evennia/contrib/base_systems/components/component.py +++ b/evennia/contrib/base_systems/components/component.py @@ -13,6 +13,7 @@ class Component: Each Component must supply the name, it is used as a slot name but also part of the attribute key. """ + name = "" def __init__(self, host=None): diff --git a/evennia/contrib/base_systems/components/dbfield.py b/evennia/contrib/base_systems/components/dbfield.py index 9adbf8197f..fc3a104ffb 100644 --- a/evennia/contrib/base_systems/components/dbfield.py +++ b/evennia/contrib/base_systems/components/dbfield.py @@ -26,7 +26,7 @@ class DBField(AttributeProperty): db_fields = getattr(owner, "_db_fields", None) if db_fields is None: db_fields = {} - setattr(owner, '_db_fields', db_fields) + setattr(owner, "_db_fields", db_fields) db_fields[name] = self @@ -50,7 +50,7 @@ class NDBField(NAttributeProperty): ndb_fields = getattr(owner, "_ndb_fields", None) if ndb_fields is None: ndb_fields = {} - setattr(owner, '_ndb_fields', ndb_fields) + setattr(owner, "_ndb_fields", ndb_fields) ndb_fields[name] = self @@ -64,6 +64,7 @@ class TagField: Default value of a tag is added when the component is registered. Tags are removed if the component itself is removed. """ + def __init__(self, default=None, enforce_single=False): self._category_key = None self._default = default @@ -78,7 +79,7 @@ class TagField: tag_fields = getattr(owner, "_tag_fields", None) if tag_fields is None: tag_fields = {} - setattr(owner, '_tag_fields', tag_fields) + setattr(owner, "_tag_fields", tag_fields) tag_fields[name] = self def __get__(self, instance, owner): diff --git a/evennia/contrib/base_systems/components/holder.py b/evennia/contrib/base_systems/components/holder.py index d5112083cd..91d4330f58 100644 --- a/evennia/contrib/base_systems/components/holder.py +++ b/evennia/contrib/base_systems/components/holder.py @@ -16,6 +16,7 @@ class ComponentProperty: Defaults can be overridden for this typeclass by passing kwargs """ + def __init__(self, component_name, **kwargs): """ Initializes the descriptor @@ -49,6 +50,7 @@ class ComponentHandler: It lets you add or remove components and will load components as needed. It stores the list of registered components on the host .db with component_names as key. """ + def __init__(self, host): self.host = host self._loaded_components = {} @@ -124,7 +126,9 @@ class ComponentHandler: self.host.signals.remove_object_listeners_and_responders(component) del self._loaded_components[component_name] else: - message = f"Cannot remove {component_name} from {self.host.name} as it is not registered." + message = ( + f"Cannot remove {component_name} from {self.host.name} as it is not registered." + ) raise ComponentIsNotRegistered(message) def remove_by_name(self, name): @@ -199,7 +203,9 @@ class ComponentHandler: self._set_component(component_instance) self.host.signals.add_object_listeners_and_responders(component_instance) else: - message = f"Could not initialize runtime component {component_name} of {self.host.name}" + message = ( + f"Could not initialize runtime component {component_name} of {self.host.name}" + ) raise ComponentDoesNotExist(message) def _set_component(self, component): diff --git a/evennia/contrib/base_systems/components/signals.py b/evennia/contrib/base_systems/components/signals.py index 23ace839dd..eff5137c64 100644 --- a/evennia/contrib/base_systems/components/signals.py +++ b/evennia/contrib/base_systems/components/signals.py @@ -15,9 +15,11 @@ def as_listener(func=None, signal_name=None): signal_name (str): The name of the signal to listen to, defaults to function name. """ if not func and signal_name: + def wrapper(func): func._listener_signal_name = signal_name return func + return wrapper signal_name = func.__name__ @@ -35,9 +37,11 @@ def as_responder(func=None, signal_name=None): signal_name (str): The name of the signal to respond to, defaults to function name. """ if not func and signal_name: + def wrapper(func): func._responder_signal_name = signal_name return func + return wrapper signal_name = func.__name__ @@ -177,12 +181,12 @@ class SignalsHandler(object): """ type_host = type(obj) for att_name, att_obj in type_host.__dict__.items(): - listener_signal_name = getattr(att_obj, '_listener_signal_name', None) + listener_signal_name = getattr(att_obj, "_listener_signal_name", None) if listener_signal_name: callback = getattr(obj, att_name) self.add_listener(signal_name=listener_signal_name, callback=callback) - responder_signal_name = getattr(att_obj, '_responder_signal_name', None) + responder_signal_name = getattr(att_obj, "_responder_signal_name", None) if responder_signal_name: callback = getattr(obj, att_name) self.add_responder(signal_name=responder_signal_name, callback=callback) @@ -196,12 +200,12 @@ class SignalsHandler(object): """ type_host = type(obj) for att_name, att_obj in type_host.__dict__.items(): - listener_signal_name = getattr(att_obj, '_listener_signal_name', None) + listener_signal_name = getattr(att_obj, "_listener_signal_name", None) if listener_signal_name: callback = getattr(obj, att_name) self.remove_listener(signal_name=listener_signal_name, callback=callback) - responder_signal_name = getattr(att_obj, '_responder_signal_name', None) + responder_signal_name = getattr(att_obj, "_responder_signal_name", None) if responder_signal_name: callback = getattr(obj, att_name) self.remove_responder(signal_name=responder_signal_name, callback=callback) diff --git a/evennia/contrib/base_systems/components/tests.py b/evennia/contrib/base_systems/components/tests.py index 413b964c18..6251575af6 100644 --- a/evennia/contrib/base_systems/components/tests.py +++ b/evennia/contrib/base_systems/components/tests.py @@ -56,7 +56,7 @@ class TestComponents(EvenniaTest): def test_character_can_register_runtime_component(self): rct = RuntimeComponentTestC.create(self.char1) self.char1.components.add(rct) - test_c = self.char1.components.get('test_c') + test_c = self.char1.components.get("test_c") assert test_c assert test_c.my_int == 6 @@ -110,7 +110,7 @@ class TestComponents(EvenniaTest): assert handler.get("test_c") is rct def test_can_access_component_regular_get(self): - assert self.char1.cmp.test_a is self.char1.components.get('test_a') + assert self.char1.cmp.test_a is self.char1.components.get("test_a") def test_returns_none_with_regular_get_when_no_attribute(self): assert self.char1.cmp.does_not_exist is None @@ -127,7 +127,7 @@ class TestComponents(EvenniaTest): def test_host_has_added_component_tags(self): rct = RuntimeComponentTestC.create(self.char1) self.char1.components.add(rct) - test_c = self.char1.components.get('test_c') + test_c = self.char1.components.get("test_c") assert self.char1.tags.has(key="test_c", category="components") assert self.char1.tags.has(key="added_value", category="test_c::added_tag") @@ -162,7 +162,7 @@ class TestComponents(EvenniaTest): assert not self.char1.tags.has(key="added_value", category="test_c::added_tag") def test_component_tags_only_hold_one_value_when_enforce_single(self): - test_b = self.char1.components.get('test_b') + test_b = self.char1.components.get("test_b") test_b.single_tag = "first_value" test_b.single_tag = "second value" @@ -171,7 +171,7 @@ class TestComponents(EvenniaTest): assert not self.char1.tags.has(key="first_value", category="test_b::single_tag") def test_component_tags_default_value_is_overridden_when_enforce_single(self): - test_b = self.char1.components.get('test_b') + test_b = self.char1.components.get("test_b") test_b.default_single_tag = "second value" assert self.char1.tags.has(key="second value", category="test_b::default_single_tag") @@ -179,12 +179,14 @@ class TestComponents(EvenniaTest): assert not self.char1.tags.has(key="first_value", category="test_b::default_single_tag") def test_component_tags_support_multiple_values_by_default(self): - test_b = self.char1.components.get('test_b') + test_b = self.char1.components.get("test_b") test_b.multiple_tags = "first value" test_b.multiple_tags = "second value" test_b.multiple_tags = "third value" - assert all(val in test_b.multiple_tags for val in ("first value", "second value", "third value")) + assert all( + val in test_b.multiple_tags for val in ("first value", "second value", "third value") + ) assert self.char1.tags.has(key="first value", category="test_b::multiple_tags") assert self.char1.tags.has(key="second value", category="test_b::multiple_tags") assert self.char1.tags.has(key="third value", category="test_b::multiple_tags") @@ -193,11 +195,11 @@ class TestComponents(EvenniaTest): class CharWithSignal(ComponentHolderMixin, DefaultCharacter): @signals.as_listener def my_signal(self): - setattr(self, 'my_signal_is_called', True) + setattr(self, "my_signal_is_called", True) @signals.as_listener def my_other_signal(self): - setattr(self, 'my_other_signal_is_called', True) + setattr(self, "my_other_signal_is_called", True) @signals.as_responder def my_response(self): @@ -213,11 +215,11 @@ class ComponentWithSignal(Component): @signals.as_listener def my_signal(self): - setattr(self, 'my_signal_is_called', True) + setattr(self, "my_signal_is_called", True) @signals.as_listener def my_other_signal(self): - setattr(self, 'my_other_signal_is_called', True) + setattr(self, "my_other_signal_is_called", True) @signals.as_responder def my_response(self): @@ -236,14 +238,15 @@ class TestComponentSignals(BaseEvenniaTest): def setUp(self): super().setUp() self.char1 = create.create_object( - CharWithSignal, key="Char", + CharWithSignal, + key="Char", ) def test_host_can_register_as_listener(self): self.char1.signals.trigger("my_signal") assert self.char1.my_signal_is_called - assert not getattr(self.char1, 'my_other_signal_is_called', None) + assert not getattr(self.char1, "my_other_signal_is_called", None) def test_host_can_register_as_responder(self): responses = self.char1.signals.query("my_response") @@ -258,7 +261,7 @@ class TestComponentSignals(BaseEvenniaTest): component = char.cmp.test_signal_a assert component.my_signal_is_called - assert not getattr(component, 'my_other_signal_is_called', None) + assert not getattr(component, "my_other_signal_is_called", None) def test_component_can_register_as_responder(self): char = self.char1 diff --git a/evennia/contrib/base_systems/custom_gametime/custom_gametime.py b/evennia/contrib/base_systems/custom_gametime/custom_gametime.py index 33588deca9..eac9020b8a 100644 --- a/evennia/contrib/base_systems/custom_gametime/custom_gametime.py +++ b/evennia/contrib/base_systems/custom_gametime/custom_gametime.py @@ -328,4 +328,4 @@ class GametimeScript(DefaultScript): callback() seconds = real_seconds_until(**self.db.gametime) - self.start(interval=seconds,force_restart=True) + self.start(interval=seconds, force_restart=True) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index c191d72d93..69f29280c6 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -284,7 +284,7 @@ def parse_language(speaker, emote): # the key is simply the running match in the emote key = f"##{imatch}" # replace say with ref markers in emote - emote = "{start}{{{key}}}{end}".format( start=emote[:istart], key=key, end=emote[iend:] ) + emote = "{start}{{{key}}}{end}".format(start=emote[:istart], key=key, end=emote[iend:]) mapping[key] = (langname, saytext) if errors: @@ -339,18 +339,18 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ """ # build a list of candidates with all possible referrable names # include 'me' keyword for self-ref - candidate_map = [(sender, 'me')] + candidate_map = [(sender, "me")] for obj in candidates: # check if sender has any recogs for obj and add if hasattr(sender, "recog"): if recog := sender.recog.get(obj): - candidate_map.append((obj, recog)) + candidate_map.append((obj, recog)) # check if obj has an sdesc and add if hasattr(obj, "sdesc"): candidate_map.append((obj, obj.sdesc.get())) # if no sdesc, include key plus aliases instead else: - candidate_map.extend( [(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()] ) + candidate_map.extend([(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()]) # escape mapping syntax on the form {#id} if it exists already in emote, # if so it is replaced with just "id". @@ -374,31 +374,40 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ match_index = marker_match.start() # split the emote string at the reference marker, to process everything after it head = string[:match_index] - tail = string[match_index+1:] - + tail = string[match_index + 1 :] + if search_mode: # match the candidates against the whole search string after the marker - rquery = "".join([r"\b(" + re.escape(word.strip(punctuation)) + r").*" for word in iter(tail.split())]) - matches = ((re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map) + rquery = "".join( + [ + r"\b(" + re.escape(word.strip(punctuation)) + r").*" + for word in iter(tail.split()) + ] + ) + matches = ( + (re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map + ) # filter out any non-matching candidates bestmatches = [(obj, match.group()) for match, obj, text in matches if match] else: - # to find the longest match, we start from the marker and lengthen the + # to find the longest match, we start from the marker and lengthen the # match query one word at a time. word_list = [] bestmatches = [] # preserve punctuation when splitting - tail = re.split('(\W)', tail) + tail = re.split("(\W)", tail) iend = 0 for i, item in enumerate(tail): # don't add non-word characters to the search query if not item.isalpha(): - continue + continue word_list.append(item) rquery = "".join([r"\b(" + re.escape(word) + r").*" for word in word_list]) # match candidates against the current set of words - matches = ((re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map) + matches = ( + (re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map + ) matches = [(obj, match.group()) for match, obj, text in matches if match] if len(matches) == 0: # no matches at this length, keep previous iteration as best @@ -411,7 +420,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # save search string matched_text = "".join(tail[1:iend]) # recombine remainder of emote back into a string - tail = "".join(tail[iend+1:]) + tail = "".join(tail[iend + 1 :]) nmatches = len(bestmatches) @@ -549,18 +558,18 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): if "anonymous_add" in kwargs: anonymous_add = kwargs.pop("anonymous_add") # make sure to catch all possible self-refs - self_refs = [f"{skey}{ref}" for ref in ('t','^','v','~','')] + self_refs = [f"{skey}{ref}" for ref in ("t", "^", "v", "~", "")] if anonymous_add and not any(1 for tag in obj_mapping if tag in self_refs): # no self-reference in the emote - add it if anonymous_add == "first": # add case flag for initial caps - skey += 't' + skey += "t" # don't put a space after the self-ref if it's a possessive emote femote = "{key}{emote}" if emote.startswith("'") else "{key} {emote}" else: # add it to the end femote = "{emote} [{key}]" - emote = femote.format( key="{{"+ skey +"}}", emote=emote ) + emote = femote.format(key="{{" + skey + "}}", emote=emote) obj_mapping[skey] = sender # broadcast emote to everyone @@ -663,7 +672,9 @@ class SdescHandler: if len(cleaned_sdesc) > max_length: raise SdescError( - "Short desc can max be {} chars long (was {} chars).".format(max_length, len(cleaned_sdesc)) + "Short desc can max be {} chars long (was {} chars).".format( + max_length, len(cleaned_sdesc) + ) ) # store to attributes @@ -682,7 +693,6 @@ class SdescHandler: return self.sdesc or self.obj.key - class RecogHandler: """ This handler manages the recognition mapping @@ -758,7 +768,9 @@ class RecogHandler: if len(cleaned_recog) > max_length: raise RecogError( - "Recog string cannot be longer than {} chars (was {} chars)".format(max_length, len(cleaned_recog)) + "Recog string cannot be longer than {} chars (was {} chars)".format( + max_length, len(cleaned_recog) + ) ) # mapping #dbref:obj @@ -866,7 +878,7 @@ class CmdEmote(RPCommand): # replaces the main emote emote = self.args targets = self.caller.location.contents if not emote.endswith((".", "?", "!", '"')): # If emote is not punctuated or speech, - emote += "." # add a full-stop for good measure. + emote += "." # add a full-stop for good measure. send_emote(self.caller, targets, emote, anonymous_add="first") @@ -1132,7 +1144,11 @@ class CmdRecog(RPCommand): # assign personal alias to object in room if forget_mode: # remove existing recog caller.recog.remove(obj) - caller.msg("You will now know them only as '{}'.".format( obj.get_display_name(caller, noid=True) )) + caller.msg( + "You will now know them only as '{}'.".format( + obj.get_display_name(caller, noid=True) + ) + ) else: # set recog sdesc = obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key @@ -1216,9 +1232,10 @@ class ContribRPObject(DefaultObject): This class is meant as a mix-in or parent for objects in an rp-heavy game. It implements the base functionality for poses. """ + @lazy_property def sdesc(self): - return SdescHandler(self) + return SdescHandler(self) def at_object_creation(self): """ @@ -1409,19 +1426,18 @@ class ContribRPObject(DefaultObject): def get_posed_sdesc(self, sdesc, **kwargs): """ Displays the object with its current pose string. - + Returns: pose (str): A string containing the object's sdesc and current or default pose. """ - + # get the current pose, or default if no pose is set pose = self.db.pose or self.db.pose_default - + # return formatted string, or sdesc as fallback return f"{sdesc} {pose}" if pose else sdesc - def get_display_name(self, looker, **kwargs): """ Displays the name of the object in a viewer-aware manner. @@ -1448,8 +1464,8 @@ class ContribRPObject(DefaultObject): is privileged to control said object. """ - ref = kwargs.get("ref","~") - + ref = kwargs.get("ref", "~") + if looker == self: # always show your own key sdesc = self.key @@ -1460,13 +1476,12 @@ class ContribRPObject(DefaultObject): except AttributeError: # use own sdesc as a fallback sdesc = self.sdesc.get() - - # add dbref is looker has control access and `noid` is not set - if self.access(looker, access_type="control") and not kwargs.get("noid",False): - sdesc = f"{sdesc}(#{self.id})" - - return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc + # add dbref is looker has control access and `noid` is not set + if self.access(looker, access_type="control") and not kwargs.get("noid", False): + sdesc = f"{sdesc}(#{self.id})" + + return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc def return_appearance(self, looker): """ @@ -1475,7 +1490,7 @@ class ContribRPObject(DefaultObject): Args: looker (Object): Object doing the looking. - + Returns: string (str): A string containing the name, appearance and contents of the object. @@ -1553,11 +1568,11 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): characters stand out from other objects. """ - ref = kwargs.get("ref","~") - + ref = kwargs.get("ref", "~") + if looker == self: # process your key as recog since you recognize yourself - sdesc = self.process_recog(self.key,self) + sdesc = self.process_recog(self.key, self) else: try: # get the sdesc looker should see, with formatting @@ -1567,12 +1582,11 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): sdesc = self.sdesc.get() # add dbref is looker has control access and `noid` is not set - if self.access(looker, access_type="control") and not kwargs.get("noid",False): + if self.access(looker, access_type="control") and not kwargs.get("noid", False): sdesc = f"{sdesc}(#{self.id})" return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc - def at_object_creation(self): """ Called at initial creation. @@ -1631,7 +1645,6 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): return sdesc - def process_sdesc(self, sdesc, obj, **kwargs): """ Allows to customize how your sdesc is displayed (primarily by @@ -1713,7 +1726,4 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): the evennia.contrib.rpg.rplanguage module. """ - return "{label}|w{text}|n".format( - label=f"|W({language})" if language else "", - text=text - ) + return "{label}|w{text}|n".format(label=f"|W({language})" if language else "", text=text) diff --git a/evennia/contrib/rpg/rpsystem/tests.py b/evennia/contrib/rpg/rpsystem/tests.py index f0d040d6f7..bd2ea32896 100644 --- a/evennia/contrib/rpg/rpsystem/tests.py +++ b/evennia/contrib/rpg/rpsystem/tests.py @@ -165,17 +165,19 @@ class TestRPSystem(BaseEvenniaTest): ) def test_get_sdesc(self): - looker = self.speaker # Sender - target = self.receiver1 # Receiver1 - looker.sdesc.add(sdesc0) # A nice sender of emotes - target.sdesc.add(sdesc1) # The first receiver of emotes. + looker = self.speaker # Sender + target = self.receiver1 # Receiver1 + looker.sdesc.add(sdesc0) # A nice sender of emotes + target.sdesc.add(sdesc1) # The first receiver of emotes. # sdesc with no processing self.assertEqual(looker.get_sdesc(target), "The first receiver of emotes.") # sdesc with processing - self.assertEqual(looker.get_sdesc(target, process=True), "|bThe first receiver of emotes.|n") - - looker.recog.add(target, recog01) # Mr Receiver + self.assertEqual( + looker.get_sdesc(target, process=True), "|bThe first receiver of emotes.|n" + ) + + looker.recog.add(target, recog01) # Mr Receiver # recog with no processing self.assertEqual(looker.get_sdesc(target), "Mr Receiver") @@ -233,7 +235,7 @@ class TestRPSystem(BaseEvenniaTest): self.out1, "|bA nice sender of emotes|n looks at |mReceiver1|n. Then, " "|ba nice sender of emotes|n looks at |mReceiver1|n, |mReceiver1|n " - "and |bAnother nice colliding sdesc-guy for tests|n twice." + "and |bAnother nice colliding sdesc-guy for tests|n twice.", ) self.assertEqual( self.out2, diff --git a/evennia/contrib/rpg/traits/tests.py b/evennia/contrib/rpg/traits/tests.py index a6bc405357..612fd8840d 100644 --- a/evennia/contrib/rpg/traits/tests.py +++ b/evennia/contrib/rpg/traits/tests.py @@ -321,7 +321,6 @@ class TestTraitStatic(_TraitHandlerBase): self.trait.mult = 0.75 self.assertEqual(self._get_values(), (5, 1, 0.75, 4.5)) - def test_delete(self): """Deleting resets to default.""" self.trait.mult = 2.0 @@ -362,7 +361,14 @@ class TestTraitCounter(_TraitHandlerBase): def _get_values(self): """Get (base, mod, mult, value, min, max).""" - return (self.trait.base, self.trait.mod, self.trait.mult, self.trait.value, self.trait.min, self.trait.max) + return ( + self.trait.base, + self.trait.mod, + self.trait.mult, + self.trait.value, + self.trait.min, + self.trait.max, + ) def test_init(self): self.assertEqual( @@ -634,7 +640,14 @@ class TestTraitGauge(_TraitHandlerBase): def _get_values(self): """Get (base, mod, mult, value, min, max).""" - return (self.trait.base, self.trait.mod, self.trait.mult, self.trait.value, self.trait.min, self.trait.max) + return ( + self.trait.base, + self.trait.mod, + self.trait.mult, + self.trait.value, + self.trait.min, + self.trait.max, + ) def test_init(self): self.assertEqual( diff --git a/evennia/contrib/rpg/traits/traits.py b/evennia/contrib/rpg/traits/traits.py index 303fe89bf7..cd759fa8c0 100644 --- a/evennia/contrib/rpg/traits/traits.py +++ b/evennia/contrib/rpg/traits/traits.py @@ -1148,7 +1148,7 @@ class Trait: class StaticTrait(Trait): """ - Static Trait. This is a single value with a modifier, + Static Trait. This is a single value with a modifier, multiplier, and no concept of a 'current' value or min/max etc. value = (base + mod) * mult @@ -1161,7 +1161,9 @@ class StaticTrait(Trait): def __str__(self): status = "{value:11}".format(value=self.value) - return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(name=self.name, status=status, mod=self.mod, mult=self.mult) + return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format( + name=self.name, status=status, mod=self.mod, mult=self.mult + ) # Helpers @property @@ -1189,7 +1191,7 @@ class StaticTrait(Trait): def mult(self): """The trait's multiplier.""" return self._data["mult"] - + @mult.setter def mult(self, amount): if type(amount) in (int, float): @@ -1322,16 +1324,16 @@ class CounterTrait(Trait): now = time() tdiff = now - self._data["last_update"] current += rate * tdiff - value = (current + self.mod) + value = current + self.mod # we must make sure so we don't overstep our bounds # even if .mod is included if self._passed_ratetarget(value): - current = (self._data["ratetarget"] - self.mod) + current = self._data["ratetarget"] - self.mod self._stop_timer() elif not self._within_boundaries(value): - current = (self._enforce_boundaries(value) - self.mod) + current = self._enforce_boundaries(value) - self.mod self._stop_timer() else: self._data["last_update"] = now @@ -1378,7 +1380,7 @@ class CounterTrait(Trait): @property def mult(self): return self._data["mult"] - + @mult.setter def mult(self, amount): if type(amount) in (int, float): @@ -1571,7 +1573,9 @@ class GaugeTrait(CounterTrait): def __str__(self): status = "{value:4} / {base:4}".format(value=self.value, base=self.base) - return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(name=self.name, status=status, mod=self.mod, mult=self.mult) + return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format( + name=self.name, status=status, mod=self.mod, mult=self.mult + ) @property def base(self): @@ -1596,11 +1600,11 @@ class GaugeTrait(CounterTrait): if value + self.base < self.min: value = self.min - self.base self._data["mod"] = value - + @property def mult(self): return self._data["mult"] - + @mult.setter def mult(self, amount): if type(amount) in (int, float): @@ -1621,7 +1625,7 @@ class GaugeTrait(CounterTrait): if value is None: self._data["min"] = self.default_keys["min"] elif type(value) in (int, float): - self._data["min"] = min(value, (self.base + self.mod) * self.mult) + self._data["min"] = min(value, (self.base + self.mod) * self.mult) @property def max(self): @@ -1644,7 +1648,7 @@ class GaugeTrait(CounterTrait): def current(self): """The `current` value of the gauge.""" return self._update_current( - self._enforce_boundaries(self._data.get("current", (self.base + self.mod) * self.mult)) + self._enforce_boundaries(self._data.get("current", (self.base + self.mod) * self.mult)) ) @current.setter @@ -1655,7 +1659,7 @@ class GaugeTrait(CounterTrait): @current.deleter def current(self): "Resets current back to 'full'" - self._data["current"] = (self.base + self.mod) * self.mult + self._data["current"] = (self.base + self.mod) * self.mult @property def value(self): diff --git a/evennia/help/utils.py b/evennia/help/utils.py index 981aea1fd0..85a254ac9e 100644 --- a/evennia/help/utils.py +++ b/evennia/help/utils.py @@ -12,9 +12,13 @@ import re # since we use them (e.g. as command names). # Lunr's default ignore-word list is found here: # https://github.com/yeraydiazdiaz/lunr.py/blob/master/lunr/stop_word_filter.py -_LUNR_STOP_WORD_FILTER_EXCEPTIONS = ( - ["about", "might", "get", "who", "say"] + settings.LUNR_STOP_WORD_FILTER_EXCEPTIONS -) +_LUNR_STOP_WORD_FILTER_EXCEPTIONS = [ + "about", + "might", + "get", + "who", + "say", +] + settings.LUNR_STOP_WORD_FILTER_EXCEPTIONS _LUNR = None diff --git a/evennia/locks/lockfuncs.py b/evennia/locks/lockfuncs.py index 8f2cdb70a8..b3d3de0a5e 100644 --- a/evennia/locks/lockfuncs.py +++ b/evennia/locks/lockfuncs.py @@ -473,6 +473,7 @@ def tag(accessing_obj, accessed_obj, *args, **kwargs): category = args[1] if len(args) > 1 else None return bool(accessing_obj.tags.get(tagkey, category=category)) + def is_ooc(accessing_obj, accessed_obj, *args, **kwargs): """ Usage: @@ -489,13 +490,14 @@ def is_ooc(accessing_obj, accessed_obj, *args, **kwargs): session = accessed_obj.session except AttributeError: session = account.sessions.get()[0] # note-this doesn't work well - # for high multisession mode. We may need - # to change to sessiondb to resolve this + # for high multisession mode. We may need + # to change to sessiondb to resolve this try: return not account.get_puppet(session) except TypeError: return not session.get_puppet() + def objtag(accessing_obj, accessed_obj, *args, **kwargs): """ Usage: diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index f9b6c564d3..8c002c6145 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -528,10 +528,10 @@ def search_prototype( """ # This will load the prototypes the first time they are searched - loaded = getattr(load_module_prototypes, '_LOADED', False) + loaded = getattr(load_module_prototypes, "_LOADED", False) if not loaded: load_module_prototypes() - setattr(load_module_prototypes, '_LOADED', True) + setattr(load_module_prototypes, "_LOADED", True) # prototype keys are always in lowecase if key: diff --git a/evennia/server/evennia_launcher.py b/evennia/server/evennia_launcher.py index 846e674ad0..110e4d218a 100644 --- a/evennia/server/evennia_launcher.py +++ b/evennia/server/evennia_launcher.py @@ -2316,9 +2316,11 @@ def main(): if option in ("makemessages", "compilemessages"): # some commands don't require the presence of a game directory to work need_gamedir = False - if CURRENT_DIR != EVENNIA_LIB: - print("You must stand in the evennia/evennia/ folder (where the 'locale/' " - "folder is located) to run this command.") + if CURRENT_DIR != EVENNIA_LIB: + print( + "You must stand in the evennia/evennia/ folder (where the 'locale/' " + "folder is located) to run this command." + ) sys.exit() if option in ("shell", "check", "makemigrations", "createsuperuser", "shell_plus"): diff --git a/evennia/server/server.py b/evennia/server/server.py index 4f653ffad5..4739d43ff3 100644 --- a/evennia/server/server.py +++ b/evennia/server/server.py @@ -423,6 +423,7 @@ class Evennia: logger.log_msg("Evennia Server successfully restarted in 'reset' mode.") elif mode == "shutdown": from evennia.objects.models import ObjectDB + self.at_server_cold_start() # clear eventual lingering session storages ObjectDB.objects.clear_all_sessids() diff --git a/evennia/server/tests/test_server.py b/evennia/server/tests/test_server.py index d0c79064a0..a5bf8a825a 100644 --- a/evennia/server/tests/test_server.py +++ b/evennia/server/tests/test_server.py @@ -197,7 +197,6 @@ class TestServer(TestCase): class TestInitHooks(TestCase): - def setUp(self): from evennia.utils import create diff --git a/evennia/server/tests/testrunner.py b/evennia/server/tests/testrunner.py index 9199164c58..69bd632461 100644 --- a/evennia/server/tests/testrunner.py +++ b/evennia/server/tests/testrunner.py @@ -16,15 +16,18 @@ class EvenniaTestSuiteRunner(DiscoverRunner): avoid running the large number of tests defined by Django """ + def setup_test_environment(self, **kwargs): # the portal looping call starts before the unit-test suite so we # can't mock it - instead we stop it before starting the test - otherwise # we'd get unclean reactor errors across test boundaries. from evennia.server.portal.portal import PORTAL + PORTAL.maintenance_task.stop() # initialize evennia itself import evennia + evennia._init() from django.conf import settings @@ -37,6 +40,7 @@ class EvenniaTestSuiteRunner(DiscoverRunner): # remove testing flag after suite has run from django.conf import settings + settings._TEST_ENVIRONMENT = False super().teardown_test_environment(**kwargs) diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 7c5a537c19..9830befa7f 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -218,13 +218,15 @@ class AttributeProperty: """ value = self._default try: - value = self.at_get(getattr(instance, self.attrhandler_name).get( - key=self._key, - default=self._default, - category=self._category, - strattr=self._strattr, - raise_exception=self._autocreate, - )) + value = self.at_get( + getattr(instance, self.attrhandler_name).get( + key=self._key, + default=self._default, + category=self._category, + strattr=self._strattr, + raise_exception=self._autocreate, + ) + ) except AttributeError: if self._autocreate: # attribute didn't exist and autocreate is set diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index 7e0926156b..2803c8de26 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -96,6 +96,7 @@ class Tag(models.Model): # Handlers making use of the Tags model # + class TagProperty: """ Tag property descriptor. Allows for setting tags on an object as Django-like 'fields' @@ -112,6 +113,7 @@ class TagProperty: mytag2 = TagProperty(category="tagcategory") """ + taghandler_name = "tags" def __init__(self, category=None, data=None): @@ -134,10 +136,7 @@ class TagProperty: """ try: return getattr(instance, self.taghandler_name).get( - key=self._key, - category=self._category, - return_list=False, - raise_exception=True + key=self._key, category=self._category, return_list=False, raise_exception=True ) except AttributeError: self.__set__(instance, self._category) @@ -150,9 +149,7 @@ class TagProperty: self._category = category ( getattr(instance, self.taghandler_name).add( - key=self._key, - category=self._category, - data=self._data + key=self._key, category=self._category, data=self._data ) ) @@ -430,8 +427,15 @@ class TagHandler(object): return ret[0] if len(ret) == 1 else ret - def get(self, key=None, default=None, category=None, return_tagobj=False, return_list=False, - raise_exception=False): + def get( + self, + key=None, + default=None, + category=None, + return_tagobj=False, + return_list=False, + raise_exception=False, + ): """ Get the tag for the given key, category or combination of the two. @@ -613,6 +617,7 @@ class AliasProperty(TagProperty): bob = AliasProperty() """ + taghandler_name = "aliases" @@ -636,6 +641,7 @@ class PermissionProperty(TagProperty): myperm = PermissionProperty() """ + taghandler_name = "permissions" diff --git a/evennia/typeclasses/tests.py b/evennia/typeclasses/tests.py index 90c4945898..d97e7c3b5e 100644 --- a/evennia/typeclasses/tests.py +++ b/evennia/typeclasses/tests.py @@ -145,7 +145,9 @@ class TestTypedObjectManager(BaseEvenniaTest): def test_get_tag_with_any_including_nones(self): self.obj1.tags.add("tagA", "categoryA") self.assertEqual( - self._manager("get_by_tag", ["tagA", "tagB"], ["categoryA", "categoryB", None], match="any"), + self._manager( + "get_by_tag", ["tagA", "tagB"], ["categoryA", "categoryB", None], match="any" + ), [self.obj1], ) diff --git a/evennia/utils/evmenu.py b/evennia/utils/evmenu.py index 4dae1b700d..060d47c140 100644 --- a/evennia/utils/evmenu.py +++ b/evennia/utils/evmenu.py @@ -1255,12 +1255,12 @@ class EvMenu: min_rows = 4 # split the items into columns - split = max(min_rows, ceil(len(table)/ncols)) + split = max(min_rows, ceil(len(table) / ncols)) max_end = len(table) cols_list = [] for icol in range(ncols): - start = icol*split - end = min(start+split,max_end) + start = icol * split + end = min(start + split, max_end) cols_list.append(EvColumn(*table[start:end])) return str(EvTable(table=cols_list, border="none")) diff --git a/evennia/utils/funcparser.py b/evennia/utils/funcparser.py index 54fc64b0c8..7186ff47b0 100644 --- a/evennia/utils/funcparser.py +++ b/evennia/utils/funcparser.py @@ -410,7 +410,7 @@ class FuncParser: single_quoted = -1 elif single_quoted > 0: prefix = infuncstr[0:single_quoted] - infuncstr = prefix + infuncstr[single_quoted+1:] + infuncstr = prefix + infuncstr[single_quoted + 1 :] single_quoted = -1 else: infuncstr += char @@ -427,7 +427,7 @@ class FuncParser: double_quoted = -1 elif double_quoted > 0: prefix = infuncstr[0:double_quoted] - infuncstr = prefix + infuncstr[double_quoted + 1:] + infuncstr = prefix + infuncstr[double_quoted + 1 :] double_quoted = -1 else: infuncstr += char diff --git a/evennia/utils/gametime.py b/evennia/utils/gametime.py index ab14c77847..9b915b368b 100644 --- a/evennia/utils/gametime.py +++ b/evennia/utils/gametime.py @@ -67,7 +67,7 @@ class TimeScript(DefaultScript): callback(*args, **kwargs) seconds = real_seconds_until(**self.db.gametime) - self.start(interval=seconds,force_restart=True) + self.start(interval=seconds, force_restart=True) # Access functions diff --git a/evennia/utils/logger.py b/evennia/utils/logger.py index ea0728a501..98bc5a499d 100644 --- a/evennia/utils/logger.py +++ b/evennia/utils/logger.py @@ -50,6 +50,7 @@ def _log(msg, logfunc, prefix="", **kwargs): # log call functions (each has legacy aliases) + def log_info(msg, **kwargs): """ Logs any generic debugging/informative info that should appear in the log. @@ -62,6 +63,7 @@ def log_info(msg, **kwargs): """ _log(msg, log.info, **kwargs) + info = log_info log_infomsg = log_info log_msg = log_info @@ -79,6 +81,7 @@ def log_warn(msg, **kwargs): """ _log(msg, log.warn, **kwargs) + warn = log_warn warning = log_warn log_warnmsg = log_warn @@ -120,6 +123,7 @@ def log_trace(msg=None, **kwargs): if msg: _log(msg, log.error, prefix="!!", **kwargs) + log_tracemsg = log_trace exception = log_trace critical = log_trace @@ -156,6 +160,7 @@ def log_sec(msg, **kwargs): """ _log(msg, log.info, prefix="SS", **kwargs) + sec = log_sec security = log_sec log_secmsg = log_sec @@ -174,12 +179,12 @@ def log_server(msg, **kwargs): _log(msg, log.info, prefix="Server", **kwargs) - class GetLogObserver: """ Sets up how the system logs are formatted. """ + component_prefix = "" event_levels = { twisted_logger.LogLevel.debug: "??", @@ -207,8 +212,7 @@ class GetLogObserver: event["log_format"] = str(event.get("log_format", "")) component_prefix = self.component_prefix or "" log_msg = twisted_logger.formatEventAsClassicLogText( - event, - formatTime=lambda e: twisted_logger.formatTime(e, _TIME_FORMAT) + event, formatTime=lambda e: twisted_logger.formatTime(e, _TIME_FORMAT) ) return f"{component_prefix}{log_msg}" @@ -218,14 +222,15 @@ class GetLogObserver: # Called by server/portal on startup + class GetPortalLogObserver(GetLogObserver): component_prefix = "|Portal| " + class GetServerLogObserver(GetLogObserver): component_prefix = "" - # logging overrides @@ -352,6 +357,7 @@ class WeeklyLogFile(logfile.DailyLogFile): self.lastDate = max(self.lastDate, self.toDate()) self.size += len(data) + # Arbitrary file logger diff --git a/evennia/utils/test_resources.py b/evennia/utils/test_resources.py index 65b62ea633..53d054de37 100644 --- a/evennia/utils/test_resources.py +++ b/evennia/utils/test_resources.py @@ -96,7 +96,6 @@ DEFAULT_SETTING_RESETS = dict( "evennia.game_template.server.conf.prototypefuncs", ], BASE_GUEST_TYPECLASS="evennia.accounts.accounts.DefaultGuest", - # a special setting boolean _TEST_ENVIRONMENT is set by the test runner # while the test suite is running. ) diff --git a/evennia/utils/tests/test_dbserialize.py b/evennia/utils/tests/test_dbserialize.py index 5cd5ffceda..d4fd101ef0 100644 --- a/evennia/utils/tests/test_dbserialize.py +++ b/evennia/utils/tests/test_dbserialize.py @@ -119,14 +119,17 @@ class TestDbSerialize(TestCase): class _InvalidContainer: """Container not saveable in Attribute (if obj is dbobj, it 'hides' it)""" + def __init__(self, obj): self.hidden_obj = obj class _ValidContainer(_InvalidContainer): """Container possible to save in Attribute (handles hidden dbobj explicitly)""" + def __serialize_dbobjs__(self): self.hidden_obj = dbserialize.dbserialize(self.hidden_obj) + def __deserialize_dbobjs__(self): self.hidden_obj = dbserialize.dbunserialize(self.hidden_obj) @@ -136,6 +139,7 @@ class DbObjWrappers(TestCase): Test the `__serialize_dbobjs__` and `__deserialize_dbobjs__` methods. """ + def setUp(self): super().setUp() self.dbobj1 = DefaultObject(db_key="Tester1") @@ -148,7 +152,7 @@ class DbObjWrappers(TestCase): self.dbobj1.db.testarg = _InvalidContainer(self.dbobj1) def test_consecutive_fetch(self): - con =_ValidContainer(self.dbobj2) + con = _ValidContainer(self.dbobj2) self.dbobj1.db.testarg = con attrobj = self.dbobj1.attributes.get("testarg", return_obj=True) @@ -157,7 +161,7 @@ class DbObjWrappers(TestCase): self.assertEqual(attrobj.value.hidden_obj, self.dbobj2) def test_dbobj_hidden_obj__success(self): - con =_ValidContainer(self.dbobj2) + con = _ValidContainer(self.dbobj2) self.dbobj1.db.testarg = con # accessing the same data twice diff --git a/evennia/utils/tests/test_funcparser.py b/evennia/utils/tests/test_funcparser.py index 3f6e6eb2f9..1296f7899b 100644 --- a/evennia/utils/tests/test_funcparser.py +++ b/evennia/utils/tests/test_funcparser.py @@ -116,7 +116,7 @@ class TestFuncParser(TestCase): ("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"), (r"Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, too)"), ("Test args4 $foo('')", "Test args4 _test()"), - ('Test args4 $foo("")', 'Test args4 _test()'), + ('Test args4 $foo("")', "Test args4 _test()"), ("Test args5 $foo(\(\))", "Test args5 _test(())"), ("Test args6 $foo(\()", "Test args6 _test(()"), ("Test args7 $foo(())", "Test args7 _test(())"), @@ -183,14 +183,20 @@ class TestFuncParser(TestCase): ("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"), ("Test eval3 $eval(\"'21' + 'foo' + 'bar'\")", "Test eval3 21foobar"), (r"Test eval4 $eval(\'21\' + \'$repl()\' + \"''\" + str(10 // 2))", "Test eval4 21rr5"), - (r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))", "Test eval5 21$repl()5"), + ( + r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))", + "Test eval5 21$repl()5", + ), ("Test eval6 $eval(\"'$repl(a)' + '$repl(b)'\")", "Test eval6 rarrbr"), ("Test type1 $typ([1,2,3,4])", "Test type1 "), ("Test type2 $typ((1,2,3,4))", "Test type2 "), ("Test type3 $typ({1,2,3,4})", "Test type3 "), ("Test type4 $typ({1:2,3:4})", "Test type4 "), ("Test type5 $typ(1), $typ(1.0)", "Test type5 , "), - ("Test type6 $typ(\"'1'\"), $typ('\"1.0\"')", "Test type6 , "), + ( + "Test type6 $typ(\"'1'\"), $typ('\"1.0\"')", + "Test type6 , ", + ), ("Test add1 $add(1, 2)", "Test add1 3"), ("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"), ("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"), @@ -198,7 +204,6 @@ class TestFuncParser(TestCase): ("Test literal3 $typ($lit(1)aaa)", "Test literal3 "), ("Test literal4 $typ(aaa$lit(1))", "Test literal4 "), ("Test spider's thread", "Test spider's thread"), - ] ) def test_parse(self, string, expected): @@ -398,7 +403,7 @@ class TestDefaultCallables(TestCase): ("Some $rjust(Hello, width=30)", "Some Hello"), ("Some $cjust(Hello, 30)", "Some Hello "), ("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"), - ("$crop(\"spider's silk\", 5)", "spide"), + ('$crop("spider\'s silk", 5)', "spide"), ] ) def test_other_callables(self, string, expected): @@ -468,13 +473,14 @@ class TestDefaultCallables(TestCase): def test_escaped2(self): raw_str = 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)' - expected = 'this should be escaped, and instead, cropped with text. ' + expected = "this should be escaped, and instead, cropped with text. " result = self.parser.parse(raw_str) self.assertEqual( result, expected, ) + class TestCallableSearch(test_resources.BaseEvenniaTest): """ Test the $search(query) callable diff --git a/evennia/utils/tests/test_text2html.py b/evennia/utils/tests/test_text2html.py index 361ab086cf..cb99ab8bd7 100644 --- a/evennia/utils/tests/test_text2html.py +++ b/evennia/utils/tests/test_text2html.py @@ -12,7 +12,9 @@ class TestText2Html(TestCase): self.assertEqual("foo", parser.format_styles("foo")) self.assertEqual( 'redfoo', - parser.format_styles(ansi.ANSI_UNHILITE + ansi.ANSI_RED + "red" + ansi.ANSI_NORMAL + "foo"), + parser.format_styles( + ansi.ANSI_UNHILITE + ansi.ANSI_RED + "red" + ansi.ANSI_NORMAL + "foo" + ), ) self.assertEqual( 'redfoo', @@ -31,33 +33,15 @@ class TestText2Html(TestCase): ) self.assertEqual( 'a redfoo', - parser.format_styles( - "a " - + ansi.ANSI_UNDERLINE - + "red" - + ansi.ANSI_NORMAL - + "foo" - ), + parser.format_styles("a " + ansi.ANSI_UNDERLINE + "red" + ansi.ANSI_NORMAL + "foo"), ) self.assertEqual( 'a redfoo', - parser.format_styles( - "a " - + ansi.ANSI_BLINK - + "red" - + ansi.ANSI_NORMAL - + "foo" - ), + parser.format_styles("a " + ansi.ANSI_BLINK + "red" + ansi.ANSI_NORMAL + "foo"), ) self.assertEqual( 'a redfoo', - parser.format_styles( - "a " - + ansi.ANSI_INVERSE - + "red" - + ansi.ANSI_NORMAL - + "foo" - ), + parser.format_styles("a " + ansi.ANSI_INVERSE + "red" + ansi.ANSI_NORMAL + "foo"), ) def test_remove_bells(self): @@ -65,13 +49,7 @@ class TestText2Html(TestCase): self.assertEqual("foo", parser.remove_bells("foo")) self.assertEqual( "a red" + ansi.ANSI_NORMAL + "foo", - parser.remove_bells( - "a " - + ansi.ANSI_BEEP - + "red" - + ansi.ANSI_NORMAL - + "foo" - ), + parser.remove_bells("a " + ansi.ANSI_BEEP + "red" + ansi.ANSI_NORMAL + "foo"), ) def test_remove_backspaces(self): @@ -160,20 +138,20 @@ class TestText2Html(TestCase): self.assertEqual( text2html.parse_html("|^|[CHello|n|u|rW|go|yr|bl|md|c!|[G!"), '' - 'Hello' + "Hello" '' - 'W' + "W" '' - 'o' + "o" '' - 'r' + "r" '' - 'l' + "l" '' - 'd' + "d" '' - '!' + "!" '' - '!' - '', + "!" + "", ) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index d603a93bd2..10f8073e31 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2695,6 +2695,7 @@ def copy_word_case(base_word, new_word): + excess ) + def run_in_main_thread(function_or_method, *args, **kwargs): """ Force a callable to execute in the main Evennia thread. This is only relevant when From 87d77c6105b0574e4785046916b4c1e3e9ae2a93 Mon Sep 17 00:00:00 2001 From: Avalon Hope <96471977+avalonhope@users.noreply.github.com> Date: Fri, 17 Jun 2022 09:35:02 +0100 Subject: [PATCH 08/26] Adding unit tests for script search --- evennia/utils/tests/test_search.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 evennia/utils/tests/test_search.py diff --git a/evennia/utils/tests/test_search.py b/evennia/utils/tests/test_search.py new file mode 100644 index 0000000000..6bacfb4cb3 --- /dev/null +++ b/evennia/utils/tests/test_search.py @@ -0,0 +1,29 @@ +from evennia.scripts.scripts import DefaultScript +from evennia.utils.test_resources import EvenniaTest +from evennia.utils.search import search_script_tag + +class TestSearch(EvenniaTest): + + def test_search_script_tag(self): + """Check that a script can be found by its tag.""" + script, errors = DefaultScript.create("a-script") + script.tags.add("a-tag") + found = search_script_tag("a-tag") + self.assertEqual(len(found), 1, errors) + self.assertEqual(script.key, found[0].key, errors) + + def test_search_script_tag_category(self): + """Check that a script can be found by its tag and category.""" + script, errors = DefaultScript.create("a-script") + script.tags.add("a-tag", category="a-category") + found = search_script_tag("a-tag", category="a-category") + self.assertEqual(len(found), 1, errors) + self.assertEqual(script.key, found[0].key, errors) + + def test_search_script_tag_wrong_category(self): + """Check that a script can be found by its tag and category.""" + script, errors = DefaultScript.create("a-script") + script.tags.add("a-tag", category="a-category") + found = search_script_tag("a-tag", category="wrong-category") + self.assertEqual(len(found), 0, errors) + From ef6080ea43ff5edbad8d15f58521ad3bf4bb6dd1 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 17 Jun 2022 13:17:36 -0600 Subject: [PATCH 09/26] Update and reorganize Links --- docs/source/Links.md | 216 ++++++++++++++++++------------------------- 1 file changed, 92 insertions(+), 124 deletions(-) diff --git a/docs/source/Links.md b/docs/source/Links.md index 514f225a92..89cfd176c8 100644 --- a/docs/source/Links.md +++ b/docs/source/Links.md @@ -2,127 +2,121 @@ *A list of resources that may be useful for Evennia users and developers.* -## Official Evennia links +## Official Evennia resources - [evennia.com](https://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](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) -- Our official IRC chat #evennia at irc.freenode.net:6667. -- [Evennia forums/mailing list](https://groups.google.com/group/evennia) - Web interface to our -google group. -- [Evennia development blog](https://evennia.blogspot.com/) - Musings from the lead developer. - [Evennia's manual on ReadTheDocs](https://readthedocs.org/projects/evennia/) - Read and download offline in html, PDF or epub formats. -- [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games. ----- +- [Evennia development blog](https://evennia.blogspot.com/) - Musings from the lead developer. +- [Evennia on GitHub](https://github.com/evennia/evennia) - Download code and read documentation. - [Evennia on Open Hub](https://www.openhub.net/p/6906) - [Evennia on OpenHatch](https://openhatch.org/projects/Evennia) - [Evennia on PyPi](https://pypi.python.org/pypi/Evennia-MUD-Server/) -- [Evennia subreddit](https://www.reddit.com/r/Evennia/) (not much there yet though) -## Third-party Evennia utilities and resources +## Evennia Community -*For publicly available games running on Evennia, add and find those in the [Evennia game -index](http://games.evennia.com) instead!* +- [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games. +- [Evennia official Discord channel](https://discord.gg/AJJpcRUhtF) +- [Evennia official IRC +channel](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) ( #evennia at [irc.freenode.net:6667](http://irc.freenode.net:6667/) ) +- [Evennia official forums](https://github.com/evennia/evennia/discussions) on Github Discussions. +- [Evennia subreddit](https://www.reddit.com/r/Evennia/) -- [Discord Evennia channel](https://discord.gg/NecFePw) - This is a fan-driven Discord channel with -a bridge to the official Evennia IRC channel. +## Third-party Evennia tools ---- - -- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the -_Blackbirds_ Evennia game project. -- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin) - an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine. -- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON -27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an -Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and -challenges [used during the conference](https://dcdark.net/home#). -- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular -[Arx](https://play.arxmush.org/) Evennia game. [Here are instructions for installing](Arxcode- -installing-help) -- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your -website. -- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional -coloration for Evennia unit-test output. -- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for -telnet/web). -- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for -Evennia with things like races, combat etc. [Summary -here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/). -- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source -turn-based battle system for Evennia. It also has a [live demo](http://wcb.battlestudio.com/). -- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people -to make [RPI](https://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games. -- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an -older fork of Evennia. It has some specific design goals for building and extending the game based -on input files. -- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`) -files in the [vim](https://www.vim.org/) text editor (Emacs users can use [evennia- +- [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker container](https://www.docker.com/) for quick install and deployment in just a few commands. +- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your website. +- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional coloration for Evennia unit-test output. +- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for telnet/web). +- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source turn-based battle system for an older version of Evennia. +- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people to make [RPI](https://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games. +- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin) an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine. +- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`) files in the [vim](https://www.vim.org/) text editor (Emacs users can use [evennia- mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)). - [Other Evennia-related repos on github](https://github.com/search?p=1&q=evennia) + ---- -- [EvCast video series](https://www.youtube.com/playlist?list=PLyYMNttpc-SX1hvaqlUNmcxrhmM64pQXl) - -Tutorial videos explaining installing Evennia, basic Python etc. -- [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker -container](https://www.docker.com/) for quick install and deployment in just a few commands. -- [Evennia's docs in Chinese](http://www.evenniacn.com/) - A translated mirror of a slightly older -Evennia version. Announcement [here](https://groups.google.com/forum/#!topic/evennia/3AXS8ZTzJaA). -- [Evennia for MUSHers](https://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing -Evennia for those used to the MUSH way of doing things. -- *[Language Understanding for Text games using Deep reinforcement -learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)* -([PDF](https://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia -to train AIs. -## Other useful mud development resources +## Evennia-Based Projects -- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to -Python objects. -- [Gossip MUD chat network](https://gossip.haus/) +### Code bases +- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular [Arx](https://play.arxmush.org/) Evennia game. [Here are instructions for installing](https://www.evennia.com/docs/1.0-dev/Howtos/Arxcode-Installation.html) +- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON 27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and challenges [used during the conference](https://dcdark.net/home#). +- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an older fork of Evennia. It has some specific design goals for building and extending the game based on input files. +- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for an older version of Evennia with things like races, combat etc. [Summary here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/). -## General MUD forums and discussions +### Other -- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack - channel](https://slack.mudcoders.com/) with discussions on MUD development. -- [MuSoapbox](https://www.musoapbox.net/) - Very active Mu* game community mainly focused on MUSH-type gaming. -- [Imaginary Realities](http://journal.imaginary-realities.com/) - An e-magazine on game and MUD - design that has several articles about Evennia. There is also an - [archive of older issues](http://disinterest.org/resource/imaginary-realities/) - from 1998-2001 that are still very relevant. -- [Optional Realities](http://optionalrealities.com/) - Mud development discussion forums that has - regular articles on MUD development focused on roleplay-intensive games. After a HD crash it's not - as content-rich as it once was. +- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the _Blackbirds_ Evennia game project. +- [Evennia for MUSHers](https://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing Evennia for those used to the MUSH way of doing things. +- *[Language Understanding for Text games using Deep reinforcement learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)* +([PDF](https://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia to train AIs. + +---- + +## General MU* resources + +### Tools + +- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to Python objects. + +### Informational + +- [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 ...](https://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](https://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) - Contains a very useful list of things to think about when starting your new MUD. +- Mud Dev mailing list archive ([mirror](http://www.disinterest.org/resource/MUD-Dev/)) - Influential mailing list active 1996-2004. Advanced game design discussions. +- [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). +- [Imaginary Realities unofficial archive](http://tharsis-gate.org/articles/imaginary.html) - An e-magazine on game and MUD design that has several articles about Evennia. + +### Community + +- [Grapevine MUD community and chat network](https://grapevine.haus/) +- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack channel](https://slack.mudcoders.com/) with discussions on MUD development. +- [MuSoapbox](https://musoapbox.net/) - MU* forum mainly focused on MUSH-type gaming. - [MudLab](http://mudlab.org/) - Mud design discussion forum - [MudConnector](http://www.mudconnect.com/) - Mud listing and forums - [MudBytes](http://www.mudbytes.net/) - Mud listing and forums - [Top Mud Sites](http://www.topmudsites.com/) - Mud listing and forums -- [Planet Mud-Dev](http://planet-muddev.disinterest.org/) - A blog aggregator following blogs of - current MUD development (including Evennia) around the 'net. Worth to put among your RSS - subscriptions. -- Mud Dev mailing list archive ([mirror](http://www.disinterest.org/resource/MUD-Dev/)) - - 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 ...](https://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](https://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) - -Contains a very useful list of things to think about when starting your new MUD. -- [Lost Garden](http://www.lostgarden.com/) - A game development blog with long and interesting - articles (not MUD-specific) -- [What Games Are](http://whatgamesare.com/) - A blog about general game design (not MUD-specific) -- [The Alexandrian](https://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 when designing a virtual multiplayer world - (Raph is known for *Ultima Online* among other things). -## Literature +---- + +## General Game-Dev Resources + +### Tools + +- [GIT](https://git-scm.com/) + - [Documentation](https://git-scm.com/documentation) + - [Learn GIT in 15 minutes](https://try.github.io/levels/1/challenges/1) (interactive tutorial) + +### Frameworks + +- [Django's homepage](https://www.djangoproject.com/) + - [Documentation](https://docs.djangoproject.com/en) + - [Code](https://code.djangoproject.com/) +- [Twisted homepage](https://twistedmatrix.com/) + - [Documentation](https://twistedmatrix.com/documents/current/core/howto/index.html) + - [Code](https://twistedmatrix.com/trac/browser) + +### Learning Python + +- [Python Website](https://www.python.org/) + - [Documentation](https://www.python.org/doc/) + - [Tutorial](https://docs.python.org/tut/tut.html) + - [Library Reference](https://docs.python.org/lib/lib.html) + - [Language Reference](https://docs.python.org/ref/ref.html) +- [Python tips and tricks](https://www.siafoo.net/article/52) +- [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) - free online programming curriculum for different skill levels + +### Blogs + +- [Lost Garden](https://lostgarden.home.blog/) - A game development blog with long and interesting articles (not MUD-specific) +- [What Games Are](http://whatgamesare.com/) - A blog about general game design (not MUD-specific) +- [The Alexandrian](https://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. + +### Literature - Richard Bartle *Designing Virtual Worlds* ([amazon page](https://www.amazon.com/Designing-Virtual-Worlds-Richard-Bartle/dp/0131018167)) - @@ -148,29 +142,3 @@ Contains a very useful list of things to think about when starting your new MUD. economic theory. Written in 1730 but the translation is annotated and the essay is actually very easy to follow also for a modern reader. Required reading if you think of implementing a sane game economic system. - -## Frameworks - -- [Django's homepage](https://www.djangoproject.com/) - - [Documentation](https://docs.djangoproject.com/en) - - [Code](https://code.djangoproject.com/) -- [Twisted homepage](https://twistedmatrix.com/) - - [Documentation](https://twistedmatrix.com/documents/current/core/howto/index.html) - - [Code](https://twistedmatrix.com/trac/browser) - -## Tools - -- [GIT](https://git-scm.com/) - - [Documentation](https://git-scm.com/documentation) - - [Learn GIT in 15 minutes](https://try.github.io/levels/1/challenges/1) (interactive tutorial) - -## Python Info - -- [Python Website](https://www.python.org/) - - [Documentation](https://www.python.org/doc/) - - [Tutorial](https://docs.python.org/tut/tut.html) - - [Library Reference](https://docs.python.org/lib/lib.html) - - [Language Reference](https://docs.python.org/ref/ref.html) -- [Python tips and tricks](https://www.siafoo.net/article/52) -- [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) - - free online programming curriculum for different skill levels From 342c8a6218b2ba53c6b0528f79ed16164eafd746 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 17 Jun 2022 13:22:32 -0600 Subject: [PATCH 10/26] match line-item syntax, alphabetize --- docs/source/Links.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/source/Links.md b/docs/source/Links.md index 89cfd176c8..75f3cfe2ba 100644 --- a/docs/source/Links.md +++ b/docs/source/Links.md @@ -24,15 +24,15 @@ channel](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUm ## Third-party Evennia tools +- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional coloration for Evennia unit-test output. - [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker container](https://www.docker.com/) for quick install and deployment in just a few commands. - [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your website. -- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional coloration for Evennia unit-test output. - [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for telnet/web). -- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source turn-based battle system for an older version of Evennia. - [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people to make [RPI](https://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games. - [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin) an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine. - [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`) files in the [vim](https://www.vim.org/) text editor (Emacs users can use [evennia- mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)). +- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source turn-based battle system for an older version of Evennia. - [Other Evennia-related repos on github](https://github.com/search?p=1&q=evennia) ---- @@ -41,9 +41,9 @@ mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mo ### Code bases - [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular [Arx](https://play.arxmush.org/) Evennia game. [Here are instructions for installing](https://www.evennia.com/docs/1.0-dev/Howtos/Arxcode-Installation.html) +- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for an older version of Evennia with things like races, combat etc. [Summary here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/). - [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON 27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and challenges [used during the conference](https://dcdark.net/home#). - [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an older fork of Evennia. It has some specific design goals for building and extending the game based on input files. -- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for an older version of Evennia with things like races, combat etc. [Summary here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/). ### Other @@ -62,24 +62,23 @@ mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mo ### Informational -- [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 ...](https://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](https://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) - Contains a very useful list of things to think about when starting your new MUD. -- Mud Dev mailing list archive ([mirror](http://www.disinterest.org/resource/MUD-Dev/)) - Influential mailing list active 1996-2004. Advanced game design discussions. -- [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). - [Imaginary Realities unofficial archive](http://tharsis-gate.org/articles/imaginary.html) - An e-magazine on game and MUD design that has several articles about Evennia. +- [Lost Library of MOO](https://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in particular moo). +- [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD telnet protocols. +- [Mud-dev wiki](http://mud-dev.wikidot.com/) - A (very) slowly growing resource on MUD creation. +- [Mud Tech's fun/cool but ...](https://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-ship-damned-thing/) - Greg Taylor gives good advice on mud design. +- [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) - Contains a very useful list of things to think about when starting your new MUD. +- [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). ### Community -- [Grapevine MUD community and chat network](https://grapevine.haus/) +- [Grapevine](https://grapevine.haus/) - MUD listings and inter-game chat network - [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack channel](https://slack.mudcoders.com/) with discussions on MUD development. +- [MudBytes](http://www.mudbytes.net/) - MUD listing and forums +- [MudConnector](http://www.mudconnect.com/) - MUD listing and forums +- [MudLab](http://mudlab.org/) - MUD design discussion forum - [MuSoapbox](https://musoapbox.net/) - MU* forum mainly focused on MUSH-type gaming. -- [MudLab](http://mudlab.org/) - Mud design discussion forum -- [MudConnector](http://www.mudconnect.com/) - Mud listing and forums -- [MudBytes](http://www.mudbytes.net/) - Mud listing and forums -- [Top Mud Sites](http://www.topmudsites.com/) - Mud listing and forums +- [Top Mud Sites](http://www.topmudsites.com/) - MUD listing and forums ---- From 8c612fd3e3214d5caf0cc6eca131866c36f3d233 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 17 Jun 2022 13:25:09 -0600 Subject: [PATCH 11/26] add discord relay --- docs/source/Links.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/Links.md b/docs/source/Links.md index 75f3cfe2ba..d5204156e3 100644 --- a/docs/source/Links.md +++ b/docs/source/Links.md @@ -24,6 +24,7 @@ channel](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUm ## Third-party Evennia tools +- [Discord relay](https://github.com/InspectorCaracal/evennia-things/tree/main/discord_relay) - Two-way chat relays between Evennia channels and Discord channels. - [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional coloration for Evennia unit-test output. - [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker container](https://www.docker.com/) for quick install and deployment in just a few commands. - [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your website. From 1e66de58aba8895bad9e00ddab40a50a6ea7ed7b Mon Sep 17 00:00:00 2001 From: Hellgheast Date: Mon, 20 Jun 2022 18:45:20 +0200 Subject: [PATCH 12/26] Update Building-Quickstart.md Changing the description command to desc (which exist in dev-1.0) --- .../Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md b/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md index 3407ece825..f4fa3f3821 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md @@ -115,7 +115,7 @@ Try to `look` at the box to see the (default) description. The description you get is not very exciting. Let's add some flavor. - describe box = This is a large and very heavy box. + desc box = This is a large and very heavy box. If you try the `get` command we will pick up the box. So far so good, but if we really want this to be a large and heavy box, people should _not_ be able to run off with it that easily. To prevent From 4fdbae04c22739e571f115cf7f5cf11444d3c5b1 Mon Sep 17 00:00:00 2001 From: Hellgheast Date: Mon, 20 Jun 2022 19:05:18 +0200 Subject: [PATCH 13/26] doc: Fixing bodyfunctions script call --- .../Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md b/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md index f4fa3f3821..49748d4812 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md @@ -155,10 +155,10 @@ later, in the [Commands tutorial](./Adding-Commands.md). [Scripts](../../../Components/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` +on ourselves. There is an example script in `evennia/contrib/tutorials/bodyfunctions/bodyfunctions.py` that is called `BodyFunctions`. To add this to us we will use the `script` command: - script self = tutorial_examples.bodyfunctions.BodyFunctions + script self = tutorials.bodyfunctions.BodyFunctions This string will tell Evennia to dig up the Python code at the place we indicate. It already knows to look in the `contrib/` folder, so we don't have to give the full path. @@ -179,7 +179,7 @@ output every time it fires. When you are tired of your character's "insights", kill the script with - script/stop self = tutorial_examples.bodyfunctions.BodyFunctions + script/stop self = tutorials.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](../../../Components/Scripts.md) page explains more details. From 87402e1c8af7ebdc8452fe7c1ee07f794ae8aeda Mon Sep 17 00:00:00 2001 From: Hellgheast Date: Mon, 20 Jun 2022 19:18:16 +0200 Subject: [PATCH 14/26] doc: Correction of red_button calls --- .../Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md b/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md index 49748d4812..4140edff96 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md @@ -199,7 +199,7 @@ named simply `Object`. Let's create an object that is a little more interesting. Let's make us one of _those_! - create/drop button:tutorial_examples.red_button.RedButton + create/drop button:tutorials.red_button.RedButton The same way we did with the Script Earler, we specify a "Python-path" to the Python code we want Evennia to use for creating the object. There you go - one red button. From ac3830fdf82a9bd6aa92902233411b5ab7797db0 Mon Sep 17 00:00:00 2001 From: Hellgheast Date: Mon, 20 Jun 2022 19:38:20 +0200 Subject: [PATCH 15/26] doc : sethelp removed the /add switch --- .../Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md b/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md index 4140edff96..c1f8daa202 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part1/Building-Quickstart.md @@ -301,7 +301,7 @@ The Command-help is something you modify in Python code. We'll get to that when add Commands. But you can also add regular help entries, for example to explain something about the history of your game world: - sethelp/add History = At the dawn of time ... + sethelp History = At the dawn of time ... You will now find your new `History` entry in the `help` list and read your help-text with `help History`. From c10121bde21178fafce9a0f424b5ad8353c8f0ac Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Mon, 20 Jun 2022 13:32:33 -0600 Subject: [PATCH 16/26] remove IRC, update docker-compose --- docs/source/Links.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/source/Links.md b/docs/source/Links.md index d5204156e3..088ba072c6 100644 --- a/docs/source/Links.md +++ b/docs/source/Links.md @@ -17,19 +17,17 @@ offline in html, PDF or epub formats. - [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games. - [Evennia official Discord channel](https://discord.gg/AJJpcRUhtF) -- [Evennia official IRC -channel](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) ( #evennia at [irc.freenode.net:6667](http://irc.freenode.net:6667/) ) - [Evennia official forums](https://github.com/evennia/evennia/discussions) on Github Discussions. - [Evennia subreddit](https://www.reddit.com/r/Evennia/) ## Third-party Evennia tools - [Discord relay](https://github.com/InspectorCaracal/evennia-things/tree/main/discord_relay) - Two-way chat relays between Evennia channels and Discord channels. +- [docker-compose for Evennia](https://github.com/gtaylor/evennia-docker) - A quick-install setup for running Evennia in a [Docker container](https://www.docker.com/). (See [the official Evennia docs](https://www.evennia.com/docs/latest/Running-Evennia-in-Docker.html) for more details on running Evennia with Docker.) - [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional coloration for Evennia unit-test output. -- [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker container](https://www.docker.com/) for quick install and deployment in just a few commands. - [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your website. -- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for telnet/web). - [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people to make [RPI](https://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games. +- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for telnet/web). - [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin) an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine. - [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`) files in the [vim](https://www.vim.org/) text editor (Emacs users can use [evennia- mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)). From 66b3770f6e7a318a23b51b33677ba4384e80ad26 Mon Sep 17 00:00:00 2001 From: Hellgheast Date: Tue, 21 Jun 2022 11:28:21 +0200 Subject: [PATCH 17/26] Testing to fix the jinja2 problem --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index ab4da74f57..7b2280b898 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,7 @@ autobahn >= 20.7.1, < 21.0.0 lunr == 0.6.0 simpleeval <= 1.0 uritemplate == 4.1.1 +Jinja2<3.1 # try to resolve dependency issue in py3.7 attrs >= 19.2.0 From 2922c0c7d9cee2474b3bcb2068a355159d884302 Mon Sep 17 00:00:00 2001 From: Avalon Hope <96471977+avalonhope@users.noreply.github.com> Date: Tue, 21 Jun 2022 12:23:49 +0100 Subject: [PATCH 18/26] Update test_search.py --- evennia/utils/tests/test_search.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/evennia/utils/tests/test_search.py b/evennia/utils/tests/test_search.py index 6bacfb4cb3..ae9b9a9694 100644 --- a/evennia/utils/tests/test_search.py +++ b/evennia/utils/tests/test_search.py @@ -21,9 +21,16 @@ class TestSearch(EvenniaTest): self.assertEqual(script.key, found[0].key, errors) def test_search_script_tag_wrong_category(self): - """Check that a script can be found by its tag and category.""" + """Check that a script cannot be found by the wrong category.""" script, errors = DefaultScript.create("a-script") script.tags.add("a-tag", category="a-category") found = search_script_tag("a-tag", category="wrong-category") self.assertEqual(len(found), 0, errors) + + def test_search_script_tag_wrong(self): + """Check that a script cannot be found by the wrong tag.""" + script, errors = DefaultScript.create("a-script") + script.tags.add("a-tag", category="a-category") + found = search_script_tag("wrong-tag", category="a-category") + self.assertEqual(len(found), 0, errors) From a05ec09f51bb0d47998a93a848deb28349515ae7 Mon Sep 17 00:00:00 2001 From: "Cory F. Cohen" Date: Tue, 21 Jun 2022 21:07:11 -0400 Subject: [PATCH 19/26] Fix various typos in several files. All are in comments and docstrings, and none should be controversial in any way (e.g. British versus American spelling). --- evennia/accounts/accounts.py | 10 +++++----- evennia/commands/default/building.py | 6 +++--- evennia/commands/default/comms.py | 2 +- evennia/commands/default/help.py | 2 +- evennia/game_template/world/README.md | 2 +- evennia/game_template/world/prototypes.py | 4 ++-- evennia/server/sessionhandler.py | 2 +- evennia/utils/evmore.py | 4 ++-- evennia/utils/utils.py | 12 ++++++------ 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/evennia/accounts/accounts.py b/evennia/accounts/accounts.py index f779a4281f..4b566c7238 100644 --- a/evennia/accounts/accounts.py +++ b/evennia/accounts/accounts.py @@ -678,7 +678,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): typeclass (str, optional): Typeclass to use for this character. If not given, use settings.BASE_CHARACTER_TYPECLASS. permissions (list, optional): If not given, use the account's permissions. - ip (str, optiona): The client IP creating this character. Will fall back to the + ip (str, optional): The client IP creating this character. Will fall back to the one stored for the account if not given. kwargs (any): Other kwargs will be used in the create_call. Returns: @@ -955,7 +955,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): kwargs (any): Other keyword arguments will be added to the found command object instance as variables before it executes. This is unused by default Evennia but may be - used to set flags and change operating paramaters for + used to set flags and change operating parameters for commands at run-time. """ @@ -1433,7 +1433,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key)) if _MULTISESSION_MODE == 0: # in this mode we should have only one character available. We - # try to auto-connect to our last conneted object, if any + # try to auto-connect to our last connected object, if any try: self.puppet_object(session, self.db._last_puppet) except RuntimeError: @@ -1460,7 +1460,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase): """ Called by the login process if a user account is targeted correctly but provided with an invalid password. By default it does nothing, - but exists to be overriden. + but exists to be overridden. Args: session (session): Session logging in. @@ -1703,7 +1703,7 @@ class DefaultGuest(DefaultAccount): Gets or creates a Guest account object. Keyword Args: - ip (str, optional): IP address of requestor; used for ban checking, + ip (str, optional): IP address of requester; used for ban checking, throttling and logging Returns: diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 582c6434e4..56ee7b033d 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1071,7 +1071,7 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS): exitname, backshort = self.directions[exitshort] backname = self.directions[backshort][0] - # if we recieved a typeclass for the exit, add it to the alias(short name) + # if we received a typeclass for the exit, add it to the alias(short name) if ":" in self.lhs: # limit to only the first : character exit_typeclass = ":" + self.lhs.split(":", 1)[-1] @@ -1665,7 +1665,7 @@ class CmdSetAttribute(ObjManipCommand): def split_nested_attr(self, attr): """ Yields tuples of (possible attr name, nested keys on that attr). - For performance, this is biased to the deepest match, but allows compatability + For performance, this is biased to the deepest match, but allows compatibility with older attrs that might have been named with `[]`'s. > list(split_nested_attr("nested['asdf'][0]")) @@ -4031,7 +4031,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): ) return try: - # we homogenize the protoype first, to be more lenient with free-form + # we homogenize the prototype first, to be more lenient with free-form protlib.validate_prototype(protlib.homogenize_prototype(prototype)) except RuntimeError as err: self.caller.msg(str(err)) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index b150bdaf84..6552e5c745 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -1818,7 +1818,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS): class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS): """ - Link an Evennia channel to an exteral Grapevine channel + Link an Evennia channel to an external Grapevine channel Usage: grapevine2chan[/switches] = diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index 3ac6c4924a..e70fda3968 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -138,7 +138,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): click_topics=True, ): """This visually formats the help entry. - This method can be overriden to customize the way a help + This method can be overridden to customize the way a help entry is displayed. Args: diff --git a/evennia/game_template/world/README.md b/evennia/game_template/world/README.md index 0f3862dad4..0e4a2fe18f 100644 --- a/evennia/game_template/world/README.md +++ b/evennia/game_template/world/README.md @@ -1,6 +1,6 @@ # world/ -This folder is meant as a miscellanous folder for all that other stuff +This folder is meant as a miscellaneous folder for all that other stuff related to the game. Code which are not commands or typeclasses go here, like custom economy systems, combat code, batch-files etc. diff --git a/evennia/game_template/world/prototypes.py b/evennia/game_template/world/prototypes.py index 04aba091f3..8a05ed5f6c 100644 --- a/evennia/game_template/world/prototypes.py +++ b/evennia/game_template/world/prototypes.py @@ -28,7 +28,7 @@ Possible keywords are: - `prototype_key` - the name of the prototype. This is required for db-prototypes, for module-prototypes, the global variable name of the dict is used instead - `prototype_parent` - string pointing to parent prototype if any. Prototype inherits - in a similar way as classes, with children overriding values in their partents. + in a similar way as classes, with children overriding values in their parents. - `key` - string, the main object identifier. - `typeclass` - string, if not set, will use `settings.BASE_OBJECT_TYPECLASS`. - `location` - this should be a valid object or #dbref. @@ -42,7 +42,7 @@ Possible keywords are: of the shorter forms, defaults are used for the rest. - `tags` - Tags, as a list of tuples `(tag,)`, `(tag, category)` or `(tag, category, data)`. - Any other keywords are interpreted as Attributes with no category or lock. - These will internally be added to `attrs` (eqivalent to `(attrname, value)`. + These will internally be added to `attrs` (equivalent to `(attrname, value)`. See the `spawn` command and `evennia.prototypes.spawner.spawn` for more info. diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 7ccb302720..5e1bcf0830 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -683,7 +683,7 @@ class ServerSessionHandler(SessionHandler): Get a unique list of connected and logged-in Accounts. Returns: - accounts (list): All conected Accounts (which may be fewer than the + accounts (list): All connected Accounts (which may be fewer than the amount of Sessions due to multi-playing). """ diff --git a/evennia/utils/evmore.py b/evennia/utils/evmore.py index 9129db1298..da8f04ad5a 100644 --- a/evennia/utils/evmore.py +++ b/evennia/utils/evmore.py @@ -181,7 +181,7 @@ class EvMore(object): justify (bool, optional): If set, auto-justify long lines. This must be turned off for fixed-width or formatted output, like tables. It's force-disabled if `inp` is an EvTable. - justify_kwargs (dict, optional): Keywords for the justifiy function. Used only + justify_kwargs (dict, optional): Keywords for the justify function. Used only if `justify` is True. If this is not set, default arguments will be used. exit_on_lastpage (bool, optional): If reaching the last page without the page being completely filled, exit pager immediately. If unset, @@ -507,7 +507,7 @@ class EvMore(object): def page_formatter(self, page): """ Page formatter. Every page passes through this method. Override - it to customize behvaior per-page. A common use is to generate a new + it to customize behavior per-page. A common use is to generate a new EvTable for every page (this is more efficient than to generate one huge EvTable across many pages and feed it into EvMore all at once). diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 10f8073e31..b25e8da447 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -819,7 +819,7 @@ def latinify(string, default="?", pure_ascii=False): This is used as a last resort when normal encoding does not work. Arguments: - string (str): A string to convert to 'safe characters' convertable + string (str): A string to convert to 'safe characters' convertible to an latin-1 bytestring later. default (str, optional): Characters resisting mapping will be replaced with this character or string. The intent is to apply an encode operation @@ -1078,7 +1078,7 @@ def delay(timedelay, callback, *args, **kwargs): 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 + which is the task's id intended 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. @@ -1531,12 +1531,12 @@ def class_from_module(path, defaultpaths=None, fallback=None): defaultpaths (iterable, optional): If a direct import from `path` fails, try subsequent imports by prepending those paths to `path`. fallback (str): If all other attempts fail, use this path as a fallback. - This is intended as a last-resport. In the example of Evennia + This is intended as a last-resort. In the example of Evennia loading, this would be a path to a default parent class in the evennia repo itself. Returns: - class (Class): An uninstatiated class recovered from path. + class (Class): An uninstantiated class recovered from path. Raises: ImportError: If all loading failed. @@ -1675,7 +1675,7 @@ def string_partial_matching(alternatives, inp, ret_index=True): Matching is made from the start of each subword in each alternative. Case is not important. So e.g. "bi sh sw" or just "big" or "shiny" or "sw" will match "Big shiny sword". Scoring is - done to allow to separate by most common demoninator. You will get + done to allow to separate by most common denominator. You will get multiple matches returned if appropriate. Args: @@ -1749,7 +1749,7 @@ def format_table(table, extra_space=1): ftable = format_table([[1,2,3], [4,5,6]]) string = "" - for ir, row in enumarate(ftable): + for ir, row in enumerate(ftable): if ir == 0: # make first row white string += "\\n|w" + "".join(row) + "|n" From 7321173d3b16bc46bbcc7f898b086e3593d9b83e Mon Sep 17 00:00:00 2001 From: "Cory F. Cohen" Date: Tue, 21 Jun 2022 21:13:06 -0400 Subject: [PATCH 20/26] A couple more typos that are only on develop. --- evennia/commands/default/building.py | 2 +- evennia/commands/default/help.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 56ee7b033d..cecc701fe4 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -2832,7 +2832,7 @@ class CmdExamine(ObjManipCommand): objdata["Stored Cmdset(s)"] = self.format_stored_cmdsets(obj) objdata["Merged Cmdset(s)"] = self.format_merged_cmdsets(obj, current_cmdset) objdata[ - f"Commands vailable to {obj.key} (result of Merged Cmdset(s))" + f"Commands available to {obj.key} (result of Merged Cmdset(s))" ] = self.format_current_cmds(obj, current_cmdset) if self.object_type == "script": objdata["Description"] = self.format_script_desc(obj) diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index e70fda3968..bdbc62d2ad 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -67,7 +67,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): help // ... Use the 'help' command alone to see an index of all help topics, organized - by category.eSome big topics may offer additional sub-topics. + by category. Some big topics may offer additional sub-topics. """ From c619d03639c1981d1b9a1c072012f4c620d43bc3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 22 Jun 2022 08:25:17 +0200 Subject: [PATCH 21/26] Limit jinja2 version for sphinx error --- docs/requirements.txt | 1 + requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 683117213d..9f5d80a334 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,6 +3,7 @@ sphinx==3.2.1 myst-parser==0.15.2 myst-parser[linkify]==0.15.2 +Jinja2 < 3.1 # sphinx-multiversion with evennia fixes git+https://github.com/evennia/sphinx-multiversion.git@evennia-mods#egg=sphinx-multiversion diff --git a/requirements.txt b/requirements.txt index 7b2280b898..a08725076e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ autobahn >= 20.7.1, < 21.0.0 lunr == 0.6.0 simpleeval <= 1.0 uritemplate == 4.1.1 -Jinja2<3.1 +Jinja2 < 3.1 # try to resolve dependency issue in py3.7 attrs >= 19.2.0 From f8c6ca2797eefec6a013c802d5185842893b9cbb Mon Sep 17 00:00:00 2001 From: Avalon Hope <96471977+avalonhope@users.noreply.github.com> Date: Wed, 22 Jun 2022 09:38:57 +0100 Subject: [PATCH 22/26] Unit test for search_script_attribute --- evennia/utils/tests/test_search.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/evennia/utils/tests/test_search.py b/evennia/utils/tests/test_search.py index ae9b9a9694..f9ab2e2e4a 100644 --- a/evennia/utils/tests/test_search.py +++ b/evennia/utils/tests/test_search.py @@ -1,6 +1,6 @@ from evennia.scripts.scripts import DefaultScript from evennia.utils.test_resources import EvenniaTest -from evennia.utils.search import search_script_tag +from evennia.utils.search import search_script_attribute, search _script_tag class TestSearch(EvenniaTest): @@ -33,4 +33,21 @@ class TestSearch(EvenniaTest): script.tags.add("a-tag", category="a-category") found = search_script_tag("wrong-tag", category="a-category") self.assertEqual(len(found), 0, errors) + + def test_search_script_attribute(self): + """Check that a script can be found by its attributes.""" + script, errors = DefaultScript.create("a-script") + script.db.an_attribute = "some value" + found = search_script_attribute(key="an_attribute", value="some value") + self.assertEqual(len(found), 1, errors) + self.assertEqual(script.key, found[0].key, errors) + + def test_search_script_attribute_wrong(self): + """Check that a script can be found by its attributes.""" + script, errors = DefaultScript.create("a-script") + script.db.an_attribute = "some value" + found = search_script_attribute(key="an_attribute", value="wrong value") + self.assertEqual(len(found), 1, errors) + self.assertEqual(script.key, found[0].key, errors) + From 5db14e8c4de810e762d466034cf958558d212670 Mon Sep 17 00:00:00 2001 From: Avalon Hope <96471977+avalonhope@users.noreply.github.com> Date: Wed, 22 Jun 2022 09:39:45 +0100 Subject: [PATCH 23/26] Update test_search.py --- evennia/utils/tests/test_search.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/evennia/utils/tests/test_search.py b/evennia/utils/tests/test_search.py index f9ab2e2e4a..e1380f5402 100644 --- a/evennia/utils/tests/test_search.py +++ b/evennia/utils/tests/test_search.py @@ -43,11 +43,9 @@ class TestSearch(EvenniaTest): self.assertEqual(script.key, found[0].key, errors) def test_search_script_attribute_wrong(self): - """Check that a script can be found by its attributes.""" + """Check that a script cannot be found by wrong value of its attributes.""" script, errors = DefaultScript.create("a-script") script.db.an_attribute = "some value" found = search_script_attribute(key="an_attribute", value="wrong value") self.assertEqual(len(found), 1, errors) self.assertEqual(script.key, found[0].key, errors) - - From 975c38c0af4102f9e32f6f29bd41546d1128218e Mon Sep 17 00:00:00 2001 From: Avalon Hope <96471977+avalonhope@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:06:31 +0100 Subject: [PATCH 24/26] Update test_search.py --- evennia/utils/tests/test_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/utils/tests/test_search.py b/evennia/utils/tests/test_search.py index e1380f5402..55b57d0add 100644 --- a/evennia/utils/tests/test_search.py +++ b/evennia/utils/tests/test_search.py @@ -1,6 +1,6 @@ from evennia.scripts.scripts import DefaultScript from evennia.utils.test_resources import EvenniaTest -from evennia.utils.search import search_script_attribute, search _script_tag +from evennia.utils.search import search_script_attribute, search_script_tag class TestSearch(EvenniaTest): From 10a543d0239eea84c42e5449e5c89267bee8491e Mon Sep 17 00:00:00 2001 From: Avalon Hope <96471977+avalonhope@users.noreply.github.com> Date: Wed, 22 Jun 2022 10:30:13 +0100 Subject: [PATCH 25/26] Update test_search.py --- evennia/utils/tests/test_search.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/evennia/utils/tests/test_search.py b/evennia/utils/tests/test_search.py index 55b57d0add..63515d6754 100644 --- a/evennia/utils/tests/test_search.py +++ b/evennia/utils/tests/test_search.py @@ -47,5 +47,4 @@ class TestSearch(EvenniaTest): script, errors = DefaultScript.create("a-script") script.db.an_attribute = "some value" found = search_script_attribute(key="an_attribute", value="wrong value") - self.assertEqual(len(found), 1, errors) - self.assertEqual(script.key, found[0].key, errors) + self.assertEqual(len(found), 0, errors) From 7bc787d41f596367e3a374c3b8cf0c4549113d27 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 3 Jul 2022 11:11:52 +0200 Subject: [PATCH 26/26] Set echo option, probably no effect --- evennia/server/portal/telnet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 4d25696c50..5d764e34f7 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -226,6 +226,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): or option == naws.NAWS or option == MCCP or option == mssp.MSSP + or option == ECHO or option == suppress_ga.SUPPRESS_GA ) @@ -236,6 +237,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS): or option == naws.NAWS or option == MCCP or option == mssp.MSSP + or option == ECHO or option == suppress_ga.SUPPRESS_GA )