Refactoring Concept/Component documentation. Still not done

This commit is contained in:
Griatch 2022-11-26 15:37:02 +01:00
parent 7114aea912
commit c7ec3dfad3
42 changed files with 745 additions and 1275 deletions

View file

@ -1,14 +1,17 @@
# Async Process
*This is considered an advanced topic.*
```{important}
This is considered an advanced topic.
```
## Synchronous versus Asynchronous
Most program code operates *synchronously*. This means that each statement in your code gets
processed and finishes before the next can begin. This makes for easy-to-understand code. It is also
a *requirement* in many cases - a subsequent piece of code often depend on something calculated or
defined in a previous statement.
```{sidebar}
This is also explored in the [Command Duration Tutorial](../Howtos/Howto-Command-Duration.md).
```
Most program code operates *synchronously*. This means that each statement in your code gets processed and finishes before the next can begin. This makes for easy-to-understand code. It is also a *requirement* in many cases - a subsequent piece of code often depend on something calculated or defined in a previous statement.
Consider this piece of code in a traditional Python program:
@ -16,27 +19,94 @@ Consider this piece of code in a traditional Python program:
print("before call ...")
long_running_function()
print("after call ...")
```
When run, this will print `"before call ..."`, after which the `long_running_function` gets to work
for however long time. Only once that is done, the system prints `"after call ..."`. Easy and
logical to follow. Most of Evennia work in this way and often it's important that commands get
for however long time. Only once that is done, the system prints `"after call ..."`. Easy and logical to follow. Most of Evennia work in this way and often it's important that commands get
executed in the same strict order they were coded.
Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it
swiftly switches between dealing with player input so quickly that each player feels like they do
things at the same time. This is a clever illusion however: If one user, say, runs a command
containing that `long_running_function`, *all* other players are effectively forced to wait until it
finishes.
Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it swiftly switches between dealing with player input so quickly that each player feels like they do things at the same time. This is a clever illusion however: If one user, say, runs a command containing that `long_running_function`, *all* other players are effectively forced to wait until it finishes.
Now, it should be said that on a modern computer system this is rarely an issue. Very few commands
run so long that other users notice it. And as mentioned, most of the time you *want* to enforce
all commands to occur in strict sequence.
Now, it should be said that on a modern computer system this is rarely an issue. Very few commands run so long that other users notice it. And as mentioned, most of the time you *want* to enforce all commands to occur in strict sequence.
When delays do become noticeable and you don't care in which order the command actually completes,
you can run it *asynchronously*. This makes use of the `run_async()` function in
`src/utils/utils.py`:
## `utils.delay`
```{sidebar} delay() vs time.sleep()
This is equivalent to something like `time.sleep()` except `delay` is asynchronous while `sleep` would lock the entire server for the duration of the sleep.
```
The `delay` function is a much simpler sibling to `run_async`. It is in fact just a way to delay the execution of a command until a future time.
```python
from evennia.utils import delay
# [...]
# e.g. inside a Command, where `self.caller` is available
def callback(obj):
obj.msg("Returning!")
delay(10, callback, self.caller)
```
This will delay the execution of the callback for 10 seconds. Provide `persistent=True` to make the delay survive a server `reload`. While waiting, you can input commands normally.
You can also try the following snippet just see how it works:
py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self)
Wait 10 seconds and 'Test!' should be echoed back to you.
## `@utils.interactive` decorator
The `@interactive` [decorator](https://realpython.com/primer-on-python- decorators/) makes any function or method possible to 'pause' and/or await player input in an interactive way.
```python
from evennia.utils import interactive
@interactive
def myfunc(caller):
while True:
caller.msg("Getting ready to wait ...")
yield(5)
caller.msg("Now 5 seconds have passed.")
response = yield("Do you want to wait another 5 secs?")
if response.lower() not in ("yes", "y"):
break
```
The `@interactive` decorator gives the function the ability to pause. The use of `yield(seconds)` will do just that - it will asynchronously pause for the number of seconds given before continuing. This is technically equivalent to using `call_async` with a callback that continues after 5 secs. But the code with `@interactive` is a little easier to follow.
Within the `@interactive` function, the `response = yield("question")` question allows you to ask the user for input. You can then process the input, just like you would if you used the Python `input` function.
All of this makes the `@interactive` decorator very useful. But it comes with a few caveats.
- The decorated function/method/callable must have an argument named exactly `caller`. Evennia will look for an argument with this name and treat it as the source of input.
- Decorating a function this way turns it turns it into a Python [generator](https://wiki.python.org/moin/Generators). This means
- You can't use `return <value>` from a generator (just an empty `return` works). To return a value from a function/method you have decorated with `@interactive`, you must instead use a special Twisted function `twisted.internet.defer.returnValue`. Evennia also makes this function conveniently available from `evennia.utils`:
```python
from evennia.utils import interactive, returnValue
@interactive
def myfunc():
# ...
result = 10
# this must be used instead of `return result`
returnValue(result)
```
## `utils.run_async`
```{warning}
Unless you have a very clear purpose in mind, you are unlikely to get an expected result from `run_async`. Notably, it will still run your long-running function _in the same thread_ as the rest of the server. So while it does run async, a very heavy and CPU-heavy operation will still block the server. So don't consider this as a way to offload heavy operations without affecting the rest of the server.
```
When you don't care in which order the command actually completes, you can run it *asynchronously*. This makes use of the `run_async()` function in `src/utils/utils.py`:
```python
run_async(function, *args, **kwargs)
@ -51,12 +121,7 @@ Where `function` will be called asynchronously with `*args` and `**kwargs`. Exam
print("after call ...")
```
Now, when running this you will find that the program will not wait around for
`long_running_function` to finish. In fact you will see `"before call ..."` and `"after call ..."`
printed out right away. The long-running function will run in the background and you (and other
users) can go on as normal.
## Customizing asynchronous operation
Now, when running this you will find that the program will not wait around for `long_running_function` to finish. In fact you will see `"before call ..."` and `"after call ..."` printed out right away. The long-running function will run in the background and you (and other users) can go on as normal.
A complication with using asynchronous calls is what to do with the result from that call. What if
`long_running_function` returns a value that you need? It makes no real sense to put any lines of
@ -75,8 +140,7 @@ line quite pointless for processing any data from the function. Instead one has
print(r)
```
- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the
`at_return` callback.
- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the `at_return` callback.
- `at_err(e)` (the *errback*) is called if the asynchronous function fails and raises an exception.
This exception is passed to the errback wrapped in a *Failure* object `e`. If you do not supply an
errback of your own, Evennia will automatically add one that silently writes errors to the evennia
@ -116,119 +180,6 @@ An example of making an asynchronous call from inside a [Command](../Components/
at_err=at_err_function)
```
That's it - from here on we can forget about `long_running_function` and go on with what else need
to be done. *Whenever* it finishes, the `at_return_function` function will be called and the final
value will
pop up for us to see. If not we will see an error message.
That's it - from here on we can forget about `long_running_function` and go on with what else need to be done. *Whenever* it finishes, the `at_return_function` function will be called and the final value will pop up for us to see. If not we will see an error message.
## delay
The `delay` function is a much simpler sibling to `run_async`. It is in fact just a way to delay the
execution of a command until a future time. This is equivalent to something like `time.sleep()`
except delay is asynchronous while `sleep` would lock the entire server for the duration of the
sleep.
```python
from evennia.utils import delay
# [...]
# e.g. inside a Command, where `self.caller` is available
def callback(obj):
obj.msg("Returning!")
delay(10, callback, self.caller)
```
This will delay the execution of the callback for 10 seconds. This function is explored much more in
the [Command Duration Tutorial](../Howtos/Howto-Command-Duration.md).
You can also try the following snippet just see how it works:
@py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self)
Wait 10 seconds and 'Test!' should be echoed back to you.
## The @interactive decorator
As of Evennia 0.9, the `@interactive` [decorator](https://realpython.com/primer-on-python-
decorators/)
is available. This makes any function or method possible to 'pause' and/or await player input
in an interactive way.
```python
from evennia.utils import interactive
@interactive
def myfunc(caller):
while True:
caller.msg("Getting ready to wait ...")
yield(5)
caller.msg("Now 5 seconds have passed.")
response = yield("Do you want to wait another 5 secs?")
if response.lower() not in ("yes", "y"):
break
```
The `@interactive` decorator gives the function the ability to pause. The use
of `yield(seconds)` will do just that - it will asynchronously pause for the
number of seconds given before continuing. This is technically equivalent to
using `call_async` with a callback that continues after 5 secs. But the code
with `@interactive` is a little easier to follow.
Within the `@interactive` function, the `response = yield("question")` question
allows you to ask the user for input. You can then process the input, just like
you would if you used the Python `input` function. There is one caveat to this
functionality though - _it will only work if the function/method has an
argument named exactly `caller`_. This is because internally Evennia will look
for the `caller` argument and treat that as the source of input.
All of this makes the `@interactive` decorator very useful. But it comes with a
few caveats. Notably, decorating a function/method with `@interactive` turns it
into a Python [generator](https://wiki.python.org/moin/Generators). The most
common issue is that you cannot use `return <value>` from a generator (just an
empty `return` works). To return a value from a function/method you have decorated
with `@interactive`, you must instead use a special Twisted function
`twisted.internet.defer.returnValue`. Evennia also makes this function
conveniently available from `evennia.utils`:
```python
from evennia.utils import interactive, returnValue
@interactive
def myfunc():
# ...
result = 10
# this must be used instead of `return result`
returnValue(result)
```
## Assorted notes
Overall, be careful with choosing when to use asynchronous calls. It is mainly useful for large
administration operations that have no direct influence on the game world (imports and backup
operations come to mind). Since there is no telling exactly when an asynchronous call actually ends,
using them for in-game commands is to potentially invite confusion and inconsistencies (and very
hard-to-reproduce bugs).
The very first synchronous example above is not *really* correct in the case of Twisted, which is
inherently an asynchronous server. Notably you might find that you will *not* see the first `before
call ...` text being printed out right away. Instead all texts could end up being delayed until
after the long-running process finishes. So all commands will retain their relative order as
expected, but they may appear with delays or in groups.
## Further reading
Technically, `run_async` is just a very thin and simplified wrapper around a
[Twisted Deferred](https://twistedmatrix.com/documents/9.0.0/core/howto/defer.html) object; the
wrapper sets
up a default errback also if none is supplied. If you know what you are doing there is nothing
stopping you from bypassing the utility function, building a more sophisticated callback chain after
your own liking.
> Technically, `run_async` is just a very thin and simplified wrapper around a [Twisted Deferred](https://twistedmatrix.com/documents/9.0.0/core/howto/defer.html) object; the wrapper sets up a default errback also if none is supplied. If you know what you are doing there is nothing stopping you from bypassing the utility function, building a more sophisticated callback chain after your own liking.

View file

@ -7,10 +7,7 @@ admin tools to handle this, primarily `ban`, `unban`, and `boot`.
## Creating a ban
Say we have a troublesome player "YouSuck" - this is a person that refuses common courtesy - an
abusive
and spammy account that is clearly created by some bored internet hooligan only to cause grief. You
have tried to be nice. Now you just want this troll gone.
Say we have a troublesome player "YouSuck" - this is a person that refuses common courtesy - an abusive and spammy account that is clearly created by some bored internet hooligan only to cause grief. You have tried to be nice. Now you just want this troll gone.
### Name ban
@ -18,16 +15,13 @@ The easiest recourse is to block the account YouSuck from ever connecting again.
ban YouSuck
This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and
next time they try to log in with this name the server will not let them!
This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and next time they try to log in with this name the server will not let them!
You can also give a reason so you remember later why this was a good thing (the banned account will
never see this)
You can also give a reason so you remember later why this was a good thing (the banned account will never see this)
ban YouSuck:This is just a troll.
If you are sure this is just a spam account, you might even consider deleting the player account
outright:
If you are sure this is just a spam account, you might even consider deleting the player account outright:
accounts/delete YouSuck
@ -36,9 +30,7 @@ change your mind you can always remove the block later whereas a deletion is per
### IP ban
Just because you block YouSuck's name might not mean the trolling human behind that account gives
up. They can just create a new account YouSuckMore and be back at it. One way to make things harder
for them is to tell the server to not allow connections from their particular IP address.
Just because you block YouSuck's name might not mean the trolling human behind that account gives up. They can just create a new account YouSuckMore and be back at it. One way to make things harder for them is to tell the server to not allow connections from their particular IP address.
First, when the offending account is online, check which IP address they use. This you can do with
the `who` command, which will show you something like this:
@ -46,42 +38,31 @@ the `who` command, which will show you something like this:
Account Name On for Idle Room Cmds Host
YouSuckMore 01:12 2m 22 212 237.333.0.223
The "Host" bit is the IP address from which the account is connecting. Use this to define the ban
instead of the name:
The "Host" bit is the IP address from which the account is connecting. Use this to define the ban instead of the name:
ban 237.333.0.223
This will stop YouSuckMore connecting from their computer. Note however that IP address might change
easily - either due to how the player's Internet Service Provider operates or by the user simply
changing computers. You can make a more general ban by putting asterisks `*` as wildcards for the
groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from
237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea
to put down a ban like this to include any number in that subnet:
This will stop YouSuckMore connecting from their computer. Note however that IP address might change easily - either due to how the player's Internet Service Provider operates or by the user simply changing computers. You can make a more general ban by putting asterisks `*` as wildcards for the groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from `237.333.0.223`, `237.333.0.225`, and `237.333.0.256` (only changes in their subnet), it might be an idea to put down a ban like this to include any number in that subnet:
ban 237.333.0.*
You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly
locked regardless of where they connect from.
You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly locked regardless of where they connect from.
Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be
blocking out innocent players who just happen to connect from the same subnet as the offender.
Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be blocking out innocent players who just happen to connect from the same subnet as the offender.
## Booting
YouSuck is not really noticing all this banning yet though - and won't until having logged out and
trying to log back in again. Let's help the troll along.
YouSuck is not really noticing all this banning yet though - and won't until having logged out and trying to log back in again. Let's help the troll along.
boot YouSuck
Good riddance. You can give a reason for booting too (to be echoed to the player before getting
kicked out).
Good riddance. You can give a reason for booting too (to be echoed to the player before getting kicked out).
boot YouSuck:Go troll somewhere else.
### Lifting a ban
Use the `unban` (or `ban`) command without any arguments and you will see a list of all currently
active bans:
Use the `unban` (or `ban`) command without any arguments and you will see a list of all currently active bans:
Active bans
id name/ip date reason
@ -113,32 +94,21 @@ is not what you want in this case.
- **unban 34** -- Remove ban with id #34
- **cboot mychannel = thomas** -- Boot a subscriber from a channel you control
- **clock mychannel = control:perm(Admin);listen:all();send:all()** -- Fine control of access to
your channel using [lock definitions](../Components/Locks.md).
- **clock mychannel = control:perm(Admin);listen:all();send:all()** -- Fine control of access to your channel using [lock definitions](../Components/Locks.md).
Locking a specific command (like `page`) is accomplished like so:
1. Examine the source of the command. [The default `page` command class](
https://github.com/evennia/evennia/blob/master/evennia/commands/default/comms.py#L686) has the lock
string **"cmd:not pperm(page_banned)"**. This means that unless the player has the 'permission'
"page_banned" they can use this command. You can assign any lock string to allow finer customization
in your commands. You might look for the value of an [Attribute](../Components/Attributes.md) or [Tag](../Components/Tags.md), your
current location etc.
2. **perm/account thomas = page_banned** -- Give the account the 'permission' which causes (in this
case) the lock to fail.
- **perm/del/account thomas = page_banned** -- Remove the given permission
1. Examine the source of the command. [The default `page` command class]( https://github.com/evennia/evennia/blob/master/evennia/commands/default/comms.py#L686) has the lock string **"cmd:not pperm(page_banned)"**. This means that unless the player has the 'permission' "page_banned" they can use this command. You can assign any lock string to allow finer customization in your commands. You might look for the value of an [Attribute](../Components/Attributes.md) or [Tag](../Components/Tags.md), your current location etc.
2. **perm/account thomas = page_banned** -- Give the account the 'permission' which causes (in this case) the lock to fail.
- **perm/del/account thomas = page_banned** -- Remove the given permission
- **tel thomas = jail** -- Teleport a player to a specified location or #dbref
- **type thomas = FlowerPot** -- Turn an annoying player into a flower pot (assuming you have a
`FlowerPot` typeclass ready)
- **type thomas = FlowerPot** -- Turn an annoying player into a flower pot (assuming you have a `FlowerPot` typeclass ready)
- **userpassword thomas = fooBarFoo** -- Change a user's password
- **accounts/delete thomas** -- Delete a player account (not recommended, use **ban** instead)
- **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are
cached
- **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are cached
- **time** -- Gives server uptime, runtime, etc
- **reload** -- Reloads the server without disconnecting anyone
- **reset** -- Restarts the server, kicking all connections
- **shutdown** -- Stops the server cold without it auto-starting again
- **py** -- Executes raw Python code, allows for direct inspection of the database and account
objects on the fly. For advanced users.
- **py** -- Executes raw Python code, allows for direct inspection of the database and account objects on the fly. For advanced users.

View file

@ -1,101 +0,0 @@
# Bootstrap & Evennia
# What is Bootstrap?
Evennia's new default web page uses a framework called [Bootstrap](https://getbootstrap.com/). This
framework is in use across the internet - you'll probably start to recognize its influence once you
learn some of the common design patterns. This switch is great for web developers, perhaps like
yourself, because instead of wondering about setting up different grid systems or what custom class
another designer used, we have a base, a bootstrap, to work from. Bootstrap is responsive by
default, and comes with some default styles that Evennia has lightly overrode to keep some of the
same colors and styles you're used to from the previous design.
For your reading pleasure, a brief overview of Bootstrap follows. For more in-depth info, please
read [the documentation](https://getbootstrap.com/docs/4.0/getting-started/introduction/).
***
## The Layout System
Other than the basic styling Bootstrap includes, it also includes [a built in layout and grid
system](https://getbootstrap.com/docs/4.0/layout/overview/).
The first part of this system is [the
container](https://getbootstrap.com/docs/4.0/layout/overview/#containers).
The container is meant to hold all your page content. Bootstrap provides two types: fixed-width and
full-width.
Fixed-width containers take up a certain max-width of the page - they're useful for limiting the
width on Desktop or Tablet platforms, instead of making the content span the width of the page.
```
<div class="container">
<!--- Your content here -->
</div>
```
Full width containers take up the maximum width available to them - they'll span across a wide-
screen desktop or a smaller screen phone, edge-to-edge.
```
<div class="container-fluid">
<!--- This content will span the whole page -->
</div>
```
The second part of the layout system is [the grid](https://getbootstrap.com/docs/4.0/layout/grid/).
This is the bread-and-butter of the layout of Bootstrap - it allows you to change the size of
elements depending on the size of the screen, without writing any media queries. We'll briefly go
over it - to learn more, please read the docs or look at the source code for Evennia's home page in
your browser.
> Important! Grid elements should be in a .container or .container-fluid. This will center the
contents of your site.
Bootstrap's grid system allows you to create rows and columns by applying classes based on
breakpoints. The default breakpoints are extra small, small, medium, large, and extra-large. If
you'd like to know more about these breakpoints, please [take a look at the documentation for
them.](https://getbootstrap.com/docs/4.0/layout/overview/#responsive-breakpoints)
To use the grid system, first create a container for your content, then add your rows and columns
like so:
```
<div class="container">
<div class="row">
<div class="col">
1 of 3
</div>
<div class="col">
2 of 3
</div>
<div class="col">
3 of 3
</div>
</div>
</div>
```
This layout would create three equal-width columns.
To specify your sizes - for instance, Evennia's default site has three columns on desktop and
tablet, but reflows to single-column on smaller screens. Try it out!
```
<div class="container">
<div class="row">
<div class="col col-md-6 col-lg-3">
1 of 4
</div>
<div class="col col-md-6 col-lg-3">
2 of 4
</div>
<div class="col col-md-6 col-lg-3">
3 of 4
</div>
<div class="col col-md-6 col-lg-3">
4 of 4
</div>
</div>
</div>
```
This layout would be 4 columns on large screens, 2 columns on medium screens, and 1 column on
anything smaller.
To learn more about Bootstrap's grid, please [take a look at the
docs](https://getbootstrap.com/docs/4.0/layout/grid/)
***
## More Bootstrap
Bootstrap also provides a huge amount of utilities, as well as styling and content elements. To
learn more about them, please [read the Bootstrap docs](https://getbootstrap.com/docs/4.0/getting-
started/introduction/) or read one of our other web tutorials.

View file

@ -1,72 +0,0 @@
# Building Permissions
*OBS: This gives only a brief introduction to the access system. Locks and permissions are fully
detailed* [here](../Components/Locks.md).
## The super user
There are strictly speaking two types of users in Evennia, the *super user* and everyone else. The
superuser is the first user you create, object `#1`. This is the all-powerful server-owner account.
Technically the superuser not only has access to everything, it *bypasses* the permission checks
entirely. This makes the superuser impossible to lock out, but makes it unsuitable to actually play-
test the game's locks and restrictions with (see `@quell` below). Usually there is no need to have
but one superuser.
## Assigning permissions
Whereas permissions can be used for anything, those put in `settings.PERMISSION_HIERARCHY` will have
a ranking relative each other as well. We refer to these types of permissions as *hierarchical
permissions*. When building locks to check these permissions, the `perm()` [lock function](../Components/Locks.md) is
used. By default Evennia creates the following hierarchy (spelled exactly like this):
1. **Developers** basically have the same access as superusers except that they do *not* sidestep
the Permission system. Assign only to really trusted server-admin staff since this level gives
access both to server reload/shutdown functionality as well as (and this may be more critical) gives
access to the all-powerful `@py` command that allows the execution of arbitrary Python code on the
command line.
1. **Admins** can do everything *except* affecting the server functions themselves. So an Admin
couldn't reload or shutdown the server for example. They also cannot execute arbitrary Python code
on the console or import files from the hard drive.
1. **Builders** - have all the build commands, but cannot affect other accounts or mess with the
server.
1. **Helpers** are almost like a normal *Player*, but they can also add help files to the database.
1. **Players** is the default group that new players end up in. A new player have permission to use
tells and to use and create new channels.
A user having a certain level of permission automatically have access to locks specifying access of
a lower level.
To assign a new permission from inside the game, you need to be able to use the `@perm` command.
This is an *Developer*-level command, but it could in principle be made lower-access since it only
allows assignments equal or lower to your current level (so you cannot use it to escalate your own
permission level). So, assuming you yourself have *Developer* access (or is superuser), you assign
a new account "Tommy" to your core staff with the command
@perm/account Tommy = Developer
or
@perm *Tommy = Developer
We use a switch or the `*name` format to make sure to put the permission on the *Account* and not on
any eventual *Character* that may also be named "Tommy". This is usually what you want since the
Account will then remain an Developer regardless of which Character they are currently controlling.
To limit permission to a per-Character level you should instead use *quelling* (see below). Normally
permissions can be any string, but for these special hierarchical permissions you can also use
plural ("Developer" and "Developers" both grant the same powers).
## Quelling your permissions
When developing it can be useful to check just how things would look had your permission-level been
lower. For this you can use *quelling*. Normally, when you puppet a Character you are using your
Account-level permission. So even if your Character only has *Accounts* level permissions, your
*Developer*-level Account will take precedence. With the `@quell` command you can change so that the
Character's permission takes precedence instead:
@quell
This will allow you to test out the game using the current Character's permission level. A developer
or builder can thus in principle maintain several test characters, all using different permission
levels. Note that you cannot escalate your permissions this way; If the Character happens to have a
*higher* permission level than the Account, the *Account's* (lower) permission will still be used.

View file

@ -1,7 +1,6 @@
# Sending different messages depending on viewpoint and receiver
# Messages varying per receiver
Sending messages to everyong in a location is handled by the
[msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method on
Sending messages to everyong in a location is handled by the [msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method on
all [Objects](../Components/Objects.md). It's most commonly called on rooms.
```python
@ -19,26 +18,21 @@ room.msg_contents("{anna} walks into the room.",
Use `exclude=object_or_list_of_object` to skip sending the message one or more targets.
The advantage of this is that `anna_object.get_display_name(looker)` will be called
for every onlooker; this allows the `{anna}` stanza to be different depending on who
sees the strings. How this is to work depends on the _stance_ of your game.
The advantage of this is that `anna_object.get_display_name(looker)` will be called for every onlooker; this allows the `{anna}` stanza to be different depending on who sees the strings. How this is to work depends on the _stance_ of your game.
The stance indicates how your game echoes its messages to the player. Knowing how you want to
handle the stance is important for a text game. There are two main stances that are usually considered,
_Actor stance_ and _Director stance_.
handle the stance is important for a text game. There are two main stances that are usually considered, _Actor stance_ and _Director stance_.
| Stance | You see | Others in the same location see |
| --- | --- | --- |
| Actor stance | You pick up the stone | Anna picks up the stone |
|Director stance | Anna picks up the stone | Anna picks up the stone |
It's not unheard of to mix the two stances - with commands from the game being told
in Actor stance while Director stance is used for complex emoting and roleplaying. One should
usually try to be consistent however.
It's not unheard of to mix the two stances - with commands from the game being told in Actor stance while Director stance is used for complex emoting and roleplaying. One should usually try to be consistent however.
## Director Stance
While not so common as Actor stance, director stance has the advantage of simplicity, particularly
While not as common as Actor stance, director stance has the advantage of simplicity, particularly
in roleplaying MUDs where longer roleplaying emotes are used. It is also a pretty simple stance to
implement technically since everyone sees the same text, regardless of viewpoint.
@ -60,8 +54,7 @@ technically, it's also easy to write for the player.
## Actor Stance
This means that the game addresses "you" when it does things. In actor stance, whenever you perform
an action, you should get a different message than those _observing_ you doing that action.
This means that the game addresses "you" when it does things. In actor stance, whenever you perform an action, you should get a different message than those _observing_ you doing that action.
Tom picks up the gun, whistling to himself.
@ -69,16 +62,9 @@ This is what _others_ should see. The player themselves should see this:
You pick up the gun, whistling to yourself.
Not only do you need to map "Tom" to "You" above, there are also grammatical differences -
"Tom walks" vs "You walk" and "himself" vs "yourself". This is a lot more complex to handle. For a
developer making simple "You/Tom pick/picks up the stone" messages, you could in principle hand-craft
the strings from every view point, but there's a better way.
The `msg_contents` method helps by parsing the ingoing string with a
[FuncParser functions](../Components/FuncParser.md) with some very specific `$inline-functions`. The inline funcs
basically provides you with a mini-language for building _one_ string that will change
appropriately depending on who sees it.
Not only do you need to map "Tom" to "You" above, there are also grammatical differences - "Tom walks" vs "You walk" and "himself" vs "yourself". This is a lot more complex to handle. For a developer making simple "You/Tom pick/picks up the stone" messages, you could in principle hand-craft the strings from every view point, but there's a better way.
The `msg_contents` method helps by parsing the ingoing string with a [FuncParser functions](../Components/FuncParser.md) with some very specific `$inline-functions`. The inline funcs basically provides you with a mini-language for building _one_ string that will change appropriately depending on who sees it.
```python
text = "$You() $conj(pick) up the gun, whistling to $pron(yourself)."
@ -93,8 +79,7 @@ These are the inline-functions available:
to `picks`). Enter the root form of the verb.
- `$pron(pronoun[,options])` - A pronoun is a word you want to use instead of a proper noun, like
_him_, _herself_, _its_, _me_, _I_, _their_ and so on. The `options` is a space- or comma-separated
set of options to help the system map your pronoun from 1st/2nd person to 3rd person and vice versa.
See next section.
set of options to help the system map your pronoun from 1st/2nd person to 3rd person and vice versa. See next section.
### More on $pron()
@ -117,15 +102,7 @@ it translates between this table ...
| **3rd person neutral** | it | it | its | theirs* | itself |
| **3rd person plural** | they | them | their | theirs | themselves |
> *) The neutral 3rd person possessive pronoun is not actually used in English. We set it to "theirs"
> just to have something to show should someone accidentally ask for a neutral possessive pronoun.
Some mappings are easy. For example, if you write `$pron(yourselves)` then the 3rd-person
form is always `themselves`. But because English grammar is the way it is, not all mappings
are 1:1. For example, if you write
`$pron(you)`, Evennia will not know which 3rd-persion equivalent this should map to - you need to
provide more info to help out. This can either be provided as a second space-separated option
to `$pron` or the system will try to figure it out on its own.
Some mappings are easy. For example, if you write `$pron(yourselves)` then the 3rd-person form is always `themselves`. But because English grammar is the way it is, not all mappings are 1:1. For example, if you write `$pron(you)`, Evennia will not know which 3rd-persion equivalent this should map to - you need to provide more info to help out. This can either be provided as a second space-separated option to `$pron` or the system will try to figure it out on its own.
- `pronoun_type` - this is one of the columns in the table and can be set as a `$pron` option.
@ -176,14 +153,11 @@ to `$pron` or the system will try to figure it out on its own.
| `$pron(her, 1)` | I | her | 3rd person -> 1st person |
| `$pron(its, 1st)` | my | its | 3rd person -> 1st person |
Note the three last examples - instead of specifying the 2nd person form you
can also specify the 3rd-person and do a 'reverse' lookup - you will still see the proper 1st/2nd text.
So writing `$pron(her)` instead of `$pron(you, op f)` gives the same result.
Note the three last examples - instead of specifying the 2nd person form you can also specify the 3rd-person and do a 'reverse' lookup - you will still see the proper 1st/2nd text. So writing `$pron(her)` instead of `$pron(you, op f)` gives the same result.
The [$pron inlinefunc api is found here](evennia.utils.funcparser.funcparser_callable_pronoun)
# Referencing other objects
## Referencing other objects
There is one more inlinefunc understood by `msg_contents`. This can be used natively to spruce up
your strings (for both director- and actor stance):
@ -205,16 +179,11 @@ text = "$You() $conj(pick) up the $obj(gun), whistling to $pron(yourself)"
room.msg_contents(text, from_obj=caller, mapping={"gun": gun_object})
```
Depending on your game, Tom may now see himself picking up `A rusty old gun`, whereas an onlooker
with a high gun smith skill may instead see him picking up `A rare-make Smith & Wesson model 686
in poor condition" ...`
Depending on your game, Tom may now see himself picking up `A rusty old gun`, whereas an onlooker with a high gun smith skill may instead see him picking up `A rare-make Smith & Wesson model 686 in poor condition" ...`
# Recog systems and roleplaying
## Recog systems and roleplaying
The `$funcparser` inline functions are very powerful for the game developer, but they may
be a bit too much to write for the regular player.
The [rpsystem contrib](evennia.contrib.rpg.rpsystem) implements a full dynamic emote/pose and recognition
system with short-descriptions and disguises. It uses director stance with a custom markup
language, like `/me` `/gun` and `/tall man` to refer to players and objects in the location. It can be
worth checking out for inspiration.
The [rpsystem contrib](evennia.contrib.rpg.rpsystem) implements a full dynamic emote/pose and recognition system with short-descriptions and disguises. It uses director stance with a custom markup language, like `/me` `/gun` and `/tall man` to refer to players and objects in the location. It can be worth checking out for inspiration.

View file

@ -1,6 +1,6 @@
# Colors
*Note that the Documentation does not display colour the way it would look on the screen.*
> Note that the Documentation does not display colour the way it would look on the screen.
Color can be a very useful tool for your game. It can be used to increase readability and make your
game more appealing visually.
@ -130,8 +130,11 @@ For a detailed explanation of these caveats, see the [Understanding Color Tags](
Tags) tutorial. But most of the time you might be better off to simply avoid `|*` and mark your text
manually instead.
### Xterm256 Colours
## Xterm256 Colours
```{sidebar}
See the [Understanding Color Tags](../Howtos/Tutorial-Understanding-Color-Tags.md) tutorial, for more on the use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.
```
The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background. It can be combined freely with ANSI colors (above), but some ANSI tags don't affect Xterm256 tags.
While this offers many more possibilities than traditional ANSI colours, be wary that too many text
@ -170,7 +173,3 @@ If you have a client that supports Xterm256, you can use
to get a table of all the 256 colours and the codes that produce them. If the table looks broken up
into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement. You can use the `options` command to see if xterm256 is active for you. This depends on if your client told Evennia what it supports - if not, and you know what your client supports, you may have to activate some features manually.
## More reading
There is an [Understanding Color Tags](../Howtos/Tutorial-Understanding-Color-Tags.md) tutorial which expands on the use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.

View file

@ -7,12 +7,19 @@ This documentation cover more over-arching concepts of Evennia, often involving
```{toctree}
:maxdepth: 2
Async-Process.md
Soft-Code.md
Using-MUX-as-a-Standard.md
Messagepath.md
OOB.md
Async-Process.md
```
## Text processing
```{toctree}
:maxdepth: 2
Tags-Parsed-By-Evennia.md
Change-Message-Per-Receiver.md
Internationalization.md
Text-Encodings.md
```
## Access
@ -21,10 +28,8 @@ OOB.md
:maxdepth: 2
Multisession-modes.md
Building-Permissions.md
Guest-Logins.md
Guests.md
Banning.md
```
## Extending the Server
@ -32,31 +37,7 @@ Banning.md
```{toctree}
:maxdepth: 2
Custom-Protocols.md
Bootstrap-&-Evennia.md
New-Models.md
Protocols.md
Models.md
Zones.md
```
## Text processing
```{toctree}
:maxdepth: 2
Internationalization.md
Text-Encodings.md
Inline-Tags-and-Functions.md
Change-Messages-Per-Receiver.md
Clickable-Links.md
Colors.md
```
## Web features
```{toctree}
:maxdepth: 2
Web-Features.md
```
```

View file

@ -0,0 +1,22 @@
# Inline functions
```{sidebar}
For much more information about inline functions, see the [FuncParser](../Components/FuncParser.md) documentation
```
_Inline functions_, also known as _funcparser functions_ are embedded strings on the form
$funcname(args, kwargs)
For example
> say the answer is $eval(24 * 12)!
You say, "the answer is 288!"
General processing of outgoing strings is disabled by default. To activate inline-function parsing of outgoing strings, add this to your settings file:
FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED=True
Inline functions are provided by the [FuncParser](../Components/FuncParser.md). It is enabled in a few other situations:
- Processing of [Prototypes](../Components/Prototypes.md); these 'prototypefuncs' allow for prototypes whose values change dynamically upon spawning. For example, you would set `{key: '$choice(["Bo", "Anne", "Tom"])'` and spawn a random-named character every time.
- Processing of strings to the `msg_contents` method. This allows for [sending different messages depending on who will see them](./Change-Message-Per-Receiver.md).

View file

@ -1,18 +0,0 @@
# In-text tags parsed by Evennia
Evennia understands various extra information embedded in text:
- [Colors](./Colors.md) - Using `|r`, `|n` etc can be used to mark parts of text with a color. The color will
become ANSI/XTerm256 color tags for Telnet connections and CSS information for the webclient.
- [Clickable links](./Clickable-Links.md) - This allows you to provide a text the user can click to execute an
in-game command. This is on the form `|lc command |lt text |le`.
- [FuncParser callables](../Components/FuncParser.md) - These are full-fledged function calls on the form `$funcname(args, kwargs)`
that lead to calls to Python functions. The parser can be run with different available callables in different circumstances. The parser is run on all outgoing messages if `settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED=True` (disabled by default).
```{toctree}
:hidden"
Colors.md
Clickable-Links.md
../Components/FuncParser.md
```

View file

@ -1,211 +1,192 @@
# Messagepath
# The Message path
```shell
> look
The main functionality of Evennia is to communicate with clients connected to it; a player enters
commands or their client queries for a gui update (ingoing data). The server responds or sends data
on its own as the game changes (outgoing data). It's important to understand how this flow of
information works in Evennia.
A Meadow
## The ingoing message path
This is a beautiful meadow. It is full of flowers.
We'll start by tracing data from the client to the server. Here it is in short:
You see: a flower
Exits: north, east
```
Client ->
PortalSession ->
PortalSessionhandler ->
(AMP) ->
ServerSessionHandler ->
ServerSession ->
Inputfunc
When you send a command like `look` into Evennia - what actually happens? How does that `look` string end up being handled by the `CmdLook` class? What happens when we use e.g. `caller.msg()` to send the message back
### Client (ingoing)
Understanding this flow of data - the _message path_ is important in order to understand how Evennia works.
The client sends data to Evennia in two ways.
## Ingoing message path
- When first connecting, the client can send data to the server about its
capabilities. This is things like "I support xterm256 but not unicode" and is
mainly used when a Telnet client connects. This is called a "handshake" and
will generally set some flags on the [Portal Session](../Components/Portal-And-Server.md) that
are later synced to the Server Session. Since this is not something the player
controls, we'll not explore this further here.
- The client can send an *inputcommand* to the server. Traditionally this only
happens when the player enters text on the command line. But with a custom
client GUI, a command could also come from the pressing of a button. Finally
the client may send commands based on a timer or some trigger.
```
Internet│
┌─────┐ │ ┌────────┐
┌──────┐ │Text │ │ ┌────────────┐ ┌─────────┐ │Command │
│Client├────┤JSON ├─┼──►commandtuple├────►Inputfunc├────►DB query│
└──────┘ │etc │ │ └────────────┘ └─────────┘ │etc │
└─────┘ │ └────────┘
│Evennia
```
Exactly how the inputcommand looks when it travels from the client to Evennia
depends on the [Protocol](./Custom-Protocols.md) used:
- Telnet: A string. If GMCP or MSDP OOB protocols are used, this string will
be formatted in a special way, but it's still a raw string. If Telnet SSL is
active, the string will be encrypted.
- SSH: An encrypted string
- Webclient: A JSON-serialized string.
### Incoming command tuples
### Portal Session (ingoing)
Each client is connected to the game via a *Portal Session*, one per connection. This Session is
different depending on the type of connection (telnet, webclient etc) and thus know how to handle
that particular data type. So regardless of how the data arrives, the Session will identify the type
of the instruction and any arguments it should have. For example, the telnet protocol will figure
that anything arriving normally over the wire should be passed on as a "text" type.
### PortalSessionHandler (ingoing)
The *PortalSessionhandler* manages all connected Sessions in the Portal. Its `data_in` method
(called by each Portal Session) will parse the command names and arguments from the protocols and
convert them to a standardized form we call the *inputcommand*:
Ingoing data from the client (coming in as raw strings or serialized JSON) is converted by Evennia to a `commandtuple`. Thesa are the same regardless of what client or connection was used. A `commandtuple` is a simple tuple with three elements:
```python
(commandname, (args), {kwargs})
(commandname, (args), {kwargs})
```
All inputcommands must have a name, but they may or may not have arguments and keyword arguments -
in fact no default inputcommands use kwargs at all. The most common inputcommand is "text", which
has the argument the player input on the command line:
For the `look`-command (and anything else written by the player), the `text` `commandtuple` is generated:
```python
("text", ("look",), {})
("text", ("look",), {})
```
This inputcommand-structure is pickled together with the unique session-id of the Session to which
it belongs. This is then sent over the AMP connection.
### Inputfuncs
### ServerSessionHandler (ingoing)
On the Server side, the AMP unpickles the data and associates the session id with the server-side
[Session](../Components/Sessions.md). Data and Session are passed to the server-side `SessionHandler.data_in`. This
in turn calls `ServerSession.data_in()`
### ServerSession (ingoing)
The method `ServerSession.data_in` is meant to offer a single place to override if they want to
examine *all* data passing into the server from the client. It is meant to call the
`ssessionhandler.call_inputfuncs` with the (potentially processed) data (so this is technically a
sort of detour back to the sessionhandler).
In `call_inputfuncs`, the inputcommand's name is compared against the names of all the *inputfuncs*
registered with the server. The inputfuncs are named the same as the inputcommand they are supposed
to handle, so the (default) inputfunc for handling our "look" command is called "text". These are
just normal functions and one can plugin new ones by simply putting them in a module where Evennia
looks for such functions.
If a matching inputfunc is found, it will be called with the Session and the inputcommand's
arguments:
On the Evennia server side, a list of [inputfucs](Inputuncs) are registered. You can add your own by extending `settings.INPUT_FUNC_MODULES`.
```python
text(session, *("look",), **{})
inputfunc_commandname(session, *args, **kwargs)
```
Here the `session` represents the unique client connection this is coming from (that is, it's identifying just _who_ is sending this input).
If no matching inputfunc is found, an inputfunc named "default" will be tried and if that is also
not found, an error will be raised.
### Inputfunc
The [Inputfunc](../Components/Inputfuncs.md) must be on the form `func(session, *args, **kwargs)`. An exception is
the `default` inputfunc which has form `default(session, cmdname, *args, **kwargs)`, where `cmdname`
is the un-matched inputcommand string.
This is where the message's path diverges, since just what happens next depends on the type of
inputfunc was triggered. In the example of sending "look", the inputfunc is named "text". It will
pass the argument to the `cmdhandler` which will eventually lead to the `look` command being
executed.
## The outgoing message path
Next let's trace the passage from server to client.
msg ->
ServerSession ->
ServerSessionHandler ->
(AMP) ->
PortalSessionHandler ->
PortalSession ->
Client
### msg
All outgoing messages start in the `msg` method. This is accessible from three places:
- `Object.msg`
- `Account.msg`
- `Session.msg`
The call sign of the `msg` method looks like this:
One such inputfunc is named `text`. For sending a `look`, it will be called as
```{sidebar}
If you know how `*args` and `**kwargs` work in Python, you'll see that this is the same as a call `text(session, "look")`
```
```python
msg(text=None, from_obj=None, session=None, options=None, **kwargs)
text(session, *("look",), **{})
```
For our purposes, what is important to know is that with the exception of `from_obj`, `session` and
`options`, all keywords given to the `msg` method is the name of an *outputcommand* and its
arguments. So `text` is actually such a command, taking a string as its argument. The reason `text`
sits as the first keyword argument is that it's so commonly used (`caller.msg("Text")` for example).
Here are some examples
What an `inputfunc` does with this depends. For an [Out-of-band](./OOB.md) instruction, it could fetch the health of a player or tick down some counter.
```{sidebar} No text parsing happens before this
If you send `look here`, the call would be `text(session, *("look here", **{})`. All parsing of the text input happens in the command-parser, after this step.
```
For the `text` `inputfunc` the Evennia [CommandHandler](../Components/Commands.md) is invoked and the argument is parsed further in order to figure which command was intended.
In the example of `look`, the `CmdLook` command-class will be invoked. This will retrieve the description of the current location.
## Outgoing message path
```
Internet│
┌─────┐ │
┌──────┐ │Text │ │ ┌──────────┐ ┌────────────┐ ┌─────┐
│Client◄────┤JSON ├─┼──┤outputfunc◄────┤commandtuple◄───┤msg()│
└──────┘ │etc │ │ └──────────┘ └────────────┘ └─────┘
└─────┘ │
│Evennia
```
### `msg` to outgoing commandtuple
When the `inputfunc` has finished whatever it is supposed to, the server may or may not decide to return a result (Some types of `inputcommands` may not expect or require a response at all). The server also often sends outgoing messages without any prior matching ingoing data.
Whenever data needs to be sent "out" of Evennia, we must generalize it into a (now outgoing) `commandtuple` `(commandname, (args), {kwargs})`. This we do with the `msg()` method. For convenience, this methods is available on every major entity, such as `Object.msg()` and `Account.msg()`. They all link back to `Session.msg()`.
```python
msg("Hello!") # using the 'text' outputfunc
msg(prompt=f"HP: {HP}, SP: {SP}, MP: {MP}")
msg(mycommand=((1,2,3,4), {"foo": "bar"})
```
Note the form of the `mycommand` outputfunction. This explicitly defines the arguments and keyword
arguments for the function. In the case of the `text` and `prompt` calls we just specify a string -
this works too: The system will convert this into a single argument for us later in the message
path.
> Note: The `msg` method sits on your Object- and Account typeclasses. It means you can easily
override `msg` and make custom- or per-object modifications to the flow of data as it passes
through.
### ServerSession (outgoing)
Nothing is processed on the Session, it just serves as a gathering points for all different `msg`.
It immediately passes the data on to ...
### ServerSessionHandler (outgoing)
In the *ServerSessionhandler*, the keywords from the `msg` method are collated into one or more
*outputcommands* on a standardized form (identical to inputcommands):
```
(commandname, (args), {kwargs})
msg(text=None, from_obj=None, session=None, options=None, **kwargs)
```
This will intelligently convert different input to the same form. So `msg("Hello")` will end up as
an outputcommand `("text", ("Hello",), {})`.
`text` is so common that it is given as the default:
This is also the point where the [FuncParser](../Components/FuncParser.md)) is applied, depending on the
session to receive the data. Said data is pickled together with the Session id then sent over the
AMP bridge.
```python
msg("A meadow\n\nThis is a beautiful meadow...")
```
### PortalSessionHandler (outgoing)
This is converted to a `commandtuple` looking like this:
```python
("text", ("A meadow\n\nThis is a beutiful meadow...",) {})
```
After the AMP connection has unpickled the data and paired the session id to the matching
PortalSession, the handler next determines if this Session has a suitable method for handling the
outputcommand.
The `msg()` method allows you to define the `commandtuple` directly, for whatever outgoing instruction you want to find:
The situation is analogous to how inputfuncs work, except that protocols are fixed things that don't
need a plugin infrastructure like the inputfuncs are handled. So instead of an "outputfunc", the
handler looks for methods on the PortalSession with names of the form `send_<commandname>`.
```python
msg(current_status=(("healthy", "charged"), {"hp": 12, "mp": 20}))
```
For example, the common sending of text expects a PortalSession method `send_text`. This will be
called as `send_text(*("Hello",), **{})`. If the "prompt" outputfunction was used, send_prompt is
called. In all other cases the `send_default(cmdname, *args, **kwargs)` will be called - this is the
case for all client-custom outputcommands, like when wanting to tell the client to update a graphic
or play a sound.
This will be converted to a `commandtuple` looking like this:
### PortalSession (outgoing)
```python
("current_status", ("healthy", "charged"), {"hp": 12, "mp": 20})
```
At this point it is up to the session to convert the command into a form understood by this
particular protocol. For telnet, `send_text` will just send the argument as a string (since that is
what telnet clients expect when "text" is coming). If `send_default` was called (basically
everything that is not traditional text or a prompt), it will pack the data as an GMCP or MSDP
command packet if the telnet client supports either (otherwise it won't send at all). If sending to
the webclient, the data will get packed into a JSON structure at all times.
### outputfuncs
### Client (outgoing)
```{sidebar}
`outputfuncs` are tightly coupled to the protocol and you usually don't need to touch them, unless you are adding a new protocol entirely.
```
Since `msg()` is aware of which [Session](../Components/Sessions.md) to send to, the outgoing `commandtuple` is always end up pointed at the right client.
Once arrived at the client, the outputcommand is handled in the way supported by the client (or it
may be quietly ignored if not). "text" commands will be displayed in the main window while others
may trigger changes in the GUI or play a sound etc.
Each supported Evennia Protocol (Telnet, SSH, Webclient etc) has their own `outputfunc`, which converts the generic `commandtuple` into a form that particular protocol understands, such as telnet instructions or JSON.
For telnet (no SSL), the `look` will return over the wire as plain text:
A meadow\n\nThis is a beautiful meadow...
When sending to the webclient, the `commandtuple` is converted as serialized JSON, like this:
'["look", ["A meadow\\n\\nThis is a beautiful meadow..."], {}]'
This is then sent to the client over the wire. It's then up to the client to interpret and handle the data properly.
## Components along the path
### Ingoing
```
┌──────┐ ┌─────────────────────────┐
│Client│ │ │
└──┬───┘ │ ┌────────────────────┐ │
│ ┌──────┼─►│ServerSessionHandler│ │
┌──────────────────┼──────┐ │ │ └───┬────────────────┘ │
│ Portal │ │ │ │ │ │
│ ┌─────────▼───┐ │ ┌─┴─┐ │ ┌───▼─────────┐ │
│ │PortalSession│ │ │AMP│ │ │ServerSession│ │
│ └─────────┬───┘ │ └─┬─┘ │ └───┬─────────┘ │
│ │ │ │ │ │ │
│ ┌────────────────▼───┐ │ │ │ ┌───▼─────┐ │
│ │PortalSessionHandler├──┼──────┘ │ │Inputfunc│ │
│ └────────────────────┘ │ │ └─────────┘ │
│ │ │ Server │
└─────────────────────────┘ └─────────────────────────┘
```
1. Client - sends handshake or commands over the wire. This is received by the Evennia [Portal](../Components/Portal-And-Server.md).
2. `PortalSession` represents one client connection. It understands the communiation protocol used. It converts the protocol-specific input to a generic `commandtuple` structure `(cmdname, (args), {kwargs})`.
3. `PortalSessionHandler` handles all connections. It pickles the `commandtuple` together with the session-id.
4. Pickled data is sent across the `AMP` (Asynchronous Message Protocol) connection to the [Server](Server-And-Portal) part of Evennia.
5. `ServerSessionHandler` unpickles the `commandtuple` and matches the session-id to a matching `SessionSession`.
6. `ServerSession` represents the session-connection on the Server side. It looks through its registry of [Inputfuncs](../Components/Inputfuncs.md) to find a match.
7. The appropriate `Inputfunc` is called with the args/kwargs included in the `commandtuple`. Depending on `Inputfunc`, this could have different effects. For the `text` inputfunc, it fires the [CommandHandler](../Components/Commands.md).
### Outgoing
```
┌──────┐ ┌─────────────────────────┐
│Client│ │ │
└──▲───┘ │ ┌────────────────────┐ │
│ ┌──────┼──┤ServerSessionHandler│ │
┌──────────────────┼──────┐ │ │ └───▲────────────────┘ │
│ Portal │ │ │ │ │ │
│ ┌─────────┴───┐ │ ┌─┴─┐ │ ┌───┴─────────┐ │
│ │PortalSession│ │ │AMP│ │ │ServerSession│ │
│ └─────────▲───┘ │ └─┬─┘ │ └───▲─────────┘ │
│ │ │ │ │ │ │
│ ┌────────────────┴───┐ │ │ │ ┌───┴──────┐ │
│ │PortalSessionHandler◄──┼──────┘ │ │msg() call│ │
│ └────────────────────┘ │ │ └──────────┘ │
│ │ │ Server │
└─────────────────────────┘ └─────────────────────────┘
```
1. The `msg()` method is called
2. `ServerSession` and in particular `ServerSession.msg()` is the central point through which all `msg()` calls are routed in order to send data to that [Session](../Components/Sessions.md).
3. `ServerSessionHandler` converts the `msg` input to a proper `commandtuple` structure `(cmdname, (args), {kwargs})`. It pickles the `commandtuple` together with the session-id.
4. Pickled data is sent across across the `AMP` (Asynchronous Message Protocol) connection to the [Portal](Server-And-Portal) part of Evennia.
5. `PortalSessionHandler` unpickles the `commandtuple` and matches its session id to a matching `PortalSession`.
6. The `PortalSession` is now responsible for converting the generic `commandtuple` to the communication protocol used by that particular connection.
7. The Client receives the data and can act on it.

View file

@ -1,155 +1,118 @@
# OOB
# Out-of-Band messaging
OOB, or Out-Of-Band, means sending data between Evennia and the user's client without the user
prompting it or necessarily being aware that it's being passed. Common uses would be to update
client health-bars, handle client button-presses or to display certain tagged text in a different
window pane.
## Briefly on input/outputcommands
If you haven't, you should be familiar with the [Messagepath](./Messagepath.md), which describes how a message enters and leaves Evennia and how along the way, all messages are converted to a generic format called a `commandtuple`:
Inside Evennia, all server-client communication happens in the same way (so plain text is also an
'OOB message' as far as Evennia is concerned). The message follows the [Message Path](./Messagepath.md).
You should read up on that if you are unfamiliar with it. As the message travels along the path it
has a standardized internal form: a tuple with a string, a tuple and a dict:
("cmdname", (args), {kwargs})
This is often referred to as an *inputcommand* or *outputcommand*, depending on the direction it's
traveling. The end point for an inputcommand, (the 'Evennia-end' of the message path) is a matching
[Inputfunc](../Components/Inputfuncs.md). This function is called as `cmdname(session, *args, **kwargs)` where
`session` is the Session-source of the command. Inputfuncs can easily be added by the developer to
support/map client commands to actions inside Evennia (see the [inputfunc](../Components/Inputfuncs.md) page for more
details).
When a message is outgoing (at the 'Client-end' of the message path) the outputcommand is handled by
a matching *Outputfunc*. This is responsible for converting the internal Evennia representation to a
form suitable to send over the wire to the Client. Outputfuncs are hard-coded. Which is chosen and
how it processes the outgoing data depends on the nature of the client it's connected to. The only
time one would want to add new outputfuncs is as part of developing support for a new Evennia
[Protocol](./Custom-Protocols.md).
(commandname, (args), {kwargs})
## Sending and receiving an OOB message
Sending is simple. You just use the normal `msg` method of the object whose session you want to send
to. For example in a Command:
Sending is simple. You just use the normal `msg` method of the object whose session you want to send to.
```python
caller.msg(cmdname=((args, ...), {key:value, ...}))
caller.msg(commandname=((args, ...), {key:value, ...}))
```
A special case is the `text` input/outputfunc. It's so common that it's the default of the `msg`
method. So these are equivalent:
The keyword becomes the command-name part of the `commandtuple` and the value its `args` and `kwargs` parts. You can also send multiple messages of different `commandname`s at the same time.
A special case is the `text` call. It's so common that it's the default of the `msg` method. So these are equivalent:
```python
caller.msg("Hello")
caller.msg(text="Hello")
```
You don't have to specify the full output/input definition. So for example, if your particular
command only needs kwargs, you can skip the `(args)` part. Like in the `text` case you can skip
You don't have to specify the full `commandtuple` definition. So for example, if your particular command only needs kwargs, you can skip the `(args)` part. Like in the `text` case you can skip
writing the tuple if there is only one arg ... and so on - the input is pretty flexible. If there
are no args at all you need to give the empty tuple `msg(cmdname=(,)` (giving `None` would mean a
single argument `None`).
Which commands you can send depends on the client. If the client does not support an explicit OOB
protocol (like many old/legacy MUD clients) Evennia can only send `text` to them and will quietly
drop any other types of outputfuncs.
### Which command-names can I send?
> Remember that a given message may go to multiple clients with different capabilities. So unless
you turn off telnet completely and only rely on the webclient, you should never rely on non-`text`
OOB messages always reaching all targets.
This depends on the client and protocol. If you use the Evennia [webclient](../Components/Webclient.md), you can modify it to have it support whatever command-names you like.
[Inputfuncs](../Components/Inputfuncs.md) lists the default inputfuncs available to handle incoming OOB messages. To
accept more you need to add more inputfuncs (see that page for more info).
Many third-party MUD clients support a range of OOB protocols listed below. If a client does not support a particular OOB instruction/command, Evennia will just send the `text` command to them and quietly drop all other OOB instructions.
> Note that a given message may go to multiple clients with different capabilities. So unless you turn off telnet completely and only rely on the webclient, you should never rely on non-`text` OOB messages always reaching all targets.
### Which command-names can I receive
This is decided by which [Inputfuncs](../Components/Inputfuncs.md) you define. You can extend Evennia's default as you like, but adding your own functions in a module pointed to by `settings.INPUT_FUNC_MODULES`.
## Supported OOB protocols
Evennia supports clients using one of the following protocols:
### Telnet
By default telnet (and telnet+SSL) supports only the plain `text` outputcommand. Evennia however
detects if the Client supports one of two MUD-specific OOB *extensions* to the standard telnet
protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the
client uses. If the client supports both, GMCP will be used.
By default telnet (and telnet+SSL) supports only the plain `text` outputcommand. Evennia detects if the Client supports one of two MUD-specific OOB *extensions* to the standard telnet protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the client uses. If the client supports both, GMCP will be used.
> Note that for Telnet, `text` has a special status as the "in-band" operation. So the `text`
outputcommand sends the `text` argument directly over the wire, without going through the OOB
translations described below.
> Note that for Telnet, `text` has a special status as the "in-band" operation. So the `text` outputcommand sends the `text` argument directly over the wire, without going through the OOB translations described below.
#### Telnet + GMCP
[GMCP](https://www.gammon.com.au/gmcp), the *Generic Mud Communication Protocol* sends data on the
form `cmdname + JSONdata`. Here the cmdname is expected to be on the form "Package.Subpackage".
There could also be additional Sub-sub packages etc. The names of these 'packages' and 'subpackages'
are not that well standardized beyond what individual MUDs or companies have chosen to go with over
the years. You can decide on your own package names, but here are what others are using:
[GMCP](https://www.gammon.com.au/gmcp), the *Generic Mud Communication Protocol* sends data on the form `cmdname + JSONdata`. Here the cmdname is expected to be on the form "Package.Subpackage". There could also be additional Sub-sub packages etc. The names of these 'packages' and 'subpackages' are not that well standardized beyond what individual MUDs or companies have chosen to go with over the years. You can decide on your own package names, but here are what others are using:
- [Aardwolf GMCP](https://www.aardwolf.com/wiki/index.php/Clients/GMCP)
- [Discworld GMCP](https://discworld.starturtle.net/lpc/playing/documentation.c?path=/concepts/gmcp)
- [Avatar GMCP](https://www.outland.org/infusions/wiclear/index.php?title=MUD%20Protocols&lang=en)
- [IRE games GMCP](https://nexus.ironrealms.com/GMCP)
Evennia will translate underscores to `.` and capitalize to fit the specification. So the
outputcommand `foo_bar` will become a GMCP command-name `Foo.Bar`. A GMCP command "Foo.Bar" will be
come `foo_bar`. To send a GMCP command that turns into an Evennia inputcommand without an
underscore, use the `Core` package. So `Core.Cmdname` becomes just `cmdname` in Evennia and vice
versa.
Evennia will translate underscores to `.` and capitalize to fit the specification. So the outputcommand `foo_bar` will become a GMCP command-name `Foo.Bar`. A GMCP command "Foo.Bar" will be come `foo_bar`. To send a GMCP command that turns into an Evennia inputcommand without an underscore, use the `Core` package. So `Core.Cmdname` becomes just `cmdname` in Evennia and vice versa.
On the wire, a GMCP instruction for `("cmdname", ("arg",), {})` will look like this:
On the wire, the `commandtuple`
("cmdname", ("arg",), {})
will be sent over the wire as this GMCP telnet instruction
IAC SB GMCP "cmdname" "arg" IAC SE
where all the capitalized words are telnet character constants specified in
`evennia/server/portal/telnet_oob.py`. These are parsed/added by the protocol and we don't include
these in the listings below.
where all the capitalized words are telnet character constants specified in ][evennia/server/portal/telnet_oob](evennia.server.portal/telnet_oob.py). These are parsed/added by the protocol and we don't include these in the listings below.
Input/Outputfunc | GMCP-Command
`[cmd_name, [], {}]` | Cmd.Name
`[cmd_name, [arg], {}]` | Cmd.Name arg
`[cmd_na_me, [args],{}]` | Cmd.Na.Me [args]
`[cmd_name, [], {kwargs}]` | Cmd.Name {kwargs}
`[cmdname, [args, {kwargs}]` | Core.Cmdname [[args],{kwargs}]
| `commandtuple` | GMCP-Command |
| --- | ---|
| `(cmd_name, (), {})` | `Cmd.Name` |
| `(cmd_name, (arg,), {})` | `Cmd.Name arg` |
| `(cmd_na_me, (args,...),{})` | `Cmd.Na.Me [arg, arg...]` |
| `(cmd_name, (), {kwargs})` | `Cmd.Name {kwargs}` |
| `(cmdname, (arg,), {kwargs})` | `Core.Cmdname [[args],{kwargs}]` |
Since Evennia already supplies default inputfuncs that don't match the names expected by the most
common GMCP implementations we have a few hard-coded mappings for those:
Since Evennia already supplies default Inputfuncs that don't match the names expected by the most common GMCP implementations we have a few hard-coded mappings for those:
GMCP command name | Input/Outputfunc name
"Core.Hello" | "client_options"
"Core.Supports.Get" | "client_options"
"Core.Commands.Get" | "get_inputfuncs"
"Char.Value.Get" | "get_value"
"Char.Repeat.Update" | "repeat"
"Char.Monitor.Update" | "monitor"
| GMCP command name | `commandtuple` command name |
| --- | --- |
| `"Core.Hello"` | `"client_options"` |
| `"Core.Supports.Get"` | `"client_options"` |
| `"Core.Commands.Get"` | `"get_inputfuncs"` |
| `"Char.Value.Get"` | `"get_value"` |
| `"Char.Repeat.Update"` | `"repeat"` |
| `"Char.Monitor.Update"`| `"monitor"` |
#### Telnet + MSDP
[MSDP](http://tintin.sourceforge.net/msdp/), the *Mud Server Data Protocol*, is a competing standard
to GMCP. The MSDP protocol page specifies a range of "recommended" available MSDP command names.
Evennia does *not* support those - since MSDP doesn't specify a special format for its command names
(like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual
name.
[MSDP](http://tintin.sourceforge.net/msdp/), the *Mud Server Data Protocol*, is a competing standard to GMCP. The MSDP protocol page specifies a range of "recommended" available MSDP command names. Evennia does *not* support those - since MSDP doesn't specify a special format for its command names (like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual name.
MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports
strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs
needed. When sending MSDP for `("cmdname", ("arg",), {})` the resulting MSDP instruction will look
like this:
MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs needed. When sending MSDP for `("cmdname", ("arg",), {})` the resulting MSDP instruction will look like this:
IAC SB MSDP VAR cmdname VAL arg IAC SE
The various available MSDP constants like `VAR` (variable), `VAL` (value), `ARRAYOPEN`/`ARRAYCLOSE`
and `TABLEOPEN`/`TABLECLOSE` are specified in `evennia/server/portal/telnet_oob`.
Outputfunc/Inputfunc | MSDP instruction
`[cmdname, [], {}]` | VAR cmdname VAL
`[cmdname, [arg], {}]` | VAR cmdname VAL arg
`[cmdname, [args],{}]` | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE
`[cmdname, [], {kwargs}]` | VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE
`[cmdname, [args], {kwargs}]` | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE VAR cmdname
VAL TABLEOPEN VAR key VAL val ... TABLECLOSE
| `commandtuple` | MSDP instruction |
| --- | --- |
| `(cmdname, (), {})` | `VAR cmdname VAL` |
| `(cmdname, (arg,), {})` | `VAR cmdname VAL arg` |
| `(cmdname, (arg,...),{})` | `VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE` |
| `(cmdname, (), {kwargs})` | `VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE` |
| `(cmdname, (args,...), {kwargs})` | `VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE` |
Observe that `VAR ... VAL` always identifies cmdnames, so if there are multiple arrays/dicts tagged
with the same cmdname they will be appended to the args, kwargs of that inputfunc. Vice-versa, a
Observe that `VAR ... VAL` always identifies `cmdnames`, so if there are multiple arrays/dicts tagged with the same cmdname they will be appended to the args, kwargs of that inputfunc. Vice-versa, a
different `VAR ... VAL` (outside a table) will come out as a second, different command input.
### SSH
@ -158,10 +121,14 @@ SSH only supports the `text` input/outputcommand.
### Web client
Our web client uses pure JSON structures for all its communication, including `text`. This maps
directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs. So the
same example `("cmdname", ("arg",), {})` will be sent/received as a valid JSON structure
Our web client uses pure [JSON](https://en.wikipedia.org/wiki/JSON) structures for all its communication, including `text`. This maps directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs.
["cmdname, ["arg"], {}]
| `commandtuple` | Evennia Webclient JSON |
| --- | --- |
| `(cmdname, (), {})` | `["cmdname", [], {}]` |
| `(cmdname, (arg,), {})` | `["cmdname", [arg], {}]` |
| `(cmdname, (arg,...),{})` | `["cmdname", [arg, ...], {})` |
| `(cmdname, (), {kwargs})` | `["cmdname", [], {kwargs})` |
| `(cmdname, (arg,...), {kwargs})` | `["cmdname", [arg, ...], {kwargs})` |
Since JSON is native to Javascript, this becomes very easy for the webclient to handle.
Since JSON is native to Javascript, this becomes very easy for the webclient to handle.

View file

@ -1,67 +0,0 @@
# Soft Code
Softcode is a very simple programming language that was created for in-game development on TinyMUD derivatives such as MUX, PennMUSH, TinyMUSH, and RhostMUSH. The idea is that by providing a stripped down, minimalistic language for in-game use, you can allow quick and easy building and game development to happen without having to learn C/C++. There is an added benefit of not having to have to hand out shell access to all developers, and permissions 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 MUX/MUSH and typing 'hello' will theoretically yield 'Hello World!', assuming
certain flags are not set on your account object.
Setting attributes 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!
```
Perhaps I want to break the Hello World into 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 still curious about how 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 not designed properly.
## 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, some of 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. As mentioned before, shell access is not necessary to develop a MUX or a MUSH. However, now that we are seeing a lot more small, one or two-man shops, the issue of shell access and stepping on each other's toes is a lot less.
## Our Solution
Evennia shuns in-game softcode for on-disk Python modules. Python is a popular, mature and
professional programming language. You code it using the conveniences of modern text editors.
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 but 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. Advanced coding in Evennia is primarily intended to be done outside the game, in full-fledged Python modules. Advanced building is best handled by extending Evennia's command system with your own sophisticated building commands. We feel that with a small development team you are better off using a professional source-control system (svn, git, bazaar, mercurial etc) anyway.
## 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. The [in-game-python](../Contribs/Contrib-Ingame-Python.md) is an optional pseudo-softcode plugin aimed at developers wanting to script their game from inside it.

View file

@ -0,0 +1,27 @@
# In-text tags parsed by Evennia
```{toctree}
:maxdepth: 2
Colors.md
Clickable-Links.md
Inline-Functions.md
```
Evennia will parse various special tags and markers embedded in text and convert it dynamically depending on if the data is going in or out of the server.
- _Colors_ - Using `|r`, `|n` etc can be used to mark parts of text with a color. The color will
become ANSI/XTerm256 color tags for Telnet connections and CSS information for the webclient.
```
> say Hello, I'm wearing my |rred hat|n today.
```
- _Clickable links_ - This allows you to provide a text the user can click to execute an
in-game command. This is on the form `|lc command |lt text |le`. Clickable links are generally only parsed in the _outgoing_ direction, since if users could provde them, they could be a potential security problem. To activate, `MXP_ENABLED=True` must be added to settings (disabled by default).
```
py self.msg("This is a |c look |ltclickable 'look' link|le")
```
- _FuncParser callables_ - These are full-fledged function calls on the form `$funcname(args, kwargs)` that lead to calls to Python functions. The parser can be run with different available callables in different circumstances. The parser is run on all outgoing messages if `settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED=True` (disabled by default).
```
> say The answer is $eval(40 + 2)!
```

View file

@ -1,12 +0,0 @@
# Using MUX as a Standard
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.
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/). 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".

View file

@ -1,133 +0,0 @@
# Web Features
Evennia is its own webserver and hosts a default website and browser webclient.
## Web site
The Evennia website is a Django application that ties in with the MUD database. Since the website
shares this database you could, for example, tell website visitors how many accounts are logged into
the game at the moment, how long the server has been up and any other database information you may
want. During development you can access the website by pointing your browser to
`http://localhost:4001`.
> You may also want to set `DEBUG = True` in your settings file for debugging the website. You will
then see proper tracebacks in the browser rather than just error codes. Note however that this will
*leak memory a lot* (it stores everything all the time) and is *not to be used in production*. It's
recommended to only use `DEBUG` for active web development and to turn it off otherwise.
A Django (and thus Evennia) website basically consists of three parts, a
[view](https://docs.djangoproject.com/en/1.9/topics/http/views/) an associated
[template](https://docs.djangoproject.com/en/1.9/topics/templates/) and an `urls.py` file. Think of
the view as the Python back-end and the template as the HTML files you are served, optionally filled
with data from the back-end. The urls file is a sort of mapping that tells Django that if a specific
URL is given in the browser, a particular view should be triggered. You are wise to review the
Django documentation for details on how to use these components.
Evennia's default website is located in
[evennia/web/website](https://github.com/evennia/evennia/tree/master/evennia/web/website). In this
folder you'll find the simple default view as well as subfolders `templates` and `static`. Static
files are things like images, CSS files and Javascript.
### Customizing the Website
You customize your website from your game directory. In the folder `web` you'll find folders
`static`, `templates`, `static_overrides` and `templates_overrides`. The first two of those are
populated automatically by Django and used to serve the website. You should not edit anything in
them - the change will be lost. To customize the website you'll need to copy the file you want to
change from the `web/website/template/` or `web/website/static/ path to the corresponding place
under one of `_overrides` directories.
Example: To override or modify `evennia/web/website/template/website/index.html` you need to
add/modify `mygame/web/template_overrides/website/index.html`.
The detailed description on how to customize the website is best described in tutorial form. See the
[Web Tutorial](../Howtos/Web-Changing-Webpage.md) for more information.
### Overloading Django views
The Python backend for every HTML page is called a [Django
view](https://docs.djangoproject.com/en/1.9/topics/http/views/). A view can do all sorts of
functions, but the main one is to update variables data that the page can display, like how your
out-of-the-box website will display statistics about number of users and database objects.
To re-point a given page to a `view.py` of your own, you need to modify `mygame/web/urls.py`. An
[URL pattern](https://docs.djangoproject.com/en/1.9/topics/http/urls/) is a [regular
expression](https://en.wikipedia.org/wiki/Regular_expression) that you need to enter in the address
field of your web browser to get to the page in question. If you put your own URL pattern *before*
the default ones, your own view will be used instead. The file `urls.py` even marks where you should
put your change.
Here's an example:
```python
# mygame/web/urls.py
from django.conf.urls import url, include
# default patterns
from evennia.web.urls import urlpatterns
# our own view to use as a replacement
from web.myviews import myview
# custom patterns to add
patterns = [
# overload the main page view
url(r'^', myview, name='mycustomview'),
]
urlpatterns = patterns + urlpatterns
```
Django will always look for a list named `urlpatterns` which consists of the results of `url()`
calls. It will use the *first* match it finds in this list. Above, we add a new URL redirect from
the root of the website. It will now our own function `myview` from a new module
`mygame/web/myviews.py`.
> If our game is found on `http://mygame.com`, the regular expression `"^"` means we just entered
`mygame.com` in the address bar. If we had wanted to add a view for `http://mygame.com/awesome`, the
regular expression would have been `^/awesome`.
Look at
[evennia/web/website/views.py](https://github.com/evennia/evennia/blob/master/evennia/web/website/views.py#L82)
to see the inputs and outputs you must have to define a view. Easiest may be to copy the default
file to `mygame/web` to have something to modify and expand on.
Restart the server and reload the page in the browser - the website will now use your custom view.
If there are errors, consider turning on `settings.DEBUG` to see the full tracebacks - in debug mode
you will also log all requests in `mygame/server/logs/http_requests.log`.
## Web client
Evennia comes with a MUD client accessible from a normal web browser. During
development you can try it at `http://localhost:4001/webclient`.
[See the Webclient page](../Components/Webclient.md) for more details.
## The Django 'Admin' Page
Django comes with a built-in [admin
website](https://docs.djangoproject.com/en/1.10/ref/contrib/admin/). This is accessible by clicking
the 'admin' button from your game website. The admin site allows you to see, edit and create objects
in your database from a graphical interface.
The behavior of default Evennia models are controlled by files `admin.py` in the Evennia package.
New database models you choose to add yourself (such as in the Web Character View Tutorial) can/will
also have `admin.py` files. New models are registered to the admin website by a call of
`admin.site.register(model class, admin class)` inside an admin.py file. It is an error to attempt
to register a model that has already been registered.
To overload Evennia's admin files you don't need to modify Evennia itself. To customize you can call
`admin.site.unregister(model class)`, then follow that with `admin.site.register` in one of your own
admin.py files in a new app that you add.
## More reading
Evennia relies on Django for its web features. For details on expanding your web experience, the
[Django documentation](https://docs.djangoproject.com/en) or the [Django
Book](http://www.djangobook.com/en/2.0/index.html) are the main resources to look into. In Django
lingo, the Evennia is a django "project" that consists of Django "applications". For the sake of web
implementation, the relevant django "applications" in default Evennia are `web/website` or
`web/webclient`.