Add legacy 2.x docs

This commit is contained in:
Griatch 2023-12-20 18:06:19 +01:00
parent df0a1a4f59
commit 07cfac0bfa
2288 changed files with 531353 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,33 @@
# Coding and development help
This documentation aims to help you set up a sane development environment to
make your game, also if you never coded before.
See also the [Beginner Tutorial](../Howtos/Beginner-Tutorial/Beginner-Tutorial-Overview.md).
```{toctree}
:maxdepth: 2
Evennia-Code-Style.md
Default-Command-Syntax.md
Version-Control.md
Debugging.md
Unit-Testing.md
Profiling.md
Continuous-Integration.md
Setting-up-PyCharm.md
```
## Evennia Changelog
```{toctree}
:maxdepth: 2
Changelog.md
```
```{toctree}
:hidden:
Release-Notes-1.0
```

View file

@ -0,0 +1,18 @@
# Continuous Integration (CI)
[Continuous Integration (CI)](https://en.wikipedia.org/wiki/Continuous_integration) is a development practice that requires developers to integrate code into a shared repository. Each check-in is then verified by an automated build, allowing teams to detect problems early. This can be set up to safely deploy data to a production server only after tests have passed, for example.
For Evennia, continuous integration allows an automated build process to:
* Pull down a latest build from Source Control.
* Run migrations on the backing SQL database.
* Automate additional unique tasks for that project.
* Run unit tests.
* Publish those files to the server directory
* Reload the game.
## Continuous-Integration guides
Evennia itself is making heavy use of [github actions](https://github.com/features/actions). This is integrated with Github and is probably the one to go for most people, especially if your code is on Github already. You can see and analyze how Evennia's actions are running [here](https://github.com/evennia/evennia/actions).
There are however a lot of tools and services providing CI functionality. [Here is a blog overview](https://www.atlassian.com/continuous-delivery/continuous-integration/tools) (external link).

View file

@ -0,0 +1,254 @@
# Debugging
Sometimes, an error is not trivial to resolve. A few simple `print` statements is not enough to find the cause of the issue. The traceback is not informative or even non-existing.
Running a *debugger* can then be very helpful and save a lot of time. Debugging means running Evennia under control of a special *debugger* program. This allows you to stop the action at a given point, view the current state and step forward through the program to see how its logic works.
Evennia natively supports these debuggers:
- [Pdb](https://docs.python.org/2/library/pdb.html) is a part of the Python distribution and
available out-of-the-box.
- [PuDB](https://pypi.org/project/pudb/) is a third-party debugger that has a slightly more
'graphical', curses-based user interface than pdb. It is installed with `pip install pudb`.
## Debugging Evennia
To run Evennia with the debugger, follow these steps:
1. Find the point in the code where you want to have more insight. Add the following line at that
point.
```python
from evennia import set_trace;set_trace()
```
2. (Re-)start Evennia in interactive (foreground) mode with `evennia istart`. This is important - without this step the debugger will not start correctly - it will start in this interactive terminal.
3. Perform the steps that will trigger the line where you added the `set_trace()` call. The debugger will start in the terminal from which Evennia was interactively started.
The `evennia.set_trace` function takes the following arguments:
```python
evennia.set_trace(debugger='auto', term_size=(140, 40))
```
Here, `debugger` is one of `pdb`, `pudb` or `auto`. If `auto`, use `pudb` if available, otherwise use `pdb`. The `term_size` tuple sets the viewport size for `pudb` only (it's ignored by `pdb`).
## A simple example using pdb
The debugger is useful in different cases, but to begin with, let's see it working in a command.
Add the following test command (which has a range of deliberate errors) and also add it to your
default cmdset. Then restart Evennia in interactive mode with `evennia istart`.
```python
# In file commands/command.py
class CmdTest(Command):
"""
A test command just to test pdb.
Usage:
test
"""
key = "test"
def func(self):
from evennia import set_trace; set_trace() # <--- start of debugger
obj = self.search(self.args)
self.msg("You've found {}.".format(obj.get_display_name()))
```
If you type `test` in your game, everything will freeze. You won't get any feedback from the game, and you won't be able to enter any command (nor anyone else). It's because the debugger has started in your console, and you will find it here. Below is an example with `pdb`.
```
...
> .../mygame/commands/command.py(79)func()
-> obj = self.search(self.args)
(Pdb)
```
`pdb` notes where it has stopped execution and, what line is about to be executed (in our case, `obj = self.search(self.args)`), and ask what you would like to do.
### Listing surrounding lines of code
When you have the `pdb` prompt `(Pdb)`, you can type in different commands to explore the code. The first one you should know is `list` (you can type `l` for short):
```
(Pdb) l
43
44 key = "test"
45
46 def func(self):
47 from evennia import set_trace; set_trace() # <--- start of debugger
48 -> obj = self.search(self.args)
49 self.msg("You've found {}.".format(obj.get_display_name()))
50
51 # -------------------------------------------------------------
52 #
53 # The default commands inherit from
(Pdb)
```
Okay, this didn't do anything spectacular, but when you become more confident with `pdb` and find yourself in lots of different files, you sometimes need to see what's around in code. Notice that there is a little arrow (`->`) before the line that is about to be executed.
This is important: **about to be**, not **has just been**. You need to tell `pdb` to go on (we'll soon see how).
### Examining variables
`pdb` allows you to examine variables (or really, to run any Python instruction). It is very useful to know the values of variables at a specific line. To see a variable, just type its name (as if you were in the Python interpreter:
```
(Pdb) self
<commands.command.CmdTest object at 0x045A0990>
(Pdb) self.args
u''
(Pdb) self.caller
<Character: XXX>
(Pdb)
```
If you try to see the variable `obj`, you'll get an error:
```
(Pdb) obj
*** NameError: name 'obj' is not defined
(Pdb)
```
That figures, since at this point, we haven't created the variable yet.
> Examining variable in this way is quite powerful. You can even run Python code and keep on
> executing, which can help to check that your fix is actually working when you have identified an
> error. If you have variable names that will conflict with `pdb` commands (like a `list`
> variable), you can prefix your variable with `!`, to tell `pdb` that what follows is Python code.
### Executing the current line
It's time we asked `pdb` to execute the current line. To do so, use the `next` command. You can
shorten it by just typing `n`:
```
(Pdb) n
AttributeError: "'CmdTest' object has no attribute 'search'"
> .../mygame/commands/command.py(79)func()
-> obj = self.search(self.args)
(Pdb)
```
`Pdb` is complaining that you try to call the `search` method on a command... whereas there's no `search` method on commands. The character executing the command is in `self.caller`, so we might change our line:
```python
obj = self.caller.search(self.args)
```
### Letting the program run
`pdb` is waiting to execute the same instruction... it provoked an error but it's ready to try again, just in case. We have fixed it in theory, but we need to reload, so we need to enter a command. To tell `pdb` to terminate and keep on running the program, use the `continue` (or `c`) command:
```
(Pdb) c
...
```
You see an error being caught, that's the error we have fixed... or hope to have. Let's reload the game and try again. You need to run `evennia istart` again and then run `test` to get into the command again.
```
> .../mygame/commands/command.py(79)func()
-> obj = self.caller.search(self.args)
(Pdb)
```
`pdb` is about to run the line again.
```
(Pdb) n
> .../mygame/commands/command.py(80)func()
-> self.msg("You've found {}.".format(obj.get_display_name()))
(Pdb)
```
This time the line ran without error. Let's see what is in the `obj` variable:
```
(Pdb) obj
(Pdb) print obj
None
(Pdb)
```
We have entered the `test` command without parameter, so no object could be found in the search
(`self.args` is an empty string).
Let's allow the command to continue and try to use an object name as parameter (although, we should
fix that bug too, it would be better):
```
(Pdb) c
...
```
Notice that you'll have an error in the game this time. Let's try with a valid parameter. I have another character, `barkeep`, in this room:
```test barkeep```
And again, the command freezes, and we have the debugger opened in the console.
Let's execute this line right away:
```
> .../mygame/commands/command.py(79)func()
-> obj = self.caller.search(self.args)
(Pdb) n
> .../mygame/commands/command.py(80)func()
-> self.msg("You've found {}.".format(obj.get_display_name()))
(Pdb) obj
<Character: barkeep>
(Pdb)
```
At least this time we have found the object. Let's process...
```
(Pdb) n
TypeError: 'get_display_name() takes exactly 2 arguments (1 given)'
> .../mygame/commands/command.py(80)func()
-> self.msg("You've found {}.".format(obj.get_display_name()))
(Pdb)
```
As an exercise, fix this error, reload and run the debugger again. Nothing better than some experimenting!
Your debugging will often follow the same strategy:
1. Receive an error you don't understand.
2. Put a breaking point **BEFORE** the error occurs.
3. Run `evennia istart`
4. Run the code again and see the debugger open.
5. Run the program line by line, examining variables, checking the logic of instructions.
6. Continue and try again, each step a bit further toward the truth and the working feature.
## Cheat-sheet of pdb/pudb commands
PuDB and Pdb share the same commands. The only real difference is how it's presented. The `look`
command is not needed much in `pudb` since it displays the code directly in its user interface.
| Pdb/PuDB command | To do what |
| ----------- | ---------- |
| list (or l) | List the lines around the point of execution (not needed for `pudb`, it will show
this directly). |
| print (or p) | Display one or several variables. |
| `!` | Run Python code (using a `!` is often optional). |
| continue (or c) | Continue execution and terminate the debugger for this time. |
| next (or n) | Execute the current line and goes to the next one. |
| step (or s) | Step inside of a function or method to examine it. |
| `<RETURN>` | Repeat the last command (don't type `n` repeatedly, just type it once and then press
`<RETURN>` to repeat it). |
If you want to learn more about debugging with Pdb, you will find an [interesting tutorial on that topic here](https://pymotw.com/3/pdb/).

View file

@ -0,0 +1,24 @@
# Default Command Syntax
Evennia allows for any command syntax.
If you like the way DikuMUDs, LPMuds or MOOs handle things, you could emulate that with Evennia. If you are ambitious you could even design a whole new style, perfectly fitting your own dreams of the ideal game. See the [Command](../Components/Commands.md) documentation for how to do this.
We do offer a default however. The default Evennia setup tends to *resemble* [MUX2](https://www.tinymux.org/), and its cousins [PennMUSH](https://www.pennmush.org), [TinyMUSH](https://github.com/TinyMUSH/TinyMUSH/wiki), and [RhostMUSH](http://www.rhostmush.com/):
```
command[/switches] object [= options]
```
While the reason for this similarity is partly historical, these codebases offer very mature feature sets for administration and building.
Evennia is *not* a MUX system though. It works very differently in many ways. For example, Evennia
deliberately lacks an online softcode language (a policy explained on our [softcode policy page](./Soft-Code.md)). Evennia also does not shy from using its own syntax when deemed appropriate: the
MUX syntax has grown organically over a long time and is, frankly, rather arcane in places. All in
all the default command syntax should at most be referred to as "MUX-like" or "MUX-inspired".
```{toctree}
:hidden:
Soft-Code
```

View file

@ -0,0 +1,278 @@
# Evennia Code Style
All code submitted or committed to the Evennia project should aim to follow the
guidelines outlined in [Python PEP 8][pep8]. Keeping the code style uniform
makes it much easier for people to collaborate and read the code.
A good way to check if your code follows PEP8 is to use the [PEP8 tool][pep8tool]
on your sources.
## Main code style specification
* 4-space indentation, NO TABS!
* Unix line endings.
* 100 character line widths
* CamelCase is only used for classes, nothing else.
* All non-global variable names and all function names are to be
lowercase, words separated by underscores. Variable names should
always be more than two letters long.
* Module-level global variables (only) are to be in CAPITAL letters.
* Imports should be done in this order:
- Python modules (builtins and standard library)
- Twisted modules
- Django modules
- Evennia library modules (`evennia`)
- Evennia contrib modules (`evennia.contrib`)
* All modules, classes, functions and methods should have doc strings formatted
as outlined below.
* All default commands should have a consistent docstring formatted as
outlined below.
## Code Docstrings
All modules, classes, functions and methods should have docstrings
formatted with [Google style][googlestyle] -inspired indents, using
[Markdown][githubmarkdown] formatting where needed. Evennia's `api2md`
parser will use this to create pretty API documentation.
### Module docstrings
Modules should all start with at least a few lines of docstring at
their top describing the contents and purpose of the module.
Example of module docstring (top of file):
```python
"""
This module handles the creation of `Objects` that
are useful in the game ...
"""
```
Sectioning (`# title`, `## subtile` etc) should not be used in
freeform docstrings - this will confuse the sectioning of the auto
documentation page and the auto-api will create this automatically.
Write just the section name bolded on its own line to mark a section.
Beyond sections markdown should be used as needed to format
the text.
Code examples should use [multi-line syntax highlighting][markdown-hilight]
to mark multi-line code blocks, using the "python" identifier. Just
indenting code blocks (common in markdown) will not produce the
desired look.
When using any code tags (inline or blocks) it's recommended that you
don't let the code extend wider than about 70 characters or it will
need to be scrolled horizontally in the wiki (this does not affect any
other text, only code).
### Class docstrings
The root class docstring should describe the over-arching use of the
class. It should usually not describe the exact call sequence nor list
important methods, this tends to be hard to keep updated as the API
develops. Don't use section markers (`#`, `##` etc).
Example of class docstring:
```python
class MyClass(object):
"""
This class describes the creation of `Objects`. It is useful
in many situations, such as ...
"""
```
### Function / method docstrings
Example of function or method docstring:
```python
def funcname(a, b, c, d=False, **kwargs):
"""
This is a brief introduction to the function/class/method
Args:
a (str): This is a string argument that we can talk about
over multiple lines.
b (int or str): Another argument.
c (list): A list argument.
d (bool, optional): An optional keyword argument.
Keyword Args:
test (list): A test keyword.
Returns:
str: The result of the function.
Raises:
RuntimeException: If there is a critical error,
this is raised.
IOError: This is only raised if there is a
problem with the database.
Notes:
This is an example function. If `d=True`, something
amazing will happen.
"""
```
The syntax is very "loose" but the indentation matters. That is, you
should end the block headers (like `Args:`) with a line break followed by
an indent. When you need to break a line you should start the next line
with another indent. For consistency with the code we recommend all
indents to be 4 spaces wide (no tabs!).
Here are all the supported block headers:
```
"""
Args
argname (freeform type): Description endind with period.
Keyword Args:
argname (freeform type): Description.
Returns/Yields:
type: Description.
Raises:
Exceptiontype: Description.
Notes/Note/Examples/Example:
Freeform text.
"""
```
Parts marked with "freeform" means that you can in principle put any
text there using any formatting except for sections markers (`#`, `##`
etc). You must also keep indentation to mark which block you are part
of. You should normally use the specified format rather than the
freeform counterpart (this will produce nicer output) but in some
cases the freeform may produce a more compact and readable result
(such as when describing an `*args` or `**kwargs` statement in general
terms). The first `self` argument of class methods should never be
documented.
Note that
```
"""
Args:
argname (type, optional): Description.
"""
```
and
```
"""
Keyword Args:
sargname (type): Description.
"""
```
mean the same thing! Which one is used depends on the function or
method documented, but there are no hard rules; If there is a large
`**kwargs` block in the function, using the `Keyword Args:` block may be a
good idea, for a small number of arguments though, just using `Args:`
and marking keywords as `optional` will shorten the docstring and make
it easier to read.
## Default Command Docstrings
These represent a special case since Commands in Evennia use their class
docstrings to represent the in-game help entry for that command.
All the commands in the _default command_ sets should have their doc-strings
formatted on a similar form. For contribs, this is loosened, but if there is
no particular reason to use a different form, one should aim to use the same
style for contrib-command docstrings as well.
```python
"""
Short header
Usage:
key[/switches, if any] <mandatory args> [optional] choice1||choice2||choice3
Switches:
switch1 - description
switch2 - description
Examples:
Usage example and output
Longer documentation detailing the command.
"""
```
- Two spaces are used for *indentation* in all default commands.
- Square brackets `[ ]` surround *optional, skippable arguments*.
- Angled brackets `< >` surround a _description_ of what to write rather than the exact syntax.
- Explicit choices are separated by `|`. To avoid this being parsed as a color code, use `||` (this
will come out as a single `|`) or put spaces around the character ("` | `") if there's plenty of room.
- The `Switches` and `Examples` blocks are optional and based on the Command.
Here is the `nick` command as an example:
```python
"""
Define a personal alias/nick
Usage:
nick[/switches] <nickname> = [<string>]
alias ''
Switches:
object - alias an object
account - alias an account
clearall - clear all your aliases
list - show all defined aliases (also "nicks" works)
Examples:
nick hi = say Hello, I'm Sarah!
nick/object tom = the tall man
A 'nick' is a personal shortcut you create for your own use [...]
"""
```
For commands that *require arguments*, the policy is for it to return a `Usage:`
string if the command is entered without any arguments. So for such commands,
the Command body should contain something to the effect of
```python
if not self.args:
self.caller.msg("Usage: nick[/switches] <nickname> = [<string>]")
return
```
## Tools for auto-linting
### black
Automatic pep8 compliant formatting and linting can be performed using the
`black` formatter:
black --line-length 100
### PyCharm
The Python IDE [Pycharm][pycharm] can auto-generate empty doc-string stubs. The
default is to use `reStructuredText` form, however. To change to Evennia's
Google-style docstrings, follow [this guide][pycharm-guide].
[pep8]: http://www.python.org/dev/peps/pep-0008
[pep8tool]: https://pypi.python.org/pypi/pep8
[googlestyle]: https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html
[githubmarkdown]: https://help.github.com/articles/github-flavored-markdown/
[markdown-hilight]: https://help.github.com/articles/github-flavored-markdown/#syntax-highlighting
[command-docstrings]: https://github.com/evennia/evennia/wiki/Using%20MUX%20As%20a%20Standard#documentation-policy
[pycharm]: https://www.jetbrains.com/pycharm/
[pycharm-guide]: https://www.jetbrains.com/help/pycharm/2016.3/python-integrated-tools.html

View file

@ -0,0 +1,197 @@
# Profiling
```{important}
This is considered an advanced topic. It's mainly of interest to server developers.
```
Sometimes it can be useful to try to determine just how efficient a particular piece of code is, or to figure out if one could speed up things more than they are. There are many ways to test the performance of Python and the running server.
Before digging into this section, remember Donald Knuth's [words of wisdom](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize):
> *[...]about 97% of the time: Premature optimization is the root of all evil*.
That is, don't start to try to optimize your code until you have actually identified a need to do so. This means your code must actually be working before you start to consider optimization. Optimization will also often make your code more complex and harder to read. Consider readability and maintainability and you may find that a small gain in speed is just not worth it.
## Simple timer tests
Python's `timeit` module is very good for testing small things. For example, in
order to test if it is faster to use a `for` loop or a list comprehension you
could use the following code:
```python
import timeit
# Time to do 1000000 for loops
timeit.timeit("for i in range(100):\n a.append(i)", setup="a = []")
<<< 10.70982813835144
# Time to do 1000000 list comprehensions
timeit.timeit("a = [i for i in range(100)]")
<<< 5.358283996582031
```
The `setup` keyword is used to set up things that should not be included in the time measurement, like `a = []` in the first call.
By default the `timeit` function will re-run the given test 1000000 times and returns the *total time* to do so (so *not* the average per test). A hint is to not use this default for testing something that includes database writes - for that you may want to use a lower number of repeats (say 100 or 1000) using the `number=100` keyword.
In the example above, we see that this number of calls, using a list comprehension is about twice as fast as building a list using `.append()`.
## Using cProfile
Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done with `pypy` at this point). Due to the way Evennia's processes are handled, there is no point in using the normal way to start the profiler (`python -m cProfile evennia.py`). Instead you start the profiler through the launcher:
evennia --profiler start
This will start Evennia with the Server component running (in daemon mode) under cProfile. You could instead try `--profile` with the `portal` argument to profile the Portal (you would then need to [start the Server separately](../Setup/Running-Evennia.md)).
Please note that while the profiler is running, your process will use a lot more memory than usual. Memory usage is even likely to climb over time. So don't leave it running perpetually but monitor it carefully (for example using the `top` command on Linux or the Task Manager's memory display on Windows).
Once you have run the server for a while, you need to stop it so the profiler can give its report. Do *not* kill the program from your task manager or by sending it a kill signal - this will most likely also mess with the profiler. Instead either use `evennia.py stop` or (which may be even better), use `@shutdown` from inside the game.
Once the server has fully shut down (this may be a lot slower than usual) you will find that profiler has created a new file `mygame/server/logs/server.prof`.
### Analyzing the profile
The `server.prof` file is a binary file. There are many ways to analyze and display its contents, all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works).
You can look at the contents of the profile file with Python's in-built `pstats` module in the evennia shell (it's recommended you install `ipython` with `pip install ipython` in your virtualenv first, for prettier output):
evennia shell
Then in the shell
```python
import pstats
from pstats import SortKey
p = pstats.Stats('server/log/server.prof')
p.strip_dirs().sort_stats(-1).print_stats()
```
See the [Python profiling documentation](https://docs.python.org/3/library/profile.html#instant-user-s-manual) for more information.
You can also visualize the data in various ways.
- [Runsnake](https://pypi.org/project/RunSnakeRun/) visualizes the profile to
give a good overview. Install with `pip install runsnakerun`. Note that this
may require a C compiler and be quite slow to install.
- For more detailed listing of usage time, you can use
[KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html). To make
KCachegrind work with Python profiles you also need the wrapper script
[pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/). You can get
`pyprof2calltree` via `pip` whereas KCacheGrind is something you need to get
via your package manager or their homepage.
How to analyze and interpret profiling data is not a trivial issue and depends on what you are profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing list if you need help and be ready to be able to supply your `server.prof` file for comparison, along with the exact conditions under which it was obtained.
## The Dummyrunner
It is difficult to test "actual" game performance without having players in your game. For this reason Evennia comes with the *Dummyrunner* system. The Dummyrunner is a stress-testing system: a separate program that logs into your game with simulated players (aka "bots" or "dummies"). Once connected, these dummies will semi-randomly perform various tasks from a list of possible actions. Use `Ctrl-C` to stop the Dummyrunner.
```{warning}
You should not run the Dummyrunner on a production database. It
will spawn many objects and also needs to run with general permissions.
This is the recommended process for using the dummy runner:
```
1. Stop your server completely with `evennia stop`.
1. At _the end_ of your `mygame/server/conf.settings.py` file, add the line
from evennia.server.profiling.settings_mixin import *
This will override your settings and disable Evennia's rate limiters and DoS-protections, which would otherwise block mass-connecting clients from one IP. Notably, it will also change to a different (faster) password hasher.
1. (recommended): Build a new database. If you use default Sqlite3 and want to
keep your existing database, just rename `mygame/server/evennia.db3` to
`mygame/server/evennia.db3_backup` and run `evennia migrate` and `evennia
start` to create a new superuser as usual.
1. (recommended) Log into the game as your superuser. This is just so you
can manually check response. If you kept an old database, you will _not_
be able to connect with an _existing_ user since the password hasher changed!
1. Start the dummyrunner with 10 dummy users from the terminal with
evennia --dummyrunner 10
Use `Ctrl-C` (or `Cmd-C`) to stop it.
If you want to see what the dummies are actually doing you can run with a single dummy:
evennia --dummyrunner 1
The inputs/outputs from the dummy will then be printed. By default the runner uses the 'looker' profile, which just logs in and sends the 'look' command over and over. To change the settings, copy the file `evennia/server/profiling/dummyrunner_settings.py` to your `mygame/server/conf/` directory, then add this line to your settings file to use it in the new location:
DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py"
The dummyrunner settings file is a python code module in its own right - it defines the actions available to the dummies. These are just tuples of command strings (like "look here") for the dummy to send to the server along with a probability of them happening. The dummyrunner looks for a global variable `ACTIONS`, a list of tuples, where the first two elements define the commands for logging in/out of the server.
Below is a simplified minimal setup (the default settings file adds a lot more functionality and info):
```python
# minimal dummyrunner setup file
# Time between each dummyrunner "tick", in seconds. Each dummy will be called
# with this frequency.
TIMESTEP = 1
# Chance of a dummy actually performing an action on a given tick. This
# spreads out usage randomly, like it would be in reality.
CHANCE_OF_ACTION = 0.5
# Chance of a currently unlogged-in dummy performing its login action every
# tick. This emulates not all accounts logging in at exactly the same time.
CHANCE_OF_LOGIN = 0.01
# Which telnet port to connect to. If set to None, uses the first default
# telnet port of the running server.
TELNET_PORT = None
# actions
def c_login(client):
name = f"Character-{client.gid}"
pwd = f"23fwsf23sdfw23wef23"
return (
f"create {name} {pwd}"
f"connect {name} {pwd}"
)
def c_logout(client):
return ("quit", )
def c_look(client):
return ("look here", "look me")
# this is read by dummyrunner.
ACTIONS = (
c_login,
c_logout,
(1.0, c_look) # (probability, command-generator)
)
```
At the bottom of the default file are a few default profiles you can test out by just setting the `PROFILE` variable to one of the options.
### Dummyrunner hints
- Don't start with too many dummies. The Dummyrunner taxes the server much more
than 'real' users tend to do. Start with 10-100 to begin with.
- Stress-testing can be fun, but also consider what a 'realistic' number of
users would be for your game.
- Note in the dummyrunner output how many commands/s are being sent to the
server by all dummies. This is usually a lot higher than what you'd
realistically expect to see from the same number of users.
- The default settings sets up a 'lag' measure to measaure the round-about
message time. It updates with an average every 30 seconds. It can be worth to
have this running for a small number of dummies in one terminal before adding
more by starting another dummyrunner in another terminal - the first one will
act as a measure of how lag changes with different loads. Also verify the
lag-times by entering commands manually in-game.
- Check the CPU usage of your server using `top/htop` (linux). In-game, use the
`server` command.
- You can run the server with `--profiler start` to test it with dummies. Note
that the profiler will itself affect server performance, especially memory
consumption.
- Generally, the dummyrunner system makes for a decent test of general
performance; but it is of course hard to actually mimic human user behavior.
For this, actual real-game testing is required.

View file

@ -0,0 +1,151 @@
# Evennia 1.0 Release Notes
This summarizes the changes. See the [Changelog](./Changelog.md) for the full list.
- Main development now on `main` branch. `master` branch remains, but will not be updated anymore.
## Minimum requirements
- Python 3.10 is now required minimum. Ubuntu LTS now installs with 3.10. Evennia 1.0 is also tested with Python 3.11 - this is the recommended version for Linux/Mac. Windows users may want to stay on Python 3.10 unless they are okay with installing a C++ compiler.
- Twisted 22.10+
- Django 4.1+
## Major new features
- Evennia is now on PyPi and is installable as [pip install evennia](../Setup/Installation.md).
- A completely revamped documentation at https://www.evennia.com/docs/latest. The old wiki and readmedocs pages will close.
- Evennia 1.0 now has a REST API which allows you access game objects using CRUD operations GET/POST etc. See [The Web-API docs][Web-API] for more information.
- [Evennia<>Discord Integration](../Setup/Channels-to-Discord.md) between Evennia channels and Discord servers.
- [Script](../Components/Scripts.md) overhaul: Scripts' timer component independent from script object deletion; can now start/stop timer without deleting Script. The `.persistent` flag now only controls if timer survives reload - Script has to be removed with `.delete()` like other typeclassed entities. This makes Scripts even more useful as general storage entities.
- The [FuncParser](../Components/FuncParser.md) centralizes and vastly improves all in-string function calls, such as `say the result is $eval(3 * 7)` and say the result `the result is 21`. The parser completely replaces the old `parse_inlinefunc`. The new parser can handle both arguments and kwargs and are also used for in-prototype parsing as well as director stance messaging, such as using `$You()` to represent yourself in a string and having the result come out differently depending on who see you.
- [Channels](../Components/Channels.md) New Channel-System using the `channel` command and nicks. The old `ChannelHandler` was removed and the customization and operation of channels have been simplified a lot. The old command syntax commands are now available as a contrib.
- [Help System](../Components/Help-System.md) was refactored.
- A new type of `FileHelp` system allows you to add in-game help files as external Python files. This means there are three ways to add help entries in Evennia: 1) Auto-generated from Command's code. 2) Manually added to the database from the `sethelp` command in-game and 3) Created as external Python files that Evennia loads and makes available in-game.
- We now use `lunr` search indexing for better `help` matching and suggestions. Also improve
the main help command's default listing output.
- Help command now uses `view` lock to determine if cmd/entry shows in index and `read` lock to determine if it can be read. It used to be `view` in the role of the latter.
- `sethelp` command now warns if shadowing other help-types when creating a new entry.
- Make `help` index output clickable for webclient/clients with MXP (PR by davewiththenicehat)
- Rework of the [Web](../Components/Website.md) setup, into a much more consistent structure and update to latest Django. The `mygame/web/static_overrides` and `-template_overrides` were removed. The folders are now just `mygame/web/static` and `/templates` and handle the automatic copying of data behind the scenes. `app.css` to `website.css` for consistency. The old `prosimii-css` files were removed.
- [AttributeProperty](../Components/Attributes.md#using-attributeproperty)/[TagProperty](../Components/Tags.md) along with `AliasProperty` and `PermissionProperty` to allow managing Attributes, Tags, Aliases and Permissios on typeclasses in the same way as Django fields. This dramatically reduces the need to assign Attributes/Tags in `at_create_object` hook.
- The old `MULTISESSION_MODE` was divided into smaller settings, for better controlling what happens when a user connects, if a character should be auto-created, and how many characters they can control at the same time. See [Connection-Styles](../Concepts/Connection-Styles.md) for a detailed explanation.
- Evennia now supports custom `evennia` launcher commands (e.g. `evennia mycmd foo bar`). Add new commands as callables accepting `*args`, as `settings.EXTRA_LAUNCHER_COMMANDS = {'mycmd': 'path.to.callable', ...}`.
## Contribs
The `contrib` folder structure was changed from 0.9.5. All contribs are now in sub-folders and organized into categories. All import paths must be updated. See [Contribs overview](../Contribs/Contribs-Overview.md).
- New [Traits contrib](../Contribs/Contrib-Traits.md), converted and expanded from Ainneve project. (whitenoise, Griatch)
- New [Crafting contrib](../Contribs/Contrib-Crafting.md), adding a full crafting subsystem (Griatch)
- New [XYZGrid contrib](../Contribs/Contrib-XYZGrid.md), adding x,y,z grid coordinates with in-game map and pathfinding. Controlled outside of the game via custom evennia launcher command (Griatch)
- New [Command cooldown contrib](../Contribs/Contrib-Cooldowns.md) contrib for making it easier to manage commands using
dynamic cooldowns between uses (owllex)
- New [Godot Protocol contrib](../Contribs/Contrib-Godotwebsocket.md) for connecting to Evennia from a client written in the open-source game engine [Godot](https://godotengine.org/) (ChrisLR).
- New [name_generator contrib](../Contribs/Contrib-Name-Generator.md) for building random real-world based or fantasy-names based on phonetic rules (InspectorCaracal)
- New [Buffs contrib](../Contribs/Contrib-Buffs.md) for managing temporary and permanent RPG status buffs effects (tegiminis)
- The existing [RPSystem contrib](../Contribs/Contrib-RPSystem.md) was refactored and saw a speed boost (InspectorCaracal, other contributors)
## Translations
- New Latin (la) translation (jamalainm)
- New German (de) translation (Zhuraj)
- Updated Italian translation (rpolve)
- Updated Swedish translation
## Utils
- New `utils.format_grid` for easily displaying long lists of items in a block. This is now used for the default help display.
- Add `utils.repeat` and `utils.unrepeat` as shortcuts to TickerHandler add/remove, similar
to how `utils.delay` is a shortcut for TaskHandler add.
- Add `utils/verb_conjugation` for automatic verb conjugation (English only). This
is useful for implementing actor-stance emoting for sending a string to different targets.
- `utils.evmenu.ask_yes_no` is a helper function that makes it easy to ask a yes/no question
to the user and respond to their input. This complements the existing `get_input` helper.
- New `tasks` command for managing tasks started with `utils.delay` (PR by davewiththenicehat)
- Add `.deserialize()` method to `_Saver*` structures to help completely
decouple structures from database without needing separate import.
- Add `run_in_main_thread` as a helper for those wanting to code server code
from a web view.
- Update `evennia.utils.logger` to use Twisted's new logging API. No change in Evennia API
except more standard aliases logger.error/info/exception/debug etc can now be used.
- Made `utils.iter_to_str` format prettier strings, using Oxford comma.
- Move `create_*` functions into db managers, leaving `utils.create` only being
wrapper functions (consistent with `utils.search`). No change of api otherwise.
## Locks
- New `search:` lock type used to completely hide an object from being found by
the `DefaultObject.search` (`caller.search`) method. (CloudKeeper)
- New default for `holds()` lockfunc - changed from default of `True` to default of `False` in order to disallow dropping nonsensical things (such as things you don't hold).
## Hook changes
- Changed all `at_before/after_*` hooks to `at_pre/post_*` for consistency
across Evennia (the old names still work but are deprecated)
- New `at_pre_object_leave(obj, destination)` method on `Objects`.
- New `at_server_init()` hook called before all other startup hooks for all
startup modes. Used for more generic overriding (volund)
- New `at_pre_object_receive(obj, source_location)` method on Objects. Called on
destination, mimicking behavior of `at_pre_move` hook - returning False will abort move.
- `Object.normalize_name` and `.validate_name` added to (by default) enforce latinify
on character name and avoid potential exploits using clever Unicode chars (trhr)
- Make `object.search` support 'stacks=0' keyword - if ``>0``, the method will return
N identical matches instead of triggering a multi-match error.
- Add `tags.has()` method for checking if an object has a tag or tags (PR by ChrisLR)
- Add `Msg.db_receiver_external` field to allowe external, string-id message-receivers.
- Add `$pron()` and `$You()` inlinefuncs for pronoun parsing in actor-stance strings using `msg_contents`.
## Command changes
- Change default multi-match syntax from `1-obj`, `2-obj` to `obj-1`, `obj-2`, which seems to be what most expect.
- Split `return_appearance` hook with helper methods and have it use a template
string in order to make it easier to override.
- Command executions now done on copies to make sure `yield` don't cause crossovers. Add
`Command.retain_instance` flag for reusing the same command instance.
- Allow sending messages with `page/tell` without a `=` if target name contains no spaces.
- The `typeclass` command will now correctly search the correct database-table for the target
obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc).
- Merged `script` and `scripts` commands into one, for both managing global- and
on-object Scripts. Moved `CmdScripts` and `CmdObjects` to `commands/default/building.py`.
- The `channel` commands replace all old channel-related commands, such as `cset` etc
- Expand `examine` command's code to much more extensible and modular. Show
attribute categories and value types (when not strings).
- Add ability to examine `/script` and `/channel` entities with `examine` command.
- Add support for `$dbref()` and `$search` when assigning an Attribute value
with the `set` command. This allows assigning real objects from in-game.
- Have `type/force` default to `update`-mode rather than `reset`mode and add more verbose
warning when using reset mode.
## Coding improvement highlights
- The db pickle-serializer now checks for methods `__serialize_dbobjs__` and `__deserialize_dbobjs__` to allow custom packing/unpacking of nested dbobjs, to allow storing in Attribute. See [Attributes](../Components/Attributes.md) documentation.
- Add `ObjectParent` mixin to default game folder template as an easy, ready-made
way to override features on all ObjectDB-inheriting objects easily.
source location, mimicking behavior of `at_pre_move` hook - returning False will abort move.
- New Unit test parent classes, for use both in Evenia core and in mygame. Restructured unit tests to always honor default settings.
## Other
- Homogenize manager search methods to always return querysets and not sometimes querysets and sometimes lists.
- Attribute/NAttribute got a homogenous representation, using intefaces, both
`AttributeHandler` and `NAttributeHandler` has same api now.
- Added `content_types` indexing to DefaultObject's ContentsHandler. (volund)
- Made most of the networking classes such as Protocols and the SessionHandlers
replaceable via `settings.py` for modding enthusiasts. (volund)
- The `initial_setup.py` file can now be substituted in `settings.py` to customize
initial game database state. (volund)
- Make IP throttle use Django-based cache system for optional persistence (PR by strikaco)
- In modules given by `settings.PROTOTYPE_MODULES`, spawner will now first look for a global
list `PROTOTYPE_LIST` of dicts before loading all dicts in the module as prototypes.
concept of a dynamically created `ChannelCmdSet`.
- Prototypes now allow setting `prototype_parent` directly to a prototype-dict.
This makes it easier when dynamically building in-module prototypes.
- Make `@lazy_property` decorator create read/delete-protected properties. This is because it's used for handlers, and e.g. self.locks=[] is a common beginner mistake.
- Change `settings.COMMAND_DEFAULT_ARG_REGEX` default from `None` to a regex meaning that
a space or `/` must separate the cmdname and args. This better fits common expectations.
- Add `settings.MXP_ENABLED=True` and `settings.MXP_OUTGOING_ONLY=True` as sane defaults, to avoid known security issues with players entering MXP links.
- Made `MonitorHandler.add/remove` support `category` for monitoring Attributes with a category (before only key was used, ignoring category entirely).

View file

@ -0,0 +1,84 @@
# Setting up PyCharm with Evennia
[PyCharm](https://www.jetbrains.com/pycharm/) is a Python developer's IDE from Jetbrains available for Windows, Mac and Linux. It is a commercial product but offer free trials, a scaled-down community edition and also generous licenses for OSS projects like Evennia.
> This page was originally tested on Windows (so use Windows-style path examples), but should work the same for all platforms.
First, install Evennia on your local machine with [[Getting Started]]. If you're new to PyCharm, loading your project is as easy as selecting the `Open` option when PyCharm starts, and browsing to your game folder (the one created with `evennia --init`). We refer to it as `mygame` here.
If you want to be able to examine evennia's core code or the scripts inside your virtualenv, you'll
need to add them to your project too:
1. Go to `File > Open...`
1. Select the folder (i.e. the `evennia` root)
1. Select "Open in current window" and "Add to currently opened projects"
It's a good idea to set up the interpreter this before attempting anything further. The rest of this page assumes your project is already configured in PyCharm.
1. Go to `File > Settings... > Project: \<mygame\> > Project Interpreter`
1. Click the Gear symbol `> Add local`
1. Navigate to your `evenv/scripts directory`, and select Python.exe
Enjoy seeing all your imports checked properly, setting breakpoints, and live variable watching!
## Debug Evennia from inside PyCharm
1. Launch Evennia in your preferred way (usually from a console/terminal)
1. Open your project in PyCharm
1. In the PyCharm menu, select `Run > Attach to Local Process...`
1. From the list, pick the `twistd` process with the `server.py` parameter (Example: `twistd.exe --nodaemon --logfile=\<mygame\>\server\logs\server.log --python=\<evennia repo\>\evennia\server\server.py`)
Of course you can attach to the `portal` process as well. If you want to debug the Evennia launcher
or runner for some reason (or just learn how they work!), see Run Configuration below.
> NOTE: Whenever you reload Evennia, the old Server process will die and a new one start. So when you restart you have to detach from the old and then reattach to the new process that was created.
> To make the process less tedious you can apply a filter in settings to show only the server.py process in the list. To do that navigate to: `Settings/Preferences | Build, Execution, Deployment | Python Debugger` and then in `Attach to process` field put in: `twistd.exe" --nodaemon`. This is an example for windows, I don't have a working mac/linux box.
![Example process filter configuration](https://i.imgur.com/vkSheR8.png)
## Run Evennia from inside PyCharm
This configuration allows you to launch Evennia from inside PyCharm. Besides convenience, it also allows suspending and debugging the evennia_launcher or evennia_runner at points earlier than you could by running them externally and attaching. In fact by the time the server and/or portal are running the launcher will have exited already.
1. Go to `Run > Edit Configutations...`
1. Click the plus-symbol to add a new configuration and choose Python
1. Add the script: `\<yourrepo\>\evenv\Scripts\evennia_launcher.py` (substitute your virtualenv if it's not named `evenv`)
1. Set script parameters to: `start -l` (-l enables console logging)
1. Ensure the chosen interpreter is from your virtualenv
1. Set Working directory to your `mygame` folder (not evenv nor evennia)
1. You can refer to the PyCharm documentation for general info, but you'll want to set at least a config name (like "MyMUD start" or similar).
Now set up a "stop" configuration by following the same steps as above, but set your Script parameters to: stop (and name the configuration appropriately).
A dropdown box holding your new configurations should appear next to your PyCharm run button. Select MyMUD start and press the debug icon to begin debugging. Depending on how far you let the program run, you may need to run your "MyMUD stop" config to actually stop the server, before you'll be able start it again.
### Alternative config - utilizing logfiles as source of data
This configuration takes a bit different approach as instead of focusing on getting the data back through logfiles. Reason for that is this way you can easily separate data streams, for example you rarely want to follow both server and portal at the same time, and this will allow it. This will also make sure to stop the evennia before starting it, essentially working as reload command (it will also include instructions how to disable that part of functionality). We will start by defining a configuration that will stop evennia. This assumes that `upfire` is your pycharm project name, and also the game name, hence the `upfire/upfire` path.
1. Go to `Run > Edit Configutations...`\
1. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should be project default)
1. Name the configuration as "stop evennia" and fill rest of the fields accordingly to the image:
![Stop run configuration](https://i.imgur.com/gbkXhlG.png)
1. Press `Apply`
Now we will define the start/reload command that will make sure that evennia is not running already, and then start the server in one go.
1. Go to `Run > Edit Configutations...`\
1. Click the plus-symbol to add a new configuration and choose the python interpreter to use (should be project default)
1. Name the configuration as "start evennia" and fill rest of the fields accordingly to the image:
![Start run configuration](https://i.imgur.com/5YEjeHq.png)
1. Navigate to the `Logs` tab and add the log files you would like to follow. The picture shows
adding `portal.log` which will show itself in `portal` tab when running:
![Configuring logs following](https://i.imgur.com/gWYuOWl.png)
1. Skip the following steps if you don't want the launcher to stop evennia before starting.
1. Head back to `Configuration` tab and press the `+` sign at the bottom, under `Before launch....`
and select `Run another configuration` from the submenu that will pop up.
1. Click `stop evennia` and make sure that it's added to the list like on the image above.
1. Click `Apply` and close the run configuration window.
You are now ready to go, and if you will fire up `start evennia` configuration you should see
following in the bottom panel:
![Example of running alternative configuration](https://i.imgur.com/nTfpC04.png)
and you can click through the tabs to check appropriate logs, or even the console output as it is
still running in interactive mode.

View file

@ -0,0 +1,64 @@
# Soft Code
Softcode is a simple programming language that was created for in-game development on TinyMUD derivatives such as MUX, PennMUSH, TinyMUSH, and RhostMUSH. The idea was that by providing a stripped down, minimalistic language for in-game use, you could allow quick and easy building and game development to happen without builders having to learn the 'hardcode' language for those servers (C/C++). There is an added benefit of not having to have to hand out shell access to all developers. Permissions in softcode can be used to alleviate many security problems.
Writing and installing softcode is done through a MUD client. Thus it is not a formatted language. Each softcode function is a single line of varying size. Some functions can be a half of a page long or more which is obviously not very readable nor (easily) maintainable over time.
## Examples of Softcode
Here is a simple 'Hello World!' command:
```bash
@set me=HELLO_WORLD.C:$hello:@pemit %#=Hello World!
```
Pasting this into a MUD client, sending it to a MUX/MUSH server and typing 'hello' will theoretically yield 'Hello World!', assuming certain flags are not set on your account object.
Setting attributes in Softcode is done via `@set`. Softcode also allows the use of the ampersand (`&`) symbol. This shorter version looks like this:
```bash
&HELLO_WORLD.C me=$hello:@pemit %#=Hello World!
```
We could also read the text from an attribute which is retrieved when emitting:
```bash
&HELLO_VALUE.D me=Hello World
&HELLO_WORLD.C me=$hello:@pemit %#=[v(HELLO_VALUE.D)]
```
The `v()` function returns the `HELLO_VALUE.D` attribute on the object that the command resides (`me`, which is yourself in this case). This should yield the same output as the first example.
If you are curious about how MUSH/MUX Softcode works, take a look at some external resources:
- https://wiki.tinymux.org/index.php/Softcode
- https://www.duh.com/discordia/mushman/man2x1
## Problems with Softcode
Softcode is excellent at what it was intended for: *simple things*. It is a great tool for making an interactive object, a room with ambiance, simple global commands, simple economies and coded systems. However, once you start to try to write something like a complex combat system or a higher end economy, you're likely to find yourself buried under a mountain of functions that span multiple objects across your entire code.
Not to mention, softcode is not an inherently fast language. It is not compiled, it is parsed with each calling of a function. While MUX and MUSH parsers have jumped light years ahead of where they once were, they can still stutter under the weight of more complex systems if those are not designed properly.
Also, Softcode is not a standardized language. Different servers each have their own slight variations. Code tools and resources are also limited to the documentation from those servers.
## Changing Times
Now that starting text-based games is easy and an option for even the most technically inarticulate, new projects are a dime a dozen. People are starting new MUDs every day with varying levels of commitment and ability. Because of this shift from fewer, larger, well-staffed games to a bunch of small, one or two developer games, the benefit of softcode fades.
Softcode is great in that it allows a mid to large sized staff all work on the same game without stepping on one another's toes without shell access. However, the rise of modern code collaboration tools (such as private github/gitlab repos) has made it trivial to collaborate on code.
## Our Solution
Evennia shuns in-game softcode for on-disk Python modules. Python is a popular, mature and professional programming language. Evennia developers have access to the entire library of Python modules out there in the wild - not to mention the vast online help resources available. Python code is not bound to one-line functions on objects; complex systems may be organized neatly into real source code modules, sub-modules, or even broken out into entire Python packages as desired.
So what is *not* included in Evennia is a MUX/MOO-like online player-coding system (aka Softcode). Advanced coding in Evennia is primarily intended to be done outside the game, in full-fledged Python modules (what MUSH would call 'hardcode'). Advanced building is best handled by extending Evennia's command system with your own sophisticated building commands.
In Evennia you develop your MU like you would any piece of modern software - using your favorite code editor/IDE and online code sharing tools.
## Your Solution
Adding advanced and flexible building commands to your game is easy and will probably be enough to satisfy most creative builders. However, if you really, *really* want to offer online coding, there is of course nothing stopping you from adding that to Evennia, no matter our recommendations. You could even re-implement MUX' softcode in Python should you be very ambitious.
In default Evennia, the [Funcparser](Funcparser) system allows for simple remapping of text on-demand without becomeing a full softcode language. The [contribs](Contrib-Overview) has several tools and utililities to start from when adding more complex in-game building.

View file

@ -0,0 +1,306 @@
# Unit Testing
*Unit testing* means testing components of a program in isolation from each other to make sure every part works on its own before using it with others. Extensive testing helps avoid new updates causing unexpected side effects as well as alleviates general code rot (a more comprehensive wikipedia article on unit testing can be found [here](https://en.wikipedia.org/wiki/Unit_test)).
A typical unit test set calls some function or method with a given input, looks at the result and makes sure that this result looks as expected. Rather than having lots of stand-alone test programs, Evennia makes use of a central *test runner*. This is a program that gathers all available tests all over the Evennia source code (called *test suites*) and runs them all in one go. Errors and tracebacks are reported.
By default Evennia only tests itself. But you can also add your own tests to your game code and have Evennia run those for you.
## Running the Evennia test suite
To run the full Evennia test suite, go to your game folder and issue the command
evennia test evennia
This will run all the evennia tests using the default settings. You could also run only a subset of
all tests by specifying a subpackage of the library:
evennia test evennia.commands.default
A temporary database will be instantiated to manage the tests. If everything works out you will see
how many tests were run and how long it took. If something went wrong you will get error messages.
If you contribute to Evennia, this is a useful sanity check to see you haven't introduced an
unexpected bug.
## Running custom game-dir unit tests
If you have implemented your own tests for your game you can run them from your game dir
with
evennia test --settings settings.py .
The period (`.`) means to run all tests found in the current directory and all subdirectories. You
could also specify, say, `typeclasses` or `world` if you wanted to just run tests in those subdirs.
An important thing to note is that those tests will all be run using the _default Evennia settings_.
To run the tests with your own settings file you must use the `--settings` option:
evennia test --settings settings.py .
The `--settings` option of Evennia takes a file name in the `mygame/server/conf` folder. It is
normally used to swap settings files for testing and development. In combination with `test`, it
forces Evennia to use this settings file over the default one.
You can also test specific things by giving their path
evennia test --settings settings.py world.tests.YourTest
## Writing new unit tests
Evennia's test suite makes use of Django unit test system, which in turn relies on Python's
*unittest* module.
To make the test runner find the tests, they must be put in a module named `test*.py` (so `test.py`,
`tests.py` etc). Such a test module will be found wherever it is in the package. It can be a good
idea to look at some of Evennia's `tests.py` modules to see how they look.
Inside the module you need to put a class inheriting (at any distance) from `unittest.TestCase`. Each
method on that class that starts with `test_` will be run separately as a unit test. There
are two special, optional methods `setUp` and `tearDown` that will (if you define them) run before
_every_ test. This can be useful for setting up and deleting things.
To actually test things, you use special `assert...` methods on the class. Most common on is
`assertEqual`, which makes sure a result is what you expect it to be.
Here's an example of the principle. Let's assume you put this in `mygame/world/tests.py`
and want to test a function in `mygame/world/myfunctions.py`
```python
# in a module tests.py somewhere i your game dir
import unittest
from evennia import create_object
# the function we want to test
from .myfunctions import myfunc
class TestObj(unittest.TestCase):
"This tests a function myfunc."
def setUp(self):
"""done before every of the test_ * methods below"""
self.obj = create_object("mytestobject")
def tearDown(self):
"""done after every test_* method below """
self.obj.delete()
def test_return_value(self):
"""test method. Makes sure return value is as expected."""
actual_return = myfunc(self.obj)
expected_return = "This is the good object 'mytestobject'."
# test
self.assertEqual(expected_return, actual_return)
def test_alternative_call(self):
"""test method. Calls with a keyword argument."""
actual_return = myfunc(self.obj, bad=True)
expected_return = "This is the baaad object 'mytestobject'."
# test
self.assertEqual(expected_return, actual_return)
```
To test this, run
evennia test --settings settings.py .
to run the entire test module
evennia test --settings settings.py world.tests
or a specific class:
evennia test --settings settings.py world.tests.TestObj
You can also run a specific test:
evennia test --settings settings.py world.tests.TestObj.test_alternative_call
You might also want to read the [Python documentation for the unittest module](https://docs.python.org/library/unittest.html).
### Using the Evennia testing classes
Evennia offers many custom testing classes that helps with testing Evennia features.
They are all found in [evennia.utils.test_resources](evennia.utils.test_resources). Note that
these classes implement the `setUp` and `tearDown` already, so if you want to add stuff in them
yourself you should remember to use e.g. `super().setUp()` in your code.
#### Classes for testing your game dir
These all use whatever setting you pass to them and works well for testing code in your game dir.
- `EvenniaTest` - this sets up a full object environment for your test. All the created entities
can be accesses as properties on the class:
- `.account` - A fake [Account](evennia.accounts.accounts.DefaultAccount) named "TestAccount".
- `.account2` - Another account named "TestAccount2"
- `char1` - A [Character](evennia.objects.objects.DefaultCharacter) linked to `.account`, named `Char`.
This has 'Developer' permissions but is not a superuser.
- `.char2` - Another character linked to `account`, named `Char2`. This has base permissions (player).
- `.obj1` - A regular [Object](evennia.objects.objects.DefaultObject) named "Obj".
- `.obj2` - Another object named "Obj2".
- `.room1` - A [Room](evennia.objects.objects.DefaultRoom) named "Room". Both characters and both
objects are located inside this room. It has a description of "room_desc".
- `.room2` - Another room named "Room2". It is empty and has no set description.
- `.exit` - An exit named "out" that leads from `.room1` to `.room2`.
- `.script` - A [Script](evennia.scripts.scripts.DefaultScript) named "Script". It's an inert script
without a timing component.
- `.session` - A fake [Session](evennia.server.serversession.ServerSession) that mimics a player
connecting to the game. It is used by `.account1` and has a sessid of 1.
- `EvenniaCommandTest` - has the same environment like `EvenniaTest` but also adds a special
[.call()](evennia.utils.test_resources.EvenniaCommandTestMixin.call) method specifically for
testing Evennia [Commands](../Components/Commands.md). It allows you to compare what the command _actually_
returns to the player with what you expect. Read the `call` api doc for more info.
- `EvenniaTestCase` - This is identical to the regular Python `TestCase` class, it's
just there for naming symmetry with `BaseEvenniaTestCase` below.
Here's an example of using `EvenniaTest`
```python
# in a test module
from evennia.utils.test_resources import EvenniaTest
class TestObject(EvenniaTest):
"""Remember that the testing class creates char1 and char2 inside room1 ..."""
def test_object_search_character(self):
"""Check that char1 can search for char2 by name"""
self.assertEqual(self.char1.search(self.char2.key), self.char2)
def test_location_search(self):
"""Check so that char1 can find the current location by name"""
self.assertEqual(self.char1.search(self.char1.location.key), self.char1.location)
# ...
```
This example tests a custom command.
```python
from evennia.commands.default.tests import EvenniaCommandTest
from commands import command as mycommand
class TestSet(EvenniaCommandTest):
"tests the look command by simple call, using Char2 as a target"
def test_mycmd_char(self):
self.call(mycommand.CmdMyLook(), "Char2", "Char2(#7)")
def test_mycmd_room(self):
"tests the look command by simple call, with target as room"
self.call(mycommand.CmdMyLook(), "Room",
"Room(#1)\nroom_desc\nExits: out(#3)\n"
"You see: Obj(#4), Obj2(#5), Char2(#7)")
```
When using `.call`, you don't need to specify the entire string; you can just give the beginning
of it and if it matches, that's enough. Use `\n` to denote line breaks and (this is a special for
the `.call` helper), `||` to indicate multiple uses of `.msg()` in the Command. The `.call` helper
has a lot of arguments for mimicing different ways of calling a Command, so make sure to
[read the API docs for .call()](evennia.utils.test_resources.EvenniaCommandTestMixin.call).
#### Classes for testing Evennia core
These are used for testing Evennia itself. They provide the same resources as the classes
above but enforce Evennias default settings found in `evennia/settings_default.py`, ignoring
any settings changes in your game dir.
- `BaseEvenniaTest` - all the default objects above but with enforced default settings
- `BaseEvenniaCommandTest` - for testing Commands, but with enforced default settings
- `BaseEvenniaTestCase` - no default objects, only enforced default settings
There are also two special 'mixin' classes. These are uses in the classes above, but may also
be useful if you want to mix your own testing classes:
- `EvenniaTestMixin` - A class mixin that creates all test environment objects.
- `EvenniaCommandMixin` - A class mixin that adds the `.call()` Command-tester helper.
If you want to help out writing unittests for Evennia, take a look at Evennia's [coveralls.io
page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of
test coverage and which does not. All help is appreciated!
### Unit testing contribs with custom models
A special case is if you were to create a contribution to go to the `evennia/contrib` folder that
uses its [own database models](../Concepts/Models.md). The problem with this is that Evennia (and Django) will
only recognize models in `settings.INSTALLED_APPS`. If a user wants to use your contrib, they will
be required to add your models to their settings file. But since contribs are optional you cannot
add the model to Evennia's central `settings_default.py` file - this would always create your
optional models regardless of if the user wants them. But at the same time a contribution is a part
of the Evennia distribution and its unit tests should be run with all other Evennia tests using
`evennia test evennia`.
The way to do this is to only temporarily add your models to the `INSTALLED_APPS` directory when the test runs. here is an example of how to do it.
> Note that this solution, derived from this [stackexchange answer](http://stackoverflow.com/questions/502916/django-how-to-create-a-model-dynamically-just-for-testing#503435) is currently untested! Please report your findings.
```python
# a file contrib/mycontrib/tests.py
from django.conf import settings
import django
from evennia.utils.test_resources import BaseEvenniaTest
OLD_DEFAULT_SETTINGS = settings.INSTALLED_APPS
DEFAULT_SETTINGS = dict(
INSTALLED_APPS=(
'contrib.mycontrib.tests',
),
DATABASES={
"default": {
"ENGINE": "django.db.backends.sqlite3"
}
},
SILENCED_SYSTEM_CHECKS=["1_7.W001"],
)
class TestMyModel(BaseEvenniaTest):
def setUp(self):
if not settings.configured:
settings.configure(**DEFAULT_SETTINGS)
django.setup()
from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
def tearDown(self):
settings.configure(**OLD_DEFAULT_SETTINGS)
django.setup()
from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
# test cases below ...
def test_case(self):
# test case here
```
### A note on making the test runner faster
If you have custom models with a large number of migrations, creating the test database can take a very long time. If you don't require migrations to run for your tests, you can disable them with the
django-test-without-migrations package. To install it, simply:
```
$ pip install django-test-without-migrations
```
Then add it to your `INSTALLED_APPS` in your `server.conf.settings.py`:
```python
INSTALLED_APPS = (
# ...
'test_without_migrations',
)
```
After doing so, you can then run tests without migrations by adding the `--nomigrations` argument:
```
evennia test --settings settings.py --nomigrations .
```

View file

@ -0,0 +1,344 @@
# Coding using Version Control
[Version control](https://en.wikipedia.org/wiki/Version_control) allows you to track changes to your code. You can save 'snapshots' of your progress which means you can roll back undo things easily. Version control also allows you to easily back up your code to an online _repository_ such as Github. It also allows you to collaborate with others on the same code without clashing or worry about who changed what.
```{sidebar} Do it!
It's _strongly_ recommended that you [put your game folder under version control](#putting-your-game-dir-under-version-control). Using git is is also the way to contribue to Evennia itself.
```
Evennia uses the most commonly used version control system, [Git](https://git-scm.com/) . For additional help on using Git, please refer to the [Official GitHub documentation](https://help.github.com/articles/set-up-git#platform-all).
## Setting up Git
- **Fedora Linux**
yum install git-core
- **Debian Linux** _(Ubuntu, Linux Mint, etc.)_
apt-get install git
- **Windows**: It is recommended to use [Git for Windows](https://gitforwindows.org/).
- **Mac**: Mac platforms offer two methods for installation, one via MacPorts, which you can find out about [here](https://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac), or you can use the [Git OSX Installer](https://sourceforge.net/projects/git-osx-installer/).
> You can find expanded instructions for installation [here](https://git-scm.com/book/en/Getting-Started-Installing-Git).
```{sidebar} Git user nickname
If you ever make your code available online (or contribute to Evennia), your name will be visible to those reading the code-commit history. So if you are not comfortable with using your real, full name online, put a nickname (or your github handler) here.
```
To avoid a common issue later, you will need to set a couple of settings; first you will need to tell Git your username, followed by your e-mail address, so that when you commit code later you will be properly credited.
1. Set the default name for git to use when you commit:
git config --global user.name "Your Name Here"
2. Set the default email for git to use when you commit:
git config --global user.email "your_email@example.com"
> To get a running start with Git, here's [a good YouTube talk about it](https://www.youtube.com/watch?v=1ffBJ4sVUb4#t=1m58s). It's a bit long but it will help you understand the underlying ideas behind GIT (which in turn makes it a lot more intuitive to use).
## Common Git commands
```{sidebar} Git repository
This is just a fancy name for the folder you have designated to be under version control. We will make your `mygame` game folder into such a repository. The Evennia code is also in a (separate) git repository.
```
Git can be controlled via a GUI. But it's often easier to use the base terminal/console commands, since it makes it clear if something goes wrong.
All these actions need to be done from inside the _git repository_ .
Git may seem daunting at first. But when working with git, you'll be using the same 2-3 commands 99% of the time. And you can make git _aliases_ to have them be even easier to remember.
### `git init`
This initializes a folder/directory on your drive as a 'git repository'
git init .
The `.` means to apply to the current directory. If you are inside `mygame`, this makes your game dir into a git repository. That's all there is to it, really. You only need to do this once.
### `git add`
git add <file>
This tells Git to start to _track_ the file under version control. You need to do this when you create a new file. You can also add all files in your current directory:
git add .
Or
git add *
All files in the current directory are now tracked by Git. You only need to do this once for every file you want to track.
### `git commit`
git commit -a -m "This is the initial commit"
This _commits_ your changes. It stores a snapshot of all (`-a`) your code at the current time, adding a message `-m` so you know what you did. Later you can _check out_ your code the way it was at a given time. The message is mandatory and you will thank yourself later if write clear and descriptive log messages. If you don't add `-m`, a text editor opens for you to write the message instead.
The `git commit` is something you'll be using all the time, so it can be useful to make a _git alias_ for it:
git config --global alias.cma 'commit -a -m'
After you've run this, you can commit much simpler, like this:
git cma "This is the initial commit"
Much easier to remember!
### `git status`, `git diff` and `git log`
git status -s
This gives a short (`-s`) of the files that changes since your last `git commit`.
git diff --word-diff`
This shows exactly what changed in each file since you last made a `git commit`. The `--word-diff` option means it will mark if a single word changed on a line.
git log
This shows the log of all `commits` done. Each log will show you who made the change, the commit-message and a unique _hash_ (like `ba214f12ab12e123...`) that uniquely describes that commit.
You can make the `log` command more succinct with some more options:
ls=log --pretty=format:%C(green)%h\ %C(yellow)[%ad]%Cred%d\ %Creset%s%Cblue\ [%an] --decorate --date=relative
This adds coloration and another fancy effects (use `git help log` to see what they mean).
Let's add aliases:
git config --global alias.st 'status -s'
git config --global alias.df 'diff --word-diff'
git config --global alias.ls 'log --pretty=format:%C(green)%h\ %C(yellow)[%ad]%Cred%d\ %Creset%s%Cblue\ [%an] --decorate --date=relative'
You can now use the much shorter
git st # short status
git dif # diff with word-marking
git ls # log with pretty formatting
for these useful functions.
### `git branch`, `checkout` and `merge`
Git allows you to work with _branches_. These are separate development paths your code may take, completely separate from each other. You can later _merge_ the code from a branch back into another branch. Evennia's `main` and `develop` branches are examples of this.
git branch -b branchaname
This creates a new branch, exactly identical to the branch you were on. It also moves you to that branch.
git branch -D branchname
Deletes a branch.
git branch
Shows all your branches, marking which one you are currently on.
git checkout branchname
This checks out another branch. As long as you are in a branch all `git commit`s will commit the code to that branch only.
git checkout .
This checks out your _current branch_ and has the effect of throwing away all your changes since your last commit. This is like undoing what you did since the last save point.
git checkout b2342bc21c124
This checks out a particular _commit_, identified by the hash you find with `git log`. This open a 'temporary branch' where the code is as it was when you made this commit. As an example, you can use this to check where a bug was introduced. Check out an existing branch to go back to your normal timeline, or use `git branch -b newbranch` to break this code off into a new branch you can continue working from.
git merge branchname
This _merges_ the code from `branchname` into the branch you are currently in. Doing so may lead to _merge conflicts_ if the same code changed in different ways in the two branches. See [how to resolve merge conflicts in git](https://phoenixnap.com/kb/how-to-resolve-merge-conflicts-in-git) for more help.
### `git glone`, `git push` and `git pull`
All of these other commands have dealt with code only sitting in your local repository-folder. These commands instead allows you to exchange code with a _remote_ repository - usually one that is online (like on github).
> How you actually set up a remote repository is described [in the next section](#pushing-your-code-online).
git clone repository/path
This copies the remote repository to your current location. If you used the [Git installation instructions](../Setup/Installation-Git.md) to install Evennia, this is what you used to get your local copy of the Evennia repository.
git pull
Once you cloned or otherwise set up a remote repository, using `git pull` will re-sync the remote with what you have locally. If what you download clashes with local changes, git will force you to `git commit` your changes before you can continue with `git pull`.
git push
This uploads your local changes _of your current branch_ to the same-named branch on the remote repository. To be able to do this you must have write-permissions to the remote repository.
### Other git commands
There are _many_ other git commands. Read up on them online:
git reflog
Shows hashes of individual git actions. This allows you to go back in the git event history itself.
git reset
Force reset a branch to an earlier commit. This could throw away some history, so be careful.
git grep -n -I -i <query>
Quickly search for a phrase/text in all files tracked by git. Very useful to quickly find where things are. Set up an alias `git gr` with
```
git config --global alias.gr 'grep -n -I -i'
```
## Putting your game dir under version control
This makes use of the git commands listed in the previous section.
```{sidebar} git aliases
If you set up the git aliases for commands suggested in the previous section, you can use them instead!
```
cd mygame
git init .
git add *
git commit -a -m "Initial commit"
Your game-dir is now tracked by git.
You will notice that some files are not covered by your git version control, notably your secret-settings file (`mygame/server/conf/secret_settings.py`) and your sqlite3 database file `mygame/server/evennia.db3`. This is intentional and controlled from the file `mygame/.gitignore`.
```{warning}
You should *never* put your sqlite3 database file into git by removing its entry
in `.gitignore`. GIT is for backing up your code, not your database. That way
lies madness and a good chance you'll confuse yourself. Make one mistake or local change and after a few commits and reverts you will have lost track of what is in your database or not. If you want to backup your SQlite3 database, do so by simply copying the database file to a safe location.
```
### Pushing your code online
So far your code is only located on your private machine. A good idea is to back it up online. The easiest way to do this is to `git push` it to your own remote repository on GitHub. So for this you need a (free) Github account.
If you don't want your code to be publicly visible, Github also allows you set up a _private_ repository, only visible to you.
Create a new, empty repository on Github. [Github explains how here](https://help.github.com/articles/create-a-repo/) . _Don't_ allow it to add a README, license etc, that will just clash with what we upload later.
```{sidebar} Origin
We label the remote repository 'origin'. This is the git default and means we won't need to specify it explicitly later.
```
Make sure you are in your local game dir (previously initialized as a git repo).
git remote add origin <github URL>
This tells Git that there is a remote repository at `<github URL>`. See the github docs as to which URL to use. Verify that the remote works with `git remote -v`
Now we push to the remote (labeled 'origin' which is the default):
git push
Depending on how you set up your authentication with github, you may be asked to enter your github username and password. If you set up SSH authentication, this command will just work.
You use `git push` to upload your local changes so the remote repository is in sync with your local one. If you edited a file online using the Github editor (or a collaborator pushed code), you use `git pull` to sync in the other direction.
## Contributing to Evennia
If you want to help contributing to Evennia you must do so by _forking_ - making your own remote copy of the Evennia repository on Github. So for this, you need a (free) Github account. Doing so is a completely separate process from [putting your game dir under version control](#putting-your-game-dir-under-version-control) (which you should also do!).
At the top right of [the evennia github page](https://github.com/evennia/evennia), click the "Fork" button:
![fork button](../_static/images/fork_button.png)
This will create a new online fork Evennia under your github account.
The fork only exists online as of yet. In a terminal, `cd` to the folder you wish to develop in. This folder should _not_ be your game dir, nor the place you cloned Evennia into if you used the [Git installation](../Setup/Installation-Git.md).
From this directory run the following command:
git clone https://github.com/yourusername/evennia.git evennia
This will download your fork to your computer. It creates a new folder `evennia/` at your current location. If you installed Evennia using the [Git installation](../Setup/Installation-Git.md), this folder will be identical in content to the `evennia` folder you cloned during that installation. The difference is that this repo is connected to your remote fork and not to the 'original' _upstream_ Evennia.
When we cloned our fork, git automatically set up a 'remote repository' labeled `origin` pointing to it. So if we do `git pull` and `git push`, we'll push to our fork.
We now want to add a second remote repository linked to the original Evennia repo. We will label this remote repository `upstream`:
cd evennia
git remote add upstream https://github.com/evennia/evennia.git
If you also want to access Evennia's `develop` branch (the bleeding edge development) do the following:
git fetch upstream develop
git checkout develop
Use
git checkout main
git checkout develop
to switch between the branches.
To pull the latest from upstream Evennia, just checkout the branch you want and do
git pull upstream
```{sidebar} Pushing to upstream
You can't do `git push upstream` unless you have write-access to the upstream Evennia repository. So there is no risk of you accidentally pushing your own code into the main, public repository.
```
### Fixing an Evennia bug or feature
This should be done in your fork of Evennia. You should _always_ do this in a _separate git branch_ based off the Evennia branch you want to improve.
git checkout main (or develop)
git branch -b myfixbranch
Now fix whatever needs fixing. Abide by the [Evennia code style](./Evennia-Code-Style.md). You can `git commit` commit your changes along the way as normal.
Upstream Evennia is not standing still, so you want to make sure that your work is up-to-date with upstream changes. Make sure to first commit your `myfixbranch` changes, then
git checkout main (or develop)
git pull upstream
git checkout myfixbranch
git merge main (or develop)
Up to this point your `myfixbranch` branch only exists on your local computer. No
one else can see it.
git push
This will automatically create a matching `myfixbranch` in your forked version of Evennia and push to it. On github you will be able to see appear it in the `branches` dropdown. You can keep pushing to your remote `myfixbranch` as much as you like.
Once you feel you have something to share, you need to [create a pull request](https://github.com/evennia/evennia/pulls) (PR):
This is a formal request for upstream Evennia to adopt and pull your code into the main repository.
1. Click `New pull request`
2. Choose `compare across forks`
3. Select your fork from dropdown list of `head repository` repos. Pick the right branch to `compare`.
4. On the Evennia side (to the left) make sure to pick the right `base` branch: If you want to contribute a change to the `develop` branch, you must pick `develop` as the `base`.
5. Then click `Create pull request` and fill in as much information as you can in the form.
6. Optional: Once you saved your PR, you can go into your code (on github) and add some per-line comments; this can help reviewers by explaining complex code or decisions you made.
Now you just need to wait for your code to be reviewed. Expect to get feedback and be asked to make changes, add more documentation etc. Getting as PR merged can take a few iterations.
```{sidebar} Not all PRs can merge
While most PRs get merged, Evennia can't **guarantee** that your PR code will be deemed suitable to merge into upstream Evennia. For this reason it's a good idea to check in with the community _before_ you spend a lot of time on a large piece of code (fixing bugs is always a safe bet though!)
```
## Troubleshooting
### Getting 403: Forbidden access
Some users have experienced this on `git push` to their remote repository. They are not asked for username/password (and don't have a ssh key set up).
Some users have reported that the workaround is to create a file `.netrc` under your home directory and add your github credentials there:
```bash
machine github.com
login <my_github_username>
password <my_github_password>
```