mirror of
https://github.com/evennia/evennia.git
synced 2026-03-27 18:26:32 +01:00
Fix merge conflicts
This commit is contained in:
commit
f4c3db1151
52 changed files with 649 additions and 417 deletions
16
README.md
16
README.md
|
|
@ -60,22 +60,22 @@ 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
|
||||
[gettingstarted]: http://github.com/evennia/evennia/wiki/Getting-Started
|
||||
[wiki]: https://github.com/evennia/evennia/wiki
|
||||
[homepage]: https://www.evennia.com
|
||||
[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
|
||||
[chat]: http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb
|
||||
[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
|
||||
|
|
|
|||
|
|
@ -152,8 +152,8 @@ mv-local:
|
|||
@echo "Documentation built (multiversion + autodocs)."
|
||||
@echo "To see result, open evennia/docs/build/html/<version>/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."
|
||||
|
|
|
|||
|
|
@ -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/* .
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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=<num>) in string"""
|
||||
"""This will be callable as $pow(number, power=<num>) 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,127 +2,119 @@
|
|||
|
||||
*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 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-
|
||||
- [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-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your website.
|
||||
- [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)).
|
||||
- [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)
|
||||
|
||||
----
|
||||
- [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)
|
||||
- [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.
|
||||
|
||||
## 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.
|
||||
- [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.
|
||||
- [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
|
||||
|
||||
- [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 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).
|
||||
- [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).
|
||||
|
||||
## Literature
|
||||
### Community
|
||||
|
||||
- [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.
|
||||
- [Top Mud Sites](http://www.topmudsites.com/) - MUD listing and forums
|
||||
|
||||
----
|
||||
|
||||
## 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 +140,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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)):
|
||||
|
|
|
|||
|
|
@ -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]"))
|
||||
|
|
@ -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
|
||||
|
|
@ -2830,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)
|
||||
|
|
@ -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:
|
||||
|
|
@ -4029,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))
|
||||
|
|
|
|||
|
|
@ -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] <evennia_channel> = <grapevine_channel>
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
help <topic>/<subtopic>/<subsubtopic> ...
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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):
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -197,7 +197,6 @@ class TestServer(TestCase):
|
|||
|
||||
|
||||
class TestInitHooks(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
from evennia.utils import create
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -85,8 +85,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
|
||||
|
|
@ -319,8 +319,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 {
|
||||
|
|
@ -331,6 +331,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:
|
||||
|
||||
|
|
@ -374,12 +375,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
|
||||
|
|
@ -402,19 +404,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
|
||||
|
|
@ -478,12 +502,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
|
||||
|
|
@ -494,6 +521,7 @@ class FuncParser:
|
|||
current_kwarg = ""
|
||||
exec_return = ""
|
||||
infuncstr = ""
|
||||
literal_infuncstr = False
|
||||
|
||||
if char == ")":
|
||||
# closing the function list - this means we have a
|
||||
|
|
@ -537,6 +565,7 @@ class FuncParser:
|
|||
if return_str:
|
||||
exec_return = ""
|
||||
infuncstr = ""
|
||||
literal_infuncstr = False
|
||||
continue
|
||||
|
||||
infuncstr += char
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,61 @@ 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)
|
||||
|
|
|
|||
|
|
@ -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,29 @@ 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 <class 'list'>"),
|
||||
("Test type2 $typ((1,2,3,4))", "Test type2 <class 'tuple'>"),
|
||||
("Test type3 $typ({1,2,3,4})", "Test type3 <class 'set'>"),
|
||||
("Test type4 $typ({1:2,3:4})", "Test type4 <class 'dict'>"),
|
||||
("Test type5 $typ(1), $typ(1.0)", "Test type5 <class 'int'>, <class 'float'>"),
|
||||
("Test type6 $typ('1'), $typ(\"1.0\")", "Test type6 <class 'str'>, <class 'str'>"),
|
||||
(
|
||||
"Test type6 $typ(\"'1'\"), $typ('\"1.0\"')",
|
||||
"Test type6 <class 'str'>, <class 'str'>",
|
||||
),
|
||||
("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 <class 'int'>"),
|
||||
("Test literal3 $typ($lit(1)aaa)", "Test literal3 <class 'str'>"),
|
||||
("Test literal4 $typ(aaa$lit(1))", "Test literal4 <class 'str'>"),
|
||||
("Test spider's thread", "Test spider's thread"),
|
||||
]
|
||||
)
|
||||
def test_parse(self, string, expected):
|
||||
|
|
@ -258,7 +266,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))
|
||||
|
||||
|
|
@ -398,6 +410,8 @@ class TestDefaultCallables(TestCase):
|
|||
("There is $int2str(1) murderer, but $int2str(12) suspects.",
|
||||
"There is one murderer, but twelve suspects."),
|
||||
("There is $an(thing) here", "There is a thing here"),
|
||||
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
|
||||
('$crop("spider\'s silk", 5)', "spide"),
|
||||
]
|
||||
)
|
||||
def test_other_callables(self, string, expected):
|
||||
|
|
@ -462,15 +476,16 @@ 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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
50
evennia/utils/tests/test_search.py
Normal file
50
evennia/utils/tests/test_search.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from evennia.scripts.scripts import DefaultScript
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.utils.search import search_script_attribute, 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 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)
|
||||
|
||||
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 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), 0, errors)
|
||||
|
|
@ -12,7 +12,9 @@ class TestText2Html(TestCase):
|
|||
self.assertEqual("foo", parser.format_styles("foo"))
|
||||
self.assertEqual(
|
||||
'<span class="color-001">red</span>foo',
|
||||
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(
|
||||
'<span class="bgcolor-001">red</span>foo',
|
||||
|
|
@ -31,33 +33,15 @@ class TestText2Html(TestCase):
|
|||
)
|
||||
self.assertEqual(
|
||||
'a <span class="underline">red</span>foo',
|
||||
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 <span class="blink">red</span>foo',
|
||||
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 <span class="bgcolor-007 color-000">red</span>foo',
|
||||
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!"),
|
||||
'<span class="blink bgcolor-006">'
|
||||
'Hello'
|
||||
"Hello"
|
||||
'</span><span class="underline color-009">'
|
||||
'W'
|
||||
"W"
|
||||
'</span><span class="underline color-010">'
|
||||
'o'
|
||||
"o"
|
||||
'</span><span class="underline color-011">'
|
||||
'r'
|
||||
"r"
|
||||
'</span><span class="underline color-012">'
|
||||
'l'
|
||||
"l"
|
||||
'</span><span class="underline color-013">'
|
||||
'd'
|
||||
"d"
|
||||
'</span><span class="underline color-014">'
|
||||
'!'
|
||||
"!"
|
||||
'</span><span class="underline bgcolor-002 color-014">'
|
||||
'!'
|
||||
'</span>',
|
||||
"!"
|
||||
"</span>",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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] = "</span>"
|
||||
if substr == ANSI_NORMAL:
|
||||
# close any existing span if necessary
|
||||
str_list[i] = "</span>" if not clean else ""
|
||||
# reset to defaults
|
||||
classes = []
|
||||
clean = True
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue