Updated HTML docs.

This commit is contained in:
Griatch 2022-11-15 19:43:25 +00:00
parent 59e50f3fa5
commit 06bc3c8bcd
663 changed files with 2 additions and 61705 deletions

View file

@ -1,234 +0,0 @@
# Async Process
*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.
Consider this piece of code in a traditional Python program:
```python
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
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.
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`:
```python
run_async(function, *args, **kwargs)
```
Where `function` will be called asynchronously with `*args` and `**kwargs`. Example:
```python
from evennia import utils
print("before call ...")
utils.run_async(long_running_function)
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
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
code after the call to try to deal with the result from `long_running_function` above - as we saw
the `"after call ..."` got printed long before `long_running_function` was finished, making that
line quite pointless for processing any data from the function. Instead one has to use *callbacks*.
`utils.run_async` takes reserved kwargs that won't be passed into the long-running function:
- `at_return(r)` (the *callback*) is called when the asynchronous function (`long_running_function`
above) finishes successfully. The argument `r` will then be the return value of that function (or
`None`).
```python
def at_return(r):
print(r)
```
- `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
log. An example of an errback is found below:
```python
def at_err(e):
print("There was an error:", str(e))
```
- `at_err_kwargs` - an optional dictionary that will be fed as keyword arguments to the `at_err`
errback.
An example of making an asynchronous call from inside a [Command](../Components/Commands.md) definition:
```python
from evennia import utils, Command
class CmdAsync(Command):
key = "asynccommand"
def func(self):
def long_running_function():
#[... lots of time-consuming code ...]
return final_value
def at_return_function(r):
self.caller.msg(f"The final value is {r}")
def at_err_function(e):
self.caller.msg(f"There was an error: {e}")
# do the async call, setting all callbacks
utils.run_async(long_running_function, at_return=at_return_function,
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.
## 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/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.

View file

@ -1,144 +0,0 @@
# Banning
Whether due to abuse, blatant breaking of your rules, or some other reason, you will eventually find
no other recourse but to kick out a particularly troublesome player. The default command set has
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.
### Name ban
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!
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:
accounts/delete YouSuck
Generally, banning the name is the easier and safer way to stop the use of an account -- if you
change your mind you can always remove the block later whereas a deletion is permanent.
### 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.
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:
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:
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:
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.
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.
boot YouSuck
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:
Active bans
id name/ip date reason
1 yousuck Fri Jan 3 23:00:22 2020 This is just a Troll.
2 237.333.0.* Fri Jan 3 23:01:03 2020 YouSuck's IP.
Use the `id` from this list to find out which ban to lift.
unban 2
Cleared ban 2: 237.333.0.*
## Summary of abuse-handling tools
Below are other useful commands for dealing with annoying players.
- **who** -- (as admin) Find the IP of a account. Note that one account can be connected to from
multiple IPs depending on what you allow in your settings.
- **examine/account thomas** -- Get all details about an account. You can also use `*thomas` to get
the account. If not given, you will get the *Object* thomas if it exists in the same location, which
is not what you want in this case.
- **boot thomas** -- Boot all sessions of the given account name.
- **boot 23** -- Boot one specific client session/IP by its unique id.
- **ban** -- List all bans (listed with ids)
- **ban thomas** -- Ban the user with the given account name
- **ban/ip `134.233.2.111`** -- Ban by IP
- **ban/ip `134.233.2.*`** -- Widen IP ban
- **ban/ip `134.233.*.*`** -- Even wider IP ban
- **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).
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
- **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)
- **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
- **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.

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,220 +0,0 @@
# Sending different messages depending on viewpoint and receiver
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
room.msg_contents("Anna walks into the room.")
```
You can also embed references in the string:
```python
room.msg_contents("{anna} walks into the room.",
from_obj=caller,
mapping={'anna': anna_object})
```
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 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_.
| 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.
## Director Stance
While not so 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.
Here's an example of a flavorful text to show the room:
Tom picks up the gun, whistling to himself.
Everyone will see this string, both Tom and others. Here's how to send it to everyone in
the room.
```python
text = "Tom picks up the gun, whistling to himself."
room.msg_contents(text)
```
One may want to expand on it by making the name `Tom` be seen differently by different people,
but the English grammar of the sentence does not change. Not only is this pretty easy to do
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.
Tom picks up the gun, whistling to himself.
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.
```python
text = "$You() $conj(pick) up the gun, whistling to $pron(yourself)."
room.msg_contents(text, from_obj=caller, mapping={"gun": gun_object})
```
These are the inline-functions available:
- `$You()/$you()` - this is a reference to 'you' in the text. It will be replaced with "You/you" for
the one sending the text and with the return from `caller.get_display_name(looker)` for everyone else.
- `$conj(verb)` - this will conjugate the given verb depending on who sees the string (like `pick`
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.
### More on $pron()
The `$pron()` inline func maps between 1st/2nd person (I/you) to 3rd person (he/she etc). In short,
it translates between this table ...
| | Subject Pronoun | Object Pronoun | Possessive Adjective | Possessive Pronoun | Reflexive Pronoun |
| --- | --- | --- | --- | --- | --- |
| **1st person** | I | me | my | mine | myself |
| **1st person plural** | we | us | our | ours | ourselves |
| **2nd person** | you | you | your | yours | yourself |
| **2nd person plural** | you | you | your | yours | yourselves |
... to this table (in both directions):
| | Subject Pronoun | Object Pronoun | Possessive Adjective | Possessive Pronoun | Reflexive Pronoun |
| --- | --- | --- | --- | --- | --- |
| **3rd person male** | he | him | his | his | himself |
| **3rd person female** | she | her | her | hers | herself |
| **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.
- `pronoun_type` - this is one of the columns in the table and can be set as a `$pron` option.
- `subject pronoun` (aliases `subject` or `sp`)
- `object pronoun` (aliases `object` or `op`)
- `possessive adjective` (aliases `adjective` or `pa`)
- `possessive pronoun` (aliases `pronoun` or `pp`).
(There is no need to specify reflexive pronouns since they
are all uniquely mapped 1:1). Speciying the pronoun-type is mainly needed when using `you`,
since the same 'you' is used to represent all sorts of things in English grammar.
If not specified and the mapping is not clear, a 'subject pronoun' (he/she/it/they) is assumed.
- `gender` - set in `$pron` option as
- `male`, or `m`
- `female'` or `f`
- `neutral`, or `n`
- `plural`, or `p` (yes plural is considered a 'gender' for this purpose).
If not set as an option the system will
look for a callable or property `.gender` on the current `from_obj`. A callable will be called
with no arguments and is expected to return a string 'male/female/neutral/plural'. If none
is found, a neutral gender is assumed.
- `viewpoint`- set in `$pron` option as
- `1st person` (aliases `1st` or `1`)
- `2nd person` (aliases `2nd` or `2`)
This is only needed if you want to have 1st person perspective - if
not, 2nd person is assumed wherever the viewpoint is unclear.
`$pron()` examples:
| Input | you see | others see | note |
| --- | --- | ---| --- |
| `$pron(I, male)` | I | he | |
| `$pron(I, f)` | I | she | |
| `$pron(my)` | my | its | figures out it's an possessive adjective, assumes neutral |
| `$pron(you)` | you | it | assumes neutral subject pronoun |
| `$pron(you, f)` | you | she | female specified, assumes subject pronoun |
| `$pron(you,op f)` | you | her | |
| `$pron(you,op p)` | you | them | |
| `$pron(you, f op)` | you | her | specified female and objective pronoun|
| `$pron(yourself)` | yourself | itself | |
| `$pron(its)` | your | its | |
| `$Pron(its)` | Your | Its | Using $Pron always capitalizes |
| `$pron(her)` | you | her | 3rd person -> 2nd person |
| `$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.
The [$pron inlinefunc api is found here](evennia.utils.funcparser.funcparser_callable_pronoun)
# 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):
- `$Obj(name)/$obj(name)` references another entity, which must be supplied
in the `mapping` keyword argument to `msg_contents`. The object's `.get_display_name(looker)` will be
called and inserted instead. This is essentially the same as using the `{anna}` marker we used
in the first example at the top of this page, but using `$Obj/$obj` allows you to easily
control capitalization.
This is used like so:
```python
# director stance
text = "Tom picks up the $obj(gun), whistling to himself"
# actor stance
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" ...`
# 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.

View file

@ -1,22 +0,0 @@
# Clickable links
Evennia supports clickable links for clients that supports it. This marks certain text so it can be
clicked by a mouse and either trigger a given Evennia command, or open a URL in an external web
browser. To support clickable links, Evennia requires the webclient or an third-party telnet client
with [MXP](http://www.zuggsoft.com/zmud/mxp.htm) support (*Note: Evennia only supports clickable links, no other MXP features*).
- `|lc` to start the link, by defining the command to execute.
- `|lu` to start the link, by defining the URL to open.
- `|lt` to continue with the text to show to the user (the link text).
- `|le` to end the link text and the link definition.
All elements must appear in exactly this order to make a valid link. For example,
```
"If you go |lcnorth|ltto the north|le you will find a cottage."
```
This will display as "If you go __to the north__ you will find a cottage." where clicking the link
will execute the command `north`. If the client does not support clickable links, only the link text
will be shown.

View file

@ -1,183 +0,0 @@
# Colors
*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.
Remember however that, with the exception of the webclient, you generally don't control the client
used to connect to the game. There is, for example, one special tag meaning "yellow". But exactly
*which* hue of yellow is actually displayed on the user's screen depends on the settings of their
particular mud client. They could even swap the colours around or turn them off altogether if so
desired. Some clients don't even support color - text games are also played with special reading
equipment by people who are blind or have otherwise diminished eyesight.
So a good rule of thumb is to use colour to enhance your game but don't *rely* on it to display
critical information. If you are coding the game, you can add functionality to let users disable
colours as they please, as described [here](../Howtos/Manually-Configuring-Color.md).
To see which colours your client support, use the default `@color` command. This will list all
available colours for ANSI and Xterm256 along with the codes you use for them. You can find a list
of all the parsed `ANSI`-colour codes in `evennia/utils/ansi.py`.
## ANSI colours
Evennia supports the `ANSI` standard for text. This is by far the most supported MUD-color standard,
available in all but the most ancient mud clients. The ANSI colours are **r**ed, **g**reen,
**y**ellow, **b**lue, **m**agenta, **c**yan, **w**hite and black. They are abbreviated by their
first letter except for black which is abbreviated with the letter **x**. In ANSI there are "bright"
and "normal" (darker) versions of each color, adding up to a total of 16 colours to use for
foreground text. There are also 8 "background" colours. These have no bright alternative in ANSI
(but Evennia uses the [Xterm256](#xterm256-colours) extension behind the scenes to offer
them anyway).
To colour your text you put special tags in it. Evennia will parse these and convert them to the
correct markup for the client used. If the user's client/console/display supports ANSI colour, they
will see the text in the specified colour, otherwise the tags will be stripped (uncolored text).
This works also for non-terminal clients, such as the webclient. For the webclient, Evennia will
translate the codes to HTML RGB colors.
Here is an example of the tags in action:
|rThis text is bright red.|n This is normal text.
|RThis is a dark red text.|n This is normal text.
|[rThis text has red background.|n This is normal text.
|b|[yThis is bright blue text on yellow background.|n This is normal text.
- `|n` - this tag will turn off all color formatting, including background colors.
- `|#`- markup marks the start of foreground color. The case defines if the text is "bright" or
"normal". So `|g` is a bright green and `|G` is "normal" (darker) green.
- `|[#` is used to add a background colour to the text. The case again specifies if it is "bright"
or "normal", so `|[c` starts a bright cyan background and `|[C` a darker cyan background.
- `|!#` is used to add foreground color without any enforced brightness/normal information.
These are normal-intensity and are thus always given as uppercase, such as
`|!R` for red. The difference between e.g. `|!R` and `|R` is that
`|!R` will "inherit" the brightness setting from previously set color tags, whereas `|R` will
always reset to the normal-intensity red. The `|#` format contains an implicit `|h`/`|H` tag in it:
disabling highlighting when switching to a normal color, and enabling it for bright ones. So `|btest
|!Rtest2` will result in a bright red `test2` since the brightness setting from `|b` "bleeds over".
You could use this to for example quickly switch the intensity of a multitude of color tags. There
is no background-color equivalent to `|!` style tags.
- `|h` is used to make any following foreground ANSI colors bright (it has no effect on Xterm
colors). This is only relevant to use with `|!` type tags and will be valid until the next `|n`,
`|H` or normal (upper-case) `|#` tag. This tag will never affect background colors, those have to be
set bright/normal explicitly. Technically, `|h|!G` is identical to `|g`.
- `|H` negates the effects `|h` and returns all ANSI foreground colors (`|!` and `|` types) to
'normal' intensity. It has no effect on background and Xterm colors.
> Note: The ANSI standard does not actually support bright backgrounds like `|[r` - the standard
only supports "normal" intensity backgrounds. To get around this Evennia instead implements these
as [Xterm256 colours](#xterm256-colours) behind the scenes. If the client does not support
Xterm256 the ANSI colors will be used instead and there will be no visible difference between using
upper- and lower-case background tags.
If you want to display an ANSI marker as output text (without having any effect), you need to escape
it by preceding its `|` with another `|`:
```
say The ||r ANSI marker changes text color to bright red.
```
This will output the raw `|r` without any color change. This can also be necessary if you are doing
ansi art that uses `|` with a letter directly following it.
Use the command
@color ansi
to get a list of all supported ANSI colours and the tags used to produce them.
A few additional ANSI codes are supported:
- `|/` A line break. You cannot put the normal Python `\n` line breaks in text entered inside the
game (Evennia will filter this for security reasons). This is what you use instead: use the `|/`
marker to format text with line breaks from the game command line.
- `` This will translate into a `TAB` character. This will not always show (or show differently) to
the client since it depends on their local settings. It's often better to use multiple spaces.
- `|_` This is a space. You can usually use the normal space character, but if the space is *at the
end of the line*, Evennia will likely crop it. This tag will not be cropped but always result in a
space.
- `|*` This will invert the current text/background colours. Can be useful to mark things (but see
below).
### Caveats of `|*`
The `|*` tag (inverse video) is an old ANSI standard and should usually not be used for more than to
mark short snippets of text. If combined with other tags it comes with a series of potentially
confusing behaviors:
* The `|*` tag will only work once in a row:, ie: after using it once it won't have an effect again
until you declare another tag. This is an example:
```
Normal text, |*reversed text|*, still reversed text.
```
that is, it will not reverse to normal at the second `|*`. You need to reset it manually:
```
Normal text, |*reversed text|n, normal again.
```
* The `|*` tag does not take "bright" colors into account:
```
|RNormal red, |hnow brightened. |*BG is normal red.
```
So `|*` only considers the 'true' foreground color, ignoring any highlighting. Think of the bright
state (`|h`) as something like like `<strong>` in HTML: it modifies the _appearance_ of a normal
foreground color to match its bright counterpart, without changing its normal color.
* Finally, after a `|*`, if the previous background was set to a dark color (via `|[`), `|!#`) will
actually change the background color instead of the foreground:
```
|*reversed text |!R now BG is red.
```
For a detailed explanation of these caveats, see the [Understanding Color Tags](Understanding-Color-
Tags) tutorial. But most of the time you might be better off to simply avoid `|*` and mark your text
manually instead.
### Xterm256 Colours
The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background.
While this offers many more possibilities than traditional ANSI colours, be wary that too many text
colors will be confusing to the eye. Also, not all clients support Xterm256 - these will instead see
the closest equivalent ANSI color. You can mix Xterm256 tags with ANSI tags as you please.
|555 This is pure white text.|n This is normal text.
|230 This is olive green text.
|[300 This text has a dark red background.
|005|[054 This is dark blue text on a bright cyan background.
|=a This is a greyscale value, equal to black.
|=m This is a greyscale value, midway between white and black.
|=z This is a greyscale value, equal to white.
|[=m This is a background greyscale value.
- `|###` - markup consists of three digits, each an integer from 0 to 5. The three digits describe
the amount of **r**ed, **g**reen and **b**lue (RGB) components used in the colour. So `|500` means
maximum red and none of the other colours - the result is a bright red. `|520` is red with a touch
of green - the result is orange. As opposed to ANSI colors, Xterm256 syntax does not worry about
bright/normal intensity, a brighter (lighter) color is just achieved by upping all RGB values with
the same amount.
- `|[###` - this works the same way but produces a coloured background.
- `|=#` - markup produces the xterm256 gray scale tones, where `#` is a letter from `a` (black) to
`z` (white). This offers many more nuances of gray than the normal `|###` markup (which only has
four gray tones between solid black and white (`|000`, `|111`, `|222`, `|333` and `|444`)).
- `|[=#` - this works in the same way but produces background gray scale tones.
If you have a client that supports Xterm256, you can use
@color xterm256
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/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

@ -1,62 +0,0 @@
# Core Concepts
This documentation cover more over-arching concepts of Evennia, often involving many [Core Components](../Components/Components-Overview.md) acting together.
## General concepts
```{toctree}
:maxdepth: 2
Async-Process.md
Soft-Code.md
Using-MUX-as-a-Standard.md
Messagepath.md
OOB.md
```
## Access
```{toctree}
:maxdepth: 2
Multisession-modes.md
Building-Permissions.md
Guest-Logins.md
Banning.md
```
## Extending the Server
```{toctree}
:maxdepth: 2
Custom-Protocols.md
Bootstrap-&-Evennia.md
New-Models.md
Zones.md
```
## Text processing
```{toctree}
:maxdepth: 2
Internationalization.md
Text-Encodings.md
TextTags.md
Change-Messages-Per-Receiver.md
Clickable-Links.md
Colors.md
```
## Web features
```{toctree}
:maxdepth: 2
Web-Features.md
```

View file

@ -1,239 +0,0 @@
# Custom Protocols
*Note: This is considered an advanced topic and is mostly of interest to users planning to implement
their own custom client protocol.*
A [PortalSession](../Components/Sessions.md#portal-and-server-sessions) is the basic data object representing an
external
connection to the Evennia [Portal](../Components/Portal-And-Server.md) -- usually a human player running a mud client
of some kind. The way they connect (the language the player's client and Evennia use to talk to
each other) is called the connection *Protocol*. The most common such protocol for MUD:s is the
*Telnet* protocol. All Portal Sessions are stored and managed by the Portal's *sessionhandler*.
It's technically sometimes hard to separate the concept of *PortalSession* from the concept of
*Protocol* since both depend heavily on the other (they are often created as the same class). When
data flows through this part of the system, this is how it goes
```
# In the Portal
You <->
Protocol + PortalSession <->
PortalSessionHandler <->
(AMP) <->
ServerSessionHandler <->
ServerSession <->
InputFunc
```
(See the [Message Path](./Messagepath.md) for the bigger picture of how data flows through Evennia). The
parts that needs to be customized to make your own custom protocol is the `Protocol + PortalSession`
(which translates between data coming in/out over the wire to/from Evennia internal representation)
as well as the `InputFunc` (which handles incoming data).
## Adding custom Protocols
Evennia has a plugin-system that add the protocol as a new "service" to the application.
Take a look at `evennia/server/portal/portal.py`, notably the sections towards the end of that file.
These are where the various in-built services like telnet, ssh, webclient etc are added to the
Portal (there is an equivalent but shorter list in `evennia/server/server.py`).
To add a new service of your own (for example your own custom client protocol) to the Portal or
Server, look at `mygame/server/conf/server_services_plugins` and `portal_services_plugins`. By
default Evennia will look into these modules to find plugins. If you wanted to have it look for more
modules, you could do the following:
```python
# add to the Server
SERVER_SERVICES_PLUGIN_MODULES.append('server.conf.my_server_plugins')
# or, if you want to add to the Portal
PORTAL_SERVICES_PLUGIN_MODULES.append('server.conf.my_portal_plugins')
```
When adding a new connection you'll most likely only need to add new things to the
`PORTAL_SERVICES_PLUGIN_MODULES`.
This module can contain whatever you need to define your protocol, but it *must* contain a function
`start_plugin_services(app)`. This is called by the Portal as part of its upstart. The function
`start_plugin_services` must contain all startup code the server need. The `app` argument is a
reference to the Portal/Server application itself so the custom service can be added to it. The
function should not return anything.
This is how it looks:
```python
# mygame/server/conf/portal_services_plugins.py
# here the new Portal Twisted protocol is defined
class MyOwnFactory( ... ):
[...]
# some configs
MYPROC_ENABLED = True # convenient off-flag to avoid having to edit settings all the time
MY_PORT = 6666
def start_plugin_services(portal):
"This is called by the Portal during startup"
if not MYPROC_ENABLED:
return
# output to list this with the other services at startup
print(f" myproc: {MY_PORT}")
# some setup (simple example)
factory = MyOwnFactory()
my_service = internet.TCPServer(MY_PORT, factory)
# all Evennia services must be uniquely named
my_service.setName("MyService")
# add to the main portal application
portal.services.addService(my_service)
```
Once the module is defined and targeted in settings, just reload the server and your new
protocol/services should start with the others.
## Writing your own Protocol
Writing a stable communication protocol from scratch is not something we'll cover here, it's no
trivial task. The good news is that Twisted offers implementations of many common protocols, ready
for adapting.
Writing a protocol implementation in Twisted usually involves creating a class inheriting from an
already existing Twisted protocol class and from `evennia.server.session.Session` (multiple
inheritance), then overloading the methods that particular protocol uses to link them to the
Evennia-specific inputs.
Here's a example to show the concept:
```python
# In module that we'll later add to the system through PORTAL_SERVICE_PLUGIN_MODULES
# pseudo code
from twisted.something import TwistedClient
# this class is used both for Portal- and Server Sessions
from evennia.server.session import Session
from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS
class MyCustomClient(TwistedClient, Session):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.sessionhandler = PORTAL_SESSIONS
# these are methods we must know that TwistedClient uses for
# communication. Name and arguments could vary for different Twisted protocols
def onOpen(self, *args, **kwargs):
# let's say this is called when the client first connects
# we need to init the session and connect to the sessionhandler. The .factory
# is available through the Twisted parents
client_address = self.getClientAddress() # get client address somehow
self.init_session("mycustom_protocol", client_address, self.factory.sessionhandler)
self.sessionhandler.connect(self)
def onClose(self, reason, *args, **kwargs):
# called when the client connection is dropped
# link to the Evennia equivalent
self.disconnect(reason)
def onMessage(self, indata, *args, **kwargs):
# called with incoming data
# convert as needed here
self.data_in(data=indata)
def sendMessage(self, outdata, *args, **kwargs):
# called to send data out
# modify if needed
super().sendMessage(self, outdata, *args, **kwargs)
# these are Evennia methods. They must all exist and look exactly like this
# The above twisted-methods call them and vice-versa. This connects the protocol
# the Evennia internals.
def disconnect(self, reason=None):
"""
Called when connection closes.
This can also be called directly by Evennia when manually closing the connection.
Do any cleanups here.
"""
self.sessionhandler.disconnect(self)
def at_login(self):
"""
Called when this session authenticates by the server (if applicable)
"""
def data_in(self, **kwargs):
"""
Data going into the server should go through this method. It
should pass data into `sessionhandler.data_in`. THis will be called
by the sessionhandler with the data it gets from the approrpriate
send_* method found later in this protocol.
"""
self.sessionhandler.data_in(self, text=kwargs['data'])
def data_out(self, **kwargs):
"""
Data going out from the server should go through this method. It should
hand off to the protocol's send method, whatever it's called.
"""
# we assume we have a 'text' outputfunc
self.onMessage(kwargs['text'])
# 'outputfuncs' are defined as `send_<outputfunc_name>`. From in-code, they are called
# with `msg(outfunc_name=<data>)`.
def send_text(self, txt, *args, **kwargs):
"""
Send text, used with e.g. `session.msg(text="foo")`
"""
# we make use of the
self.data_out(text=txt)
def send_default(self, cmdname, *args, **kwargs):
"""
Handles all outputfuncs without an explicit `send_*` method to handle them.
"""
self.data_out(**{cmdname: str(args)})
```
The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to
the Evennia-specific methods.
### Sending data out
To send data out through this protocol, you'd need to get its Session and then you could e.g.
```python
session.msg(text="foo")
```
The message will pass through the system such that the sessionhandler will dig out the session and
check if it has a `send_text` method (it has). It will then pass the "foo" into that method, which
in our case means sending "foo" across the network.
### Receiving data
Just because the protocol is there, does not mean Evennia knows what to do with it. An
[Inputfunc](../Components/Inputfuncs.md) must exist to receive it. In the case of the `text` input exemplified above,
Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So
handle that you need to simply add a cmdset with commands on your receiving Session (and/or the
Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the
[Inputfunc](../Components/Inputfuncs.md) page for how to do this.
These might not be as clear-cut in all protocols, but the principle is there. These four basic
components - however they are accessed - links to the *Portal Session*, which is the actual common
interface between the different low-level protocols and Evennia.
## Assorted notes
To take two examples, Evennia supports the *telnet* protocol as well as *webclient*, via ajax or
websockets. You'll find that whereas telnet is a textbook example of a Twisted protocol as seen
above, the ajax protocol looks quite different due to how it interacts with the
webserver through long-polling (comet) style requests. All the necessary parts
mentioned above are still there, but by necessity implemented in very different
ways.

View file

@ -1,29 +0,0 @@
# Guest Logins
Evennia supports *guest logins* out of the box. A guest login is an anonymous, low-access account
and can be useful if you want users to have a chance to try out your game without committing to
creating a real account.
Guest accounts are turned off by default. To activate, add this to your `game/settings.py` file:
GUEST_ENABLED = True
Henceforth users can use `connect guest` (in the default command set) to login with a guest account.
You may need to change your [Connection Screen](../Components/Connection-Screen.md) to inform them of this
possibility. Guest accounts work differently from normal accounts - they are automatically *deleted*
whenever the user logs off or the server resets (but not during a reload). They are literally re-
usable throw-away accounts.
You can add a few more variables to your `settings.py` file to customize your guests:
- `BASE_GUEST_TYPECLASS` - the python-path to the default [typeclass](../Components/Typeclasses.md) for guests.
Defaults to `"typeclasses.accounts.Guest"`.
- `PERMISSION_GUEST_DEFAULT` - [permission level](../Components/Locks.md) for guest accounts. Defaults to `"Guests"`,
which is the lowest permission level in the hierarchy.
- `GUEST_START_LOCATION` - the `#dbref` to the starting location newly logged-in guests should
appear at. Defaults to `"#2` (Limbo).
- `GUEST_HOME` - guest home locations. Defaults to Limbo as well.
- `GUEST_LIST` - this is a list holding the possible guest names to use when entering the game. The
length of this list also sets how many guests may log in at the same time. By default this is a list
of nine names from `"Guest1"` to `"Guest9"`.

View file

@ -1,160 +0,0 @@
# Internationalization
*Internationalization* (often abbreviated *i18n* since there are 18 characters
between the first "i" and the last "n" in that word) allows Evennia's core
server to return texts in other languages than English - without anyone having
to edit the source code.
Language-translations are done by volunteers, so support can vary a lot
depending on when a given language was last updated. Below are all languages
(besides English) with some level of support. Generally, any language not
updated after Sept 2022 will be missing some translations.
```{eval-rst}
+---------------+----------------------+--------------+
| Language Code | Language | Last updated |
+===============+======================+==============+
| es | Spanish | Aug 2019 |
+---------------+----------------------+--------------+
| fr | French | Mar 2022 |
+---------------+----------------------+--------------+
| it | Italian | Oct 2022 |
+---------------+----------------------+--------------+
| ko | Korean (simplified) | Sep 2019 |
+---------------+----------------------+--------------+
| la | Latin | Feb 2021 |
+---------------+----------------------+--------------+
| pl | Polish | Feb 2019 |
+---------------+----------------------+--------------+
| pt | Portugese | Oct 2022 |
+---------------+----------------------+--------------+
| ru | Russian | Apr 2020 |
+---------------+----------------------+--------------+
| sv | Swedish | Sep 2022 |
+---------------+----------------------+--------------+
| zh | Chinese (simplified) | May 2019 |
+---------------+----------------------+--------------+
```
Language translations are found in the [evennia/locale](github:evennia/locale/)
folder. Read below if you want to help improve an existing translation of
contribute a new one.
## Changing server language
Change language by adding the following to your `mygame/server/conf/settings.py`
file:
```python
USE_I18N = True
LANGUAGE_CODE = 'en'
```
Here `'en'` (the default English) should be changed to the abbreviation for one
of the supported languages found in `locale/` (and in the list above). Restart
the server to activate i18n.
```{important}
Even for a 'fully translated' language you will still see English text
in many places when you start Evennia. This is because we expect you (the
developer) to know English (you are reading this manual after all). So we
translate *hard-coded strings that the end player may see* - things you
can't easily change from your mygame/ folder. Outputs from Commands and
Typeclasses are generally *not* translated, nor are console/log outputs.
```
```{sidebar} Windows users
If you get errors concerning `gettext` or `xgettext` on Windows,
see the [Django documentation](https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#gettext-on-windows).
A self-installing and up-to-date version of gettext for Windows (32/64-bit) is
available on Github as [gettext-iconv-windows](https://github.com/mlocati/gettext-iconv-windows).
```
## Translating Evennia
Translations are found in the core `evennia/` library, under
`evennia/evennia/locale/`. You must make sure to have cloned this repository
from [Evennia's github](github:evennia) before you can proceed.
If you cannot find your language in `evennia/evennia/locale/` it's because noone
has translated it yet. Alternatively you might have the language but find the
translation bad ... You are welcome to help improve the situation!
To start a new translation you need to first have cloned the Evennia repositry
with GIT and activated a python virtualenv as described on the
[Setup Quickstart](../Setup/Installation.md) page.
Go to `evennia/evennia/` - that is, not your game dir, but inside the `evennia/`
repo itself. If you see the `locale/` folder you are in the right place. Make
sure your `virtualenv` is active so the `evennia` command is available. Then run
evennia makemessages --locale <language-code>
where `<language-code>` is the [two-letter locale code](http://www.science.co.il/Language/Codes.asp)
for the language you want to translate, like 'sv' for Swedish or 'es' for
Spanish. After a moment it will tell you the language has been processed. For
instance:
evennia makemessages --locale sv
If you started a new language, a new folder for that language will have emerged
in the `locale/` folder. Otherwise the system will just have updated the
existing translation with eventual new strings found in the server. Running this
command will not overwrite any existing strings so you can run it as much as you
want.
Next head to `locale/<language-code>/LC_MESSAGES` and edit the `**.po` file you
find there. You can edit this with a normal text editor but it is easiest if
you use a special po-file editor from the web (search the web for "po editor"
for many free alternatives), for example:
- [gtranslator](https://wiki.gnome.org/Apps/Gtranslator)
- [poeditor](https://poeditor.com/)
The concept of translating is simple, it's just a matter of taking the english
strings you find in the `**.po` file and add your language's translation best
you can. Once you are done, run
`evennia compilemessages`
This will compile all languages. Check your language and also check back to your
`.po` file in case the process updated it - you may need to fill in some missing
header fields and should usually note who did the translation.
When you are done, make sure that everyone can benefit from your translation!
Make a PR against Evennia with the updated `**.po` file. Less ideally (if git is
not your thing) you can also attach it to a new post in our forums.
### Hints on translation
Many of the translation strings use `{ ... }` placeholders. This is because they
are to be used in `.format()` python operations. While you can change the
_order_ of these if it makes more sense in your language, you must _not_
translate the variables in these formatting tags - Python will look for them!
Original: "|G{key} connected|n"
Swedish: "|G{key} anslöt|n"
You must also retain line breaks _at the start and end_ of a message, if any
(your po-editor should stop you if you don't). Try to also end with the same
sentence delimiter (if that makes sense in your language).
Original: "\n(Unsuccessfull tried '{path}')."
Swedish: "\nMisslyckades med att nå '{path}')."
Finally, try to get a feel for who a string is for. If a special technical term
is used it may be more confusing than helpful to translate it, even if it's
outside of a `{...}` tag. A mix of English and your language may be clearer
than you forcing some ad-hoc translation for a term everyone usually reads in
English anyway.
Original: "\nError loading cmdset: No cmdset class '{classname}' in '{path}'.
\n(Traceback was logged {timestamp})"
Swedish: "Fel medan cmdset laddades: Ingen cmdset-klass med namn '{classname}' i {path}.
\n(Traceback loggades {timestamp})"

View file

@ -1,211 +0,0 @@
# Messagepath
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.
## The ingoing message path
We'll start by tracing data from the client to the server. Here it is in short:
Client ->
PortalSession ->
PortalSessionhandler ->
(AMP) ->
ServerSessionHandler ->
ServerSession ->
Inputfunc
### Client (ingoing)
The client sends data to Evennia in two ways.
- 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.
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.
### 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*:
```python
(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:
```python
("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.
### 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:
```python
text(session, *("look",), **{})
```
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:
```python
msg(text=None, from_obj=None, session=None, options=None, **kwargs)
```
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
```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})
```
This will intelligently convert different input to the same form. So `msg("Hello")` will end up as
an outputcommand `("text", ("Hello",), {})`.
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.
### PortalSessionHandler (outgoing)
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 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>`.
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.
### PortalSession (outgoing)
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.
### Client (outgoing)
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.

View file

@ -1,3 +0,0 @@
# Multisession modes
TODO: This is covered in various places before.

View file

@ -1,264 +0,0 @@
# New Models
*Note: This is considered an advanced topic.*
Evennia offers many convenient ways to store object data, such as via Attributes or Scripts. This is
sufficient for most use cases. But if you aim to build a large stand-alone system, trying to squeeze
your storage requirements into those may be more complex than you bargain for. Examples may be to
store guild data for guild members to be able to change, tracking the flow of money across a game-
wide economic system or implement other custom game systems that requires the storage of custom data
in a quickly accessible way. Whereas [Tags](../Components/Tags.md) or [Scripts](../Components/Scripts.md) can handle many situations,
sometimes things may be easier to handle by adding your own database model.
## Overview of database tables
SQL-type databases (which is what Evennia supports) are basically highly optimized systems for
retrieving text stored in tables. A table may look like this
```
id | db_key | db_typeclass_path | db_permissions ...
------------------------------------------------------------------
1 | Griatch | evennia.DefaultCharacter | Developers ...
2 | Rock | evennia.DefaultObject | None ...
```
Each line is considerably longer in your database. Each column is referred to as a "field" and every
row is a separate object. You can check this out for yourself. If you use the default sqlite3
database, go to your game folder and run
evennia dbshell
You will drop into the database shell. While there, try:
sqlite> .help # view help
sqlite> .tables # view all tables
# show the table field names for objects_objectdb
sqlite> .schema objects_objectdb
# show the first row from the objects_objectdb table
sqlite> select * from objects_objectdb limit 1;
sqlite> .exit
Evennia uses [Django](https://docs.djangoproject.com), which abstracts away the database SQL
manipulation and allows you to search and manipulate your database entirely in Python. Each database
table is in Django represented by a class commonly called a *model* since it describes the look of
the table. In Evennia, Objects, Scripts, Channels etc are examples of Django models that we then
extend and build on.
## Adding a new database table
Here is how you add your own database table/models:
1. In Django lingo, we will create a new "application" - a subsystem under the main Evennia program.
For this example we'll call it "myapp". Run the following (you need to have a working Evennia
running before you do this, so make sure you have run the steps in [Setup Quickstart](Getting-
Started) first):
cd mygame/world
evennia startapp myapp
1. A new folder `myapp` is created. "myapp" will also be the name (the "app label") from now on. We
chose to put it in the `world/` subfolder here, but you could put it in the root of your `mygame` if
that makes more sense.
1. The `myapp` folder contains a few empty default files. What we are
interested in for now is `models.py`. In `models.py` you define your model(s). Each model will be a
table in the database. See the next section and don't continue until you have added the models you
want.
1. You now need to tell Evennia that the models of your app should be a part of your database
scheme. Add this line to your `mygame/server/conf/settings.py`file (make sure to use the path where
you put `myapp` and don't forget the comma at the end of the tuple):
```
INSTALLED_APPS = INSTALLED_APPS + ("world.myapp", )
```
1. From `mygame/`, run
evennia makemigrations myapp
evennia migrate
This will add your new database table to the database. If you have put your game under version
control (if not, [you should](../Coding/Version-Control.md)), don't forget to `git add myapp/*` to add all items
to version control.
## Defining your models
A Django *model* is the Python representation of a database table. It can be handled like any other
Python class. It defines *fields* on itself, objects of a special type. These become the "columns"
of the database table. Finally, you create new instances of the model to add new rows to the
database.
We won't describe all aspects of Django models here, for that we refer to the vast [Django
documentation](https://docs.djangoproject.com/en/2.2/topics/db/models/) on the subject. Here is a
(very) brief example:
```python
from django.db import models
class MyDataStore(models.Model):
"A simple model for storing some data"
db_key = models.CharField(max_length=80, db_index=True)
db_category = models.CharField(max_length=80, null=True, blank=True)
db_text = models.TextField(null=True, blank=True)
# we need this one if we want to be
# able to store this in an Evennia Attribute!
db_date_created = models.DateTimeField('date created', editable=False,
auto_now_add=True, db_index=True)
```
We create four fields: two character fields of limited length and one text field which has no
maximum length. Finally we create a field containing the current time of us creating this object.
> The `db_date_created` field, with exactly this name, is *required* if you want to be able to store
instances of your custom model in an Evennia [Attribute](../Components/Attributes.md). It will automatically be set
upon creation and can after that not be changed. Having this field will allow you to do e.g.
`obj.db.myinstance = mydatastore`. If you know you'll never store your model instances in Attributes
the `db_date_created` field is optional.
You don't *have* to start field names with `db_`, this is an Evennia convention. It's nevertheless
recommended that you do use `db_`, partly for clarity and consistency with Evennia (if you ever want
to share your code) and partly for the case of you later deciding to use Evennia's
`SharedMemoryModel` parent down the line.
The field keyword `db_index` creates a *database index* for this field, which allows quicker
lookups, so it's recommended to put it on fields you know you'll often use in queries. The
`null=True` and `blank=True` keywords means that these fields may be left empty or set to the empty
string without the database complaining. There are many other field types and keywords to define
them, see django docs for more info.
Similar to using [django-admin](https://docs.djangoproject.com/en/2.2/howto/legacy-databases/) you
are able to do `evennia inspectdb` to get an automated listing of model information for an existing
database. As is the case with any model generating tool you should only use this as a starting
point for your models.
## Creating a new model instance
To create a new row in your table, you instantiate the model and then call its `save()` method:
```python
from evennia.myapp import MyDataStore
new_datastore = MyDataStore(db_key="LargeSword",
db_category="weapons",
db_text="This is a huge weapon!")
# this is required to actually create the row in the database!
new_datastore.save()
```
Note that the `db_date_created` field of the model is not specified. Its flag `at_now_add=True`
makes sure to set it to the current date when the object is created (it can also not be changed
further after creation).
When you update an existing object with some new field value, remember that you have to save the
object afterwards, otherwise the database will not update:
```python
my_datastore.db_key = "Larger Sword"
my_datastore.save()
```
Evennia's normal models don't need to explicitly save, since they are based on `SharedMemoryModel`
rather than the raw django model. This is covered in the next section.
## Using the `SharedMemoryModel` parent
Evennia doesn't base most of its models on the raw `django.db.models` but on the Evennia base model
`evennia.utils.idmapper.models.SharedMemoryModel`. There are two main reasons for this:
1. Ease of updating fields without having to explicitly call `save()`
2. On-object memory persistence and database caching
The first (and least important) point means that as long as you named your fields `db_*`, Evennia
will automatically create field wrappers for them. This happens in the model's
[Metaclass](http://en.wikibooks.org/wiki/Python_Programming/Metaclasses) so there is no speed
penalty for this. The name of the wrapper will be the same name as the field, minus the `db_`
prefix. So the `db_key` field will have a wrapper property named `key`. You can then do:
```python
my_datastore.key = "Larger Sword"
```
and don't have to explicitly call `save()` afterwards. The saving also happens in a more efficient
way under the hood, updating only the field rather than the entire model using django optimizations.
Note that if you were to manually add the property or method `key` to your model, this will be used
instead of the automatic wrapper and allows you to fully customize access as needed.
To explain the second and more important point, consider the following example using the default
Django model parent:
```python
shield = MyDataStore.objects.get(db_key="SmallShield")
shield.cracked = True # where cracked is not a database field
```
And then later:
```python
shield = MyDataStore.objects.get(db_key="SmallShield")
print(shield.cracked) # error!
```
The outcome of that last print statement is *undefined*! It could *maybe* randomly work but most
likely you will get an `AttributeError` for not finding the `cracked` property. The reason is that
`cracked` doesn't represent an actual field in the database. It was just added at run-time and thus
Django don't care about it. When you retrieve your shield-match later there is *no* guarantee you
will get back the *same Python instance* of the model where you defined `cracked`, even if you
search for the same database object.
Evennia relies heavily on on-model handlers and other dynamically created properties. So rather than
using the vanilla Django models, Evennia uses `SharedMemoryModel`, which levies something called
*idmapper*. The idmapper caches model instances so that we will always get the *same* instance back
after the first lookup of a given object. Using idmapper, the above example would work fine and you
could retrieve your `cracked` property at any time - until you rebooted when all non-persistent data
goes.
Using the idmapper is both more intuitive and more efficient *per object*; it leads to a lot less
reading from disk. The drawback is that this system tends to be more memory hungry *overall*. So if
you know that you'll *never* need to add new properties to running instances or know that you will
create new objects all the time yet rarely access them again (like for a log system), you are
probably better off making "plain" Django models rather than using `SharedMemoryModel` and its
idmapper.
To use the idmapper and the field-wrapper functionality you just have to have your model classes
inherit from `evennia.utils.idmapper.models.SharedMemoryModel` instead of from the default
`django.db.models.Model`:
```python
from evennia.utils.idmapper.models import SharedMemoryModel
class MyDataStore(SharedMemoryModel):
# the rest is the same as before, but db_* is important; these will
# later be settable as .key, .category, .text ...
db_key = models.CharField(max_length=80, db_index=True)
db_category = models.CharField(max_length=80, null=True, blank=True)
db_text = models.TextField(null=True, blank=True)
db_date_created = models.DateTimeField('date created', editable=False,
auto_now_add=True, db_index=True)
```
## Searching for your models
To search your new custom database table you need to use its database *manager* to build a *query*.
Note that even if you use `SharedMemoryModel` as described in the previous section, you have to use
the actual *field names* in the query, not the wrapper name (so `db_key` and not just `key`).
```python
from world.myapp import MyDataStore
# get all datastore objects exactly matching a given key
matches = MyDataStore.objects.filter(db_key="Larger Sword")
# get all datastore objects with a key containing "sword"
# and having the category "weapons" (both ignoring upper/lower case)
matches2 = MyDataStore.objects.filter(db_key__icontains="sword",
db_category__iequals="weapons")
# show the matching data (e.g. inside a command)
for match in matches2:
self.caller.msg(match.db_text)
```
See the [Django query documentation](https://docs.djangoproject.com/en/2.2/topics/db/queries/) for a
lot more information about querying the database.

View file

@ -1,167 +0,0 @@
# OOB
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
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).
## 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:
```python
caller.msg(cmdname=((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:
```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
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.
> 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.
[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).
## 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.
> 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:
- [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.
On the wire, a GMCP instruction for `("cmdname", ("arg",), {})` will look like this:
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.
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}]
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"
#### 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 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
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
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
["cmdname, ["arg"], {}]
Since JSON is native to Javascript, this becomes very easy for the webclient to handle.

View file

@ -1,94 +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

@ -1,66 +0,0 @@
# Text Encodings
Evennia is a text-based game server. This makes it important to understand how
it actually deals with data in the form of text.
Text *byte encodings* describe how a string of text is actually stored in the
computer - that is, the particular sequence of bytes used to represent the
letters of your particular alphabet. A common encoding used in English-speaking
languages is the *ASCII* encoding. This describes the letters in the English
alphabet (Aa-Zz) as well as a bunch of special characters. For describing other
character sets (such as that of other languages with other letters than
English), sets with names such as *Latin-1*, *ISO-8859-3* and *ARMSCII-8*
are used. There are hundreds of different byte encodings in use around the
world.
A string of letters in a byte encoding is represented with the `bytes` type.
In contrast to the byte encoding is the *unicode representation*. In Python
this is the `str` type. The unicode is an internationally agreed-upon table
describing essentially all available letters you could ever want to print.
Everything from English to Chinese alphabets and all in between. So what
Evennia (as well as Python and Django) does is to store everything in Unicode
internally, but then converts the data to one of the encodings whenever
outputting data to the user.
An easy memory aid is that `bytes` are what are sent over the network wire. At
all other times, `str` (unicode) is used. This means that we must convert
between the two at the points where we send/receive network data.
The problem is that when receiving a string of bytes over the network it's
impossible for Evennia to guess which encoding was used - it's just a bunch of
bytes! Evennia must know the encoding in order to convert back and from the
correct unicode representation.
## How to customize encodings
As long as you stick to the standard ASCII character set (which means the
normal English characters, basically) you should not have to worry much
about this section.
If you want to build your game in another language however, or expect your
users to want to use special characters not in ASCII, you need to consider
which encodings you want to support.
As mentioned, there are many, many byte-encodings used around the world. It
should be clear at this point that Evennia can't guess but has to assume or
somehow be told which encoding you want to use to communicate with the server.
Basically the encoding used by your client must be the same encoding used by
the server. This can be customized in two complementary ways.
1. Point users to the default `@encoding` command or the `@options` command.
This allows them to themselves set which encoding they (and their client of
choice) uses. Whereas data will remain stored as unicode strings internally in
Evennia, all data received from and sent to this particular player will be
converted to the given format before transmitting.
1. As a back-up, in case the user-set encoding translation is erroneous or
fails in some other way, Evennia will fall back to trying with the names
defined in the settings variable `ENCODINGS`. This is a list of encoding
names Evennia will try, in order, before giving up and giving an encoding
error message.
Note that having to try several different encodings every input/output adds
unneccesary overhead. Try to guess the most common encodings you players will
use and make sure these are tried first. The International *UTF-8* encoding is
what Evennia assumes by default (and also what Python/Django use normally). See
the Wikipedia article [here](https://en.wikipedia.org/wiki/Text_encodings) for more help.

View file

@ -1,20 +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,85 +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".
## Documentation policy
All the commands in the default command sets should have their doc-strings formatted on a similar
form:
```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 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
```

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/Beginner-Tutorial/Part5/Web-Tutorial.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`.

View file

@ -1,55 +0,0 @@
# Zones
Say you create a room named *Meadow* in your nice big forest MUD. That's all nice and dandy, but
what if you, in the other end of that forest want another *Meadow*? As a game creator, this can
cause all sorts of confusion. For example, teleporting to *Meadow* will now give you a warning that
there are two *Meadow* s and you have to select which one. It's no problem to do that, you just
choose for example to go to `2-meadow`, but unless you examine them you couldn't be sure which of
the two sat in the magical part of the forest and which didn't.
Another issue is if you want to group rooms in geographic regions. Let's say the "normal" part of
the forest should have separate weather patterns from the magical part. Or maybe a magical
disturbance echoes through all magical-forest rooms. It would then be convenient to be able to
simply find all rooms that are "magical" so you could send messages to them.
## Zones in Evennia
*Zones* try to separate rooms by global location. In our example we would separate the forest into
two parts - the magical and the non-magical part. Each have a *Meadow* and rooms belonging to each
part should be easy to retrieve.
Many MUD codebases hardcode zones as part of the engine and database. Evennia does no such
distinction due to the fact that rooms themselves are meant to be customized to any level anyway.
Below is a suggestion for how to implement zones in Evennia.
All objects in Evennia can hold any number of [Tags](../Components/Tags.md). Tags are short labels that you attach to
objects. They make it very easy to retrieve groups of objects. An object can have any number of
different tags. So let's attach the relevant tag to our forest:
```python
forestobj.tags.add("magicalforest", category="zone")
```
You could add this manually, or automatically during creation somehow (you'd need to modify your
@dig command for this, most likely). You can also use the default `@tag` command during building:
@tag forestobj = magicalforest : zone
Henceforth you can then easily retrieve only objects with a given tag:
```python
import evennia
rooms = evennia.search_tag("magicalforest", category="zone")
```
## Using typeclasses and inheritance for zoning
The tagging or aliasing systems above don't instill any sort of functional difference between a
magical forest room and a normal one - they are just arbitrary ways to mark objects for quick
retrieval later. Any functional differences must be expressed using [Typeclasses](../Components/Typeclasses.md).
Of course, an alternative way to implement zones themselves is to have all rooms/objects in a zone
inherit from a given typeclass parent - and then limit your searches to objects inheriting from that
given parent. The effect would be similar but you'd need to expand the search functionality to
properly search the inheritance tree.