Refactoring Concept/Component documentation. Still not done
This commit is contained in:
parent
7114aea912
commit
c7ec3dfad3
42 changed files with 745 additions and 1275 deletions
|
|
@ -1,14 +1,17 @@
|
|||
# Async Process
|
||||
|
||||
|
||||
*This is considered an advanced topic.*
|
||||
```{important}
|
||||
This is considered an advanced topic.
|
||||
```
|
||||
|
||||
## Synchronous versus Asynchronous
|
||||
|
||||
Most program code operates *synchronously*. This means that each statement in your code gets
|
||||
processed and finishes before the next can begin. This makes for easy-to-understand code. It is also
|
||||
a *requirement* in many cases - a subsequent piece of code often depend on something calculated or
|
||||
defined in a previous statement.
|
||||
```{sidebar}
|
||||
This is also explored in the [Command Duration Tutorial](../Howtos/Howto-Command-Duration.md).
|
||||
```
|
||||
|
||||
Most program code operates *synchronously*. This means that each statement in your code gets processed and finishes before the next can begin. This makes for easy-to-understand code. It is also a *requirement* in many cases - a subsequent piece of code often depend on something calculated or defined in a previous statement.
|
||||
|
||||
Consider this piece of code in a traditional Python program:
|
||||
|
||||
|
|
@ -16,27 +19,94 @@ Consider this piece of code in a traditional Python program:
|
|||
print("before call ...")
|
||||
long_running_function()
|
||||
print("after call ...")
|
||||
|
||||
```
|
||||
|
||||
When run, this will print `"before call ..."`, after which the `long_running_function` gets to work
|
||||
for however long time. Only once that is done, the system prints `"after call ..."`. Easy and
|
||||
logical to follow. Most of Evennia work in this way and often it's important that commands get
|
||||
for however long time. Only once that is done, the system prints `"after call ..."`. Easy and logical to follow. Most of Evennia work in this way and often it's important that commands get
|
||||
executed in the same strict order they were coded.
|
||||
|
||||
Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it
|
||||
swiftly switches between dealing with player input so quickly that each player feels like they do
|
||||
things at the same time. This is a clever illusion however: If one user, say, runs a command
|
||||
containing that `long_running_function`, *all* other players are effectively forced to wait until it
|
||||
finishes.
|
||||
Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it swiftly switches between dealing with player input so quickly that each player feels like they do things at the same time. This is a clever illusion however: If one user, say, runs a command containing that `long_running_function`, *all* other players are effectively forced to wait until it finishes.
|
||||
|
||||
Now, it should be said that on a modern computer system this is rarely an issue. Very few commands
|
||||
run so long that other users notice it. And as mentioned, most of the time you *want* to enforce
|
||||
all commands to occur in strict sequence.
|
||||
Now, it should be said that on a modern computer system this is rarely an issue. Very few commands run so long that other users notice it. And as mentioned, most of the time you *want* to enforce all commands to occur in strict sequence.
|
||||
|
||||
When delays do become noticeable and you don't care in which order the command actually completes,
|
||||
you can run it *asynchronously*. This makes use of the `run_async()` function in
|
||||
`src/utils/utils.py`:
|
||||
## `utils.delay`
|
||||
|
||||
```{sidebar} delay() vs time.sleep()
|
||||
This is equivalent to something like `time.sleep()` except `delay` is asynchronous while `sleep` would lock the entire server for the duration of the sleep.
|
||||
```
|
||||
The `delay` function is a much simpler sibling to `run_async`. It is in fact just a way to delay the execution of a command until a future time.
|
||||
|
||||
```python
|
||||
from evennia.utils import delay
|
||||
|
||||
# [...]
|
||||
# e.g. inside a Command, where `self.caller` is available
|
||||
def callback(obj):
|
||||
obj.msg("Returning!")
|
||||
delay(10, callback, self.caller)
|
||||
```
|
||||
|
||||
This will delay the execution of the callback for 10 seconds. Provide `persistent=True` to make the delay survive a server `reload`. While waiting, you can input commands normally.
|
||||
|
||||
You can also try the following snippet just see how it works:
|
||||
|
||||
py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self)
|
||||
|
||||
Wait 10 seconds and 'Test!' should be echoed back to you.
|
||||
|
||||
|
||||
## `@utils.interactive` decorator
|
||||
|
||||
The `@interactive` [decorator](https://realpython.com/primer-on-python- decorators/) makes any function or method possible to 'pause' and/or await player input in an interactive way.
|
||||
|
||||
```python
|
||||
from evennia.utils import interactive
|
||||
|
||||
@interactive
|
||||
def myfunc(caller):
|
||||
|
||||
while True:
|
||||
caller.msg("Getting ready to wait ...")
|
||||
yield(5)
|
||||
caller.msg("Now 5 seconds have passed.")
|
||||
|
||||
response = yield("Do you want to wait another 5 secs?")
|
||||
|
||||
if response.lower() not in ("yes", "y"):
|
||||
break
|
||||
```
|
||||
|
||||
The `@interactive` decorator gives the function the ability to pause. The use of `yield(seconds)` will do just that - it will asynchronously pause for the number of seconds given before continuing. This is technically equivalent to using `call_async` with a callback that continues after 5 secs. But the code with `@interactive` is a little easier to follow.
|
||||
|
||||
Within the `@interactive` function, the `response = yield("question")` question allows you to ask the user for input. You can then process the input, just like you would if you used the Python `input` function.
|
||||
|
||||
All of this makes the `@interactive` decorator very useful. But it comes with a few caveats.
|
||||
|
||||
- The decorated function/method/callable must have an argument named exactly `caller`. Evennia will look for an argument with this name and treat it as the source of input.
|
||||
- Decorating a function this way turns it turns it into a Python [generator](https://wiki.python.org/moin/Generators). This means
|
||||
- You can't use `return <value>` from a generator (just an empty `return` works). To return a value from a function/method you have decorated with `@interactive`, you must instead use a special Twisted function `twisted.internet.defer.returnValue`. Evennia also makes this function conveniently available from `evennia.utils`:
|
||||
|
||||
```python
|
||||
from evennia.utils import interactive, returnValue
|
||||
|
||||
@interactive
|
||||
def myfunc():
|
||||
|
||||
# ...
|
||||
result = 10
|
||||
|
||||
# this must be used instead of `return result`
|
||||
returnValue(result)
|
||||
```
|
||||
|
||||
|
||||
## `utils.run_async`
|
||||
|
||||
```{warning}
|
||||
Unless you have a very clear purpose in mind, you are unlikely to get an expected result from `run_async`. Notably, it will still run your long-running function _in the same thread_ as the rest of the server. So while it does run async, a very heavy and CPU-heavy operation will still block the server. So don't consider this as a way to offload heavy operations without affecting the rest of the server.
|
||||
```
|
||||
|
||||
When you don't care in which order the command actually completes, you can run it *asynchronously*. This makes use of the `run_async()` function in `src/utils/utils.py`:
|
||||
|
||||
```python
|
||||
run_async(function, *args, **kwargs)
|
||||
|
|
@ -51,12 +121,7 @@ Where `function` will be called asynchronously with `*args` and `**kwargs`. Exam
|
|||
print("after call ...")
|
||||
```
|
||||
|
||||
Now, when running this you will find that the program will not wait around for
|
||||
`long_running_function` to finish. In fact you will see `"before call ..."` and `"after call ..."`
|
||||
printed out right away. The long-running function will run in the background and you (and other
|
||||
users) can go on as normal.
|
||||
|
||||
## Customizing asynchronous operation
|
||||
Now, when running this you will find that the program will not wait around for `long_running_function` to finish. In fact you will see `"before call ..."` and `"after call ..."` printed out right away. The long-running function will run in the background and you (and other users) can go on as normal.
|
||||
|
||||
A complication with using asynchronous calls is what to do with the result from that call. What if
|
||||
`long_running_function` returns a value that you need? It makes no real sense to put any lines of
|
||||
|
|
@ -75,8 +140,7 @@ line quite pointless for processing any data from the function. Instead one has
|
|||
print(r)
|
||||
```
|
||||
|
||||
- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the
|
||||
`at_return` callback.
|
||||
- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the `at_return` callback.
|
||||
- `at_err(e)` (the *errback*) is called if the asynchronous function fails and raises an exception.
|
||||
This exception is passed to the errback wrapped in a *Failure* object `e`. If you do not supply an
|
||||
errback of your own, Evennia will automatically add one that silently writes errors to the evennia
|
||||
|
|
@ -116,119 +180,6 @@ An example of making an asynchronous call from inside a [Command](../Components/
|
|||
at_err=at_err_function)
|
||||
```
|
||||
|
||||
That's it - from here on we can forget about `long_running_function` and go on with what else need
|
||||
to be done. *Whenever* it finishes, the `at_return_function` function will be called and the final
|
||||
value will
|
||||
pop up for us to see. If not we will see an error message.
|
||||
That's it - from here on we can forget about `long_running_function` and go on with what else need to be done. *Whenever* it finishes, the `at_return_function` function will be called and the final value will pop up for us to see. If not we will see an error message.
|
||||
|
||||
## delay
|
||||
|
||||
The `delay` function is a much simpler sibling to `run_async`. It is in fact just a way to delay the
|
||||
execution of a command until a future time. This is equivalent to something like `time.sleep()`
|
||||
except delay is asynchronous while `sleep` would lock the entire server for the duration of the
|
||||
sleep.
|
||||
|
||||
```python
|
||||
from evennia.utils import delay
|
||||
|
||||
# [...]
|
||||
# e.g. inside a Command, where `self.caller` is available
|
||||
def callback(obj):
|
||||
obj.msg("Returning!")
|
||||
delay(10, callback, self.caller)
|
||||
```
|
||||
|
||||
This will delay the execution of the callback for 10 seconds. This function is explored much more in
|
||||
the [Command Duration Tutorial](../Howtos/Howto-Command-Duration.md).
|
||||
|
||||
You can also try the following snippet just see how it works:
|
||||
|
||||
@py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self)
|
||||
|
||||
Wait 10 seconds and 'Test!' should be echoed back to you.
|
||||
|
||||
|
||||
## The @interactive decorator
|
||||
|
||||
As of Evennia 0.9, the `@interactive` [decorator](https://realpython.com/primer-on-python-
|
||||
decorators/)
|
||||
is available. This makes any function or method possible to 'pause' and/or await player input
|
||||
in an interactive way.
|
||||
|
||||
```python
|
||||
from evennia.utils import interactive
|
||||
|
||||
@interactive
|
||||
def myfunc(caller):
|
||||
|
||||
while True:
|
||||
caller.msg("Getting ready to wait ...")
|
||||
yield(5)
|
||||
caller.msg("Now 5 seconds have passed.")
|
||||
|
||||
response = yield("Do you want to wait another 5 secs?")
|
||||
|
||||
if response.lower() not in ("yes", "y"):
|
||||
break
|
||||
```
|
||||
|
||||
The `@interactive` decorator gives the function the ability to pause. The use
|
||||
of `yield(seconds)` will do just that - it will asynchronously pause for the
|
||||
number of seconds given before continuing. This is technically equivalent to
|
||||
using `call_async` with a callback that continues after 5 secs. But the code
|
||||
with `@interactive` is a little easier to follow.
|
||||
|
||||
Within the `@interactive` function, the `response = yield("question")` question
|
||||
allows you to ask the user for input. You can then process the input, just like
|
||||
you would if you used the Python `input` function. There is one caveat to this
|
||||
functionality though - _it will only work if the function/method has an
|
||||
argument named exactly `caller`_. This is because internally Evennia will look
|
||||
for the `caller` argument and treat that as the source of input.
|
||||
|
||||
All of this makes the `@interactive` decorator very useful. But it comes with a
|
||||
few caveats. Notably, decorating a function/method with `@interactive` turns it
|
||||
into a Python [generator](https://wiki.python.org/moin/Generators). The most
|
||||
common issue is that you cannot use `return <value>` from a generator (just an
|
||||
empty `return` works). To return a value from a function/method you have decorated
|
||||
with `@interactive`, you must instead use a special Twisted function
|
||||
`twisted.internet.defer.returnValue`. Evennia also makes this function
|
||||
conveniently available from `evennia.utils`:
|
||||
|
||||
```python
|
||||
from evennia.utils import interactive, returnValue
|
||||
|
||||
@interactive
|
||||
def myfunc():
|
||||
|
||||
# ...
|
||||
result = 10
|
||||
|
||||
# this must be used instead of `return result`
|
||||
returnValue(result)
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Assorted notes
|
||||
|
||||
Overall, be careful with choosing when to use asynchronous calls. It is mainly useful for large
|
||||
administration operations that have no direct influence on the game world (imports and backup
|
||||
operations come to mind). Since there is no telling exactly when an asynchronous call actually ends,
|
||||
using them for in-game commands is to potentially invite confusion and inconsistencies (and very
|
||||
hard-to-reproduce bugs).
|
||||
|
||||
The very first synchronous example above is not *really* correct in the case of Twisted, which is
|
||||
inherently an asynchronous server. Notably you might find that you will *not* see the first `before
|
||||
call ...` text being printed out right away. Instead all texts could end up being delayed until
|
||||
after the long-running process finishes. So all commands will retain their relative order as
|
||||
expected, but they may appear with delays or in groups.
|
||||
|
||||
## Further reading
|
||||
|
||||
Technically, `run_async` is just a very thin and simplified wrapper around a
|
||||
[Twisted Deferred](https://twistedmatrix.com/documents/9.0.0/core/howto/defer.html) object; the
|
||||
wrapper sets
|
||||
up a default errback also if none is supplied. If you know what you are doing there is nothing
|
||||
stopping you from bypassing the utility function, building a more sophisticated callback chain after
|
||||
your own liking.
|
||||
> Technically, `run_async` is just a very thin and simplified wrapper around a [Twisted Deferred](https://twistedmatrix.com/documents/9.0.0/core/howto/defer.html) object; the wrapper sets up a default errback also if none is supplied. If you know what you are doing there is nothing stopping you from bypassing the utility function, building a more sophisticated callback chain after your own liking.
|
||||
|
|
@ -7,10 +7,7 @@ admin tools to handle this, primarily `ban`, `unban`, and `boot`.
|
|||
|
||||
## Creating a ban
|
||||
|
||||
Say we have a troublesome player "YouSuck" - this is a person that refuses common courtesy - an
|
||||
abusive
|
||||
and spammy account that is clearly created by some bored internet hooligan only to cause grief. You
|
||||
have tried to be nice. Now you just want this troll gone.
|
||||
Say we have a troublesome player "YouSuck" - this is a person that refuses common courtesy - an abusive and spammy account that is clearly created by some bored internet hooligan only to cause grief. You have tried to be nice. Now you just want this troll gone.
|
||||
|
||||
### Name ban
|
||||
|
||||
|
|
@ -18,16 +15,13 @@ The easiest recourse is to block the account YouSuck from ever connecting again.
|
|||
|
||||
ban YouSuck
|
||||
|
||||
This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and
|
||||
next time they try to log in with this name the server will not let them!
|
||||
This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and next time they try to log in with this name the server will not let them!
|
||||
|
||||
You can also give a reason so you remember later why this was a good thing (the banned account will
|
||||
never see this)
|
||||
You can also give a reason so you remember later why this was a good thing (the banned account will never see this)
|
||||
|
||||
ban YouSuck:This is just a troll.
|
||||
|
||||
If you are sure this is just a spam account, you might even consider deleting the player account
|
||||
outright:
|
||||
If you are sure this is just a spam account, you might even consider deleting the player account outright:
|
||||
|
||||
accounts/delete YouSuck
|
||||
|
||||
|
|
@ -36,9 +30,7 @@ change your mind you can always remove the block later whereas a deletion is per
|
|||
|
||||
### IP ban
|
||||
|
||||
Just because you block YouSuck's name might not mean the trolling human behind that account gives
|
||||
up. They can just create a new account YouSuckMore and be back at it. One way to make things harder
|
||||
for them is to tell the server to not allow connections from their particular IP address.
|
||||
Just because you block YouSuck's name might not mean the trolling human behind that account gives up. They can just create a new account YouSuckMore and be back at it. One way to make things harder for them is to tell the server to not allow connections from their particular IP address.
|
||||
|
||||
First, when the offending account is online, check which IP address they use. This you can do with
|
||||
the `who` command, which will show you something like this:
|
||||
|
|
@ -46,42 +38,31 @@ the `who` command, which will show you something like this:
|
|||
Account Name On for Idle Room Cmds Host
|
||||
YouSuckMore 01:12 2m 22 212 237.333.0.223
|
||||
|
||||
The "Host" bit is the IP address from which the account is connecting. Use this to define the ban
|
||||
instead of the name:
|
||||
The "Host" bit is the IP address from which the account is connecting. Use this to define the ban instead of the name:
|
||||
|
||||
ban 237.333.0.223
|
||||
|
||||
This will stop YouSuckMore connecting from their computer. Note however that IP address might change
|
||||
easily - either due to how the player's Internet Service Provider operates or by the user simply
|
||||
changing computers. You can make a more general ban by putting asterisks `*` as wildcards for the
|
||||
groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from
|
||||
237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea
|
||||
to put down a ban like this to include any number in that subnet:
|
||||
This will stop YouSuckMore connecting from their computer. Note however that IP address might change easily - either due to how the player's Internet Service Provider operates or by the user simply changing computers. You can make a more general ban by putting asterisks `*` as wildcards for the groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from `237.333.0.223`, `237.333.0.225`, and `237.333.0.256` (only changes in their subnet), it might be an idea to put down a ban like this to include any number in that subnet:
|
||||
|
||||
ban 237.333.0.*
|
||||
|
||||
You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly
|
||||
locked regardless of where they connect from.
|
||||
You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly locked regardless of where they connect from.
|
||||
|
||||
Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be
|
||||
blocking out innocent players who just happen to connect from the same subnet as the offender.
|
||||
Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be blocking out innocent players who just happen to connect from the same subnet as the offender.
|
||||
|
||||
## Booting
|
||||
|
||||
YouSuck is not really noticing all this banning yet though - and won't until having logged out and
|
||||
trying to log back in again. Let's help the troll along.
|
||||
YouSuck is not really noticing all this banning yet though - and won't until having logged out and trying to log back in again. Let's help the troll along.
|
||||
|
||||
boot YouSuck
|
||||
|
||||
Good riddance. You can give a reason for booting too (to be echoed to the player before getting
|
||||
kicked out).
|
||||
Good riddance. You can give a reason for booting too (to be echoed to the player before getting kicked out).
|
||||
|
||||
boot YouSuck:Go troll somewhere else.
|
||||
|
||||
### Lifting a ban
|
||||
|
||||
Use the `unban` (or `ban`) command without any arguments and you will see a list of all currently
|
||||
active bans:
|
||||
Use the `unban` (or `ban`) command without any arguments and you will see a list of all currently active bans:
|
||||
|
||||
Active bans
|
||||
id name/ip date reason
|
||||
|
|
@ -113,32 +94,21 @@ is not what you want in this case.
|
|||
- **unban 34** -- Remove ban with id #34
|
||||
|
||||
- **cboot mychannel = thomas** -- Boot a subscriber from a channel you control
|
||||
- **clock mychannel = control:perm(Admin);listen:all();send:all()** -- Fine control of access to
|
||||
your channel using [lock definitions](../Components/Locks.md).
|
||||
- **clock mychannel = control:perm(Admin);listen:all();send:all()** -- Fine control of access to your channel using [lock definitions](../Components/Locks.md).
|
||||
|
||||
Locking a specific command (like `page`) is accomplished like so:
|
||||
1. Examine the source of the command. [The default `page` command class](
|
||||
https://github.com/evennia/evennia/blob/master/evennia/commands/default/comms.py#L686) has the lock
|
||||
string **"cmd:not pperm(page_banned)"**. This means that unless the player has the 'permission'
|
||||
"page_banned" they can use this command. You can assign any lock string to allow finer customization
|
||||
in your commands. You might look for the value of an [Attribute](../Components/Attributes.md) or [Tag](../Components/Tags.md), your
|
||||
current location etc.
|
||||
2. **perm/account thomas = page_banned** -- Give the account the 'permission' which causes (in this
|
||||
case) the lock to fail.
|
||||
|
||||
- **perm/del/account thomas = page_banned** -- Remove the given permission
|
||||
1. Examine the source of the command. [The default `page` command class]( https://github.com/evennia/evennia/blob/master/evennia/commands/default/comms.py#L686) has the lock string **"cmd:not pperm(page_banned)"**. This means that unless the player has the 'permission' "page_banned" they can use this command. You can assign any lock string to allow finer customization in your commands. You might look for the value of an [Attribute](../Components/Attributes.md) or [Tag](../Components/Tags.md), your current location etc.
|
||||
2. **perm/account thomas = page_banned** -- Give the account the 'permission' which causes (in this case) the lock to fail.
|
||||
|
||||
- **perm/del/account thomas = page_banned** -- Remove the given permission
|
||||
- **tel thomas = jail** -- Teleport a player to a specified location or #dbref
|
||||
- **type thomas = FlowerPot** -- Turn an annoying player into a flower pot (assuming you have a
|
||||
`FlowerPot` typeclass ready)
|
||||
- **type thomas = FlowerPot** -- Turn an annoying player into a flower pot (assuming you have a `FlowerPot` typeclass ready)
|
||||
- **userpassword thomas = fooBarFoo** -- Change a user's password
|
||||
- **accounts/delete thomas** -- Delete a player account (not recommended, use **ban** instead)
|
||||
|
||||
- **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are
|
||||
cached
|
||||
- **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are cached
|
||||
- **time** -- Gives server uptime, runtime, etc
|
||||
- **reload** -- Reloads the server without disconnecting anyone
|
||||
- **reset** -- Restarts the server, kicking all connections
|
||||
- **shutdown** -- Stops the server cold without it auto-starting again
|
||||
- **py** -- Executes raw Python code, allows for direct inspection of the database and account
|
||||
objects on the fly. For advanced users.
|
||||
- **py** -- Executes raw Python code, allows for direct inspection of the database and account objects on the fly. For advanced users.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
# Sending different messages depending on viewpoint and receiver
|
||||
# Messages varying per receiver
|
||||
|
||||
Sending messages to everyong in a location is handled by the
|
||||
[msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method on
|
||||
Sending messages to everyong in a location is handled by the [msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method on
|
||||
all [Objects](../Components/Objects.md). It's most commonly called on rooms.
|
||||
|
||||
```python
|
||||
|
|
@ -19,26 +18,21 @@ room.msg_contents("{anna} walks into the room.",
|
|||
|
||||
Use `exclude=object_or_list_of_object` to skip sending the message one or more targets.
|
||||
|
||||
The advantage of this is that `anna_object.get_display_name(looker)` will be called
|
||||
for every onlooker; this allows the `{anna}` stanza to be different depending on who
|
||||
sees the strings. How this is to work depends on the _stance_ of your game.
|
||||
The advantage of this is that `anna_object.get_display_name(looker)` will be called for every onlooker; this allows the `{anna}` stanza to be different depending on who sees the strings. How this is to work depends on the _stance_ of your game.
|
||||
|
||||
The stance indicates how your game echoes its messages to the player. Knowing how you want to
|
||||
handle the stance is important for a text game. There are two main stances that are usually considered,
|
||||
_Actor stance_ and _Director stance_.
|
||||
handle the stance is important for a text game. There are two main stances that are usually considered, _Actor stance_ and _Director stance_.
|
||||
|
||||
| Stance | You see | Others in the same location see |
|
||||
| --- | --- | --- |
|
||||
| Actor stance | You pick up the stone | Anna picks up the stone |
|
||||
|Director stance | Anna picks up the stone | Anna picks up the stone |
|
||||
|
||||
It's not unheard of to mix the two stances - with commands from the game being told
|
||||
in Actor stance while Director stance is used for complex emoting and roleplaying. One should
|
||||
usually try to be consistent however.
|
||||
It's not unheard of to mix the two stances - with commands from the game being told in Actor stance while Director stance is used for complex emoting and roleplaying. One should usually try to be consistent however.
|
||||
|
||||
## Director Stance
|
||||
|
||||
While not so common as Actor stance, director stance has the advantage of simplicity, particularly
|
||||
While not as common as Actor stance, director stance has the advantage of simplicity, particularly
|
||||
in roleplaying MUDs where longer roleplaying emotes are used. It is also a pretty simple stance to
|
||||
implement technically since everyone sees the same text, regardless of viewpoint.
|
||||
|
||||
|
|
@ -60,8 +54,7 @@ technically, it's also easy to write for the player.
|
|||
|
||||
## Actor Stance
|
||||
|
||||
This means that the game addresses "you" when it does things. In actor stance, whenever you perform
|
||||
an action, you should get a different message than those _observing_ you doing that action.
|
||||
This means that the game addresses "you" when it does things. In actor stance, whenever you perform an action, you should get a different message than those _observing_ you doing that action.
|
||||
|
||||
Tom picks up the gun, whistling to himself.
|
||||
|
||||
|
|
@ -69,16 +62,9 @@ This is what _others_ should see. The player themselves should see this:
|
|||
|
||||
You pick up the gun, whistling to yourself.
|
||||
|
||||
Not only do you need to map "Tom" to "You" above, there are also grammatical differences -
|
||||
"Tom walks" vs "You walk" and "himself" vs "yourself". This is a lot more complex to handle. For a
|
||||
developer making simple "You/Tom pick/picks up the stone" messages, you could in principle hand-craft
|
||||
the strings from every view point, but there's a better way.
|
||||
|
||||
The `msg_contents` method helps by parsing the ingoing string with a
|
||||
[FuncParser functions](../Components/FuncParser.md) with some very specific `$inline-functions`. The inline funcs
|
||||
basically provides you with a mini-language for building _one_ string that will change
|
||||
appropriately depending on who sees it.
|
||||
Not only do you need to map "Tom" to "You" above, there are also grammatical differences - "Tom walks" vs "You walk" and "himself" vs "yourself". This is a lot more complex to handle. For a developer making simple "You/Tom pick/picks up the stone" messages, you could in principle hand-craft the strings from every view point, but there's a better way.
|
||||
|
||||
The `msg_contents` method helps by parsing the ingoing string with a [FuncParser functions](../Components/FuncParser.md) with some very specific `$inline-functions`. The inline funcs basically provides you with a mini-language for building _one_ string that will change appropriately depending on who sees it.
|
||||
|
||||
```python
|
||||
text = "$You() $conj(pick) up the gun, whistling to $pron(yourself)."
|
||||
|
|
@ -93,8 +79,7 @@ These are the inline-functions available:
|
|||
to `picks`). Enter the root form of the verb.
|
||||
- `$pron(pronoun[,options])` - A pronoun is a word you want to use instead of a proper noun, like
|
||||
_him_, _herself_, _its_, _me_, _I_, _their_ and so on. The `options` is a space- or comma-separated
|
||||
set of options to help the system map your pronoun from 1st/2nd person to 3rd person and vice versa.
|
||||
See next section.
|
||||
set of options to help the system map your pronoun from 1st/2nd person to 3rd person and vice versa. See next section.
|
||||
|
||||
### More on $pron()
|
||||
|
||||
|
|
@ -117,15 +102,7 @@ it translates between this table ...
|
|||
| **3rd person neutral** | it | it | its | theirs* | itself |
|
||||
| **3rd person plural** | they | them | their | theirs | themselves |
|
||||
|
||||
> *) The neutral 3rd person possessive pronoun is not actually used in English. We set it to "theirs"
|
||||
> just to have something to show should someone accidentally ask for a neutral possessive pronoun.
|
||||
|
||||
Some mappings are easy. For example, if you write `$pron(yourselves)` then the 3rd-person
|
||||
form is always `themselves`. But because English grammar is the way it is, not all mappings
|
||||
are 1:1. For example, if you write
|
||||
`$pron(you)`, Evennia will not know which 3rd-persion equivalent this should map to - you need to
|
||||
provide more info to help out. This can either be provided as a second space-separated option
|
||||
to `$pron` or the system will try to figure it out on its own.
|
||||
Some mappings are easy. For example, if you write `$pron(yourselves)` then the 3rd-person form is always `themselves`. But because English grammar is the way it is, not all mappings are 1:1. For example, if you write `$pron(you)`, Evennia will not know which 3rd-persion equivalent this should map to - you need to provide more info to help out. This can either be provided as a second space-separated option to `$pron` or the system will try to figure it out on its own.
|
||||
|
||||
- `pronoun_type` - this is one of the columns in the table and can be set as a `$pron` option.
|
||||
|
||||
|
|
@ -176,14 +153,11 @@ to `$pron` or the system will try to figure it out on its own.
|
|||
| `$pron(her, 1)` | I | her | 3rd person -> 1st person |
|
||||
| `$pron(its, 1st)` | my | its | 3rd person -> 1st person |
|
||||
|
||||
|
||||
Note the three last examples - instead of specifying the 2nd person form you
|
||||
can also specify the 3rd-person and do a 'reverse' lookup - you will still see the proper 1st/2nd text.
|
||||
So writing `$pron(her)` instead of `$pron(you, op f)` gives the same result.
|
||||
Note the three last examples - instead of specifying the 2nd person form you can also specify the 3rd-person and do a 'reverse' lookup - you will still see the proper 1st/2nd text. So writing `$pron(her)` instead of `$pron(you, op f)` gives the same result.
|
||||
|
||||
The [$pron inlinefunc api is found here](evennia.utils.funcparser.funcparser_callable_pronoun)
|
||||
|
||||
# Referencing other objects
|
||||
## Referencing other objects
|
||||
|
||||
There is one more inlinefunc understood by `msg_contents`. This can be used natively to spruce up
|
||||
your strings (for both director- and actor stance):
|
||||
|
|
@ -205,16 +179,11 @@ text = "$You() $conj(pick) up the $obj(gun), whistling to $pron(yourself)"
|
|||
|
||||
room.msg_contents(text, from_obj=caller, mapping={"gun": gun_object})
|
||||
```
|
||||
Depending on your game, Tom may now see himself picking up `A rusty old gun`, whereas an onlooker
|
||||
with a high gun smith skill may instead see him picking up `A rare-make Smith & Wesson model 686
|
||||
in poor condition" ...`
|
||||
Depending on your game, Tom may now see himself picking up `A rusty old gun`, whereas an onlooker with a high gun smith skill may instead see him picking up `A rare-make Smith & Wesson model 686 in poor condition" ...`
|
||||
|
||||
# Recog systems and roleplaying
|
||||
## Recog systems and roleplaying
|
||||
|
||||
The `$funcparser` inline functions are very powerful for the game developer, but they may
|
||||
be a bit too much to write for the regular player.
|
||||
|
||||
The [rpsystem contrib](evennia.contrib.rpg.rpsystem) implements a full dynamic emote/pose and recognition
|
||||
system with short-descriptions and disguises. It uses director stance with a custom markup
|
||||
language, like `/me` `/gun` and `/tall man` to refer to players and objects in the location. It can be
|
||||
worth checking out for inspiration.
|
||||
The [rpsystem contrib](evennia.contrib.rpg.rpsystem) implements a full dynamic emote/pose and recognition system with short-descriptions and disguises. It uses director stance with a custom markup language, like `/me` `/gun` and `/tall man` to refer to players and objects in the location. It can be worth checking out for inspiration.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Colors
|
||||
|
||||
*Note that the Documentation does not display colour the way it would look on the screen.*
|
||||
> Note that the Documentation does not display colour the way it would look on the screen.
|
||||
|
||||
Color can be a very useful tool for your game. It can be used to increase readability and make your
|
||||
game more appealing visually.
|
||||
|
|
@ -130,8 +130,11 @@ For a detailed explanation of these caveats, see the [Understanding Color Tags](
|
|||
Tags) tutorial. But most of the time you might be better off to simply avoid `|*` and mark your text
|
||||
manually instead.
|
||||
|
||||
### Xterm256 Colours
|
||||
## Xterm256 Colours
|
||||
|
||||
```{sidebar}
|
||||
See the [Understanding Color Tags](../Howtos/Tutorial-Understanding-Color-Tags.md) tutorial, for more on the use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.
|
||||
```
|
||||
The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background. It can be combined freely with ANSI colors (above), but some ANSI tags don't affect Xterm256 tags.
|
||||
|
||||
While this offers many more possibilities than traditional ANSI colours, be wary that too many text
|
||||
|
|
@ -170,7 +173,3 @@ If you have a client that supports Xterm256, you can use
|
|||
|
||||
to get a table of all the 256 colours and the codes that produce them. If the table looks broken up
|
||||
into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement. You can use the `options` command to see if xterm256 is active for you. This depends on if your client told Evennia what it supports - if not, and you know what your client supports, you may have to activate some features manually.
|
||||
|
||||
## More reading
|
||||
|
||||
There is an [Understanding Color Tags](../Howtos/Tutorial-Understanding-Color-Tags.md) tutorial which expands on the use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.
|
||||
|
|
@ -7,12 +7,19 @@ This documentation cover more over-arching concepts of Evennia, often involving
|
|||
```{toctree}
|
||||
:maxdepth: 2
|
||||
|
||||
Async-Process.md
|
||||
Soft-Code.md
|
||||
Using-MUX-as-a-Standard.md
|
||||
Messagepath.md
|
||||
OOB.md
|
||||
Async-Process.md
|
||||
```
|
||||
|
||||
## Text processing
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
|
||||
Tags-Parsed-By-Evennia.md
|
||||
Change-Message-Per-Receiver.md
|
||||
Internationalization.md
|
||||
Text-Encodings.md
|
||||
```
|
||||
|
||||
## Access
|
||||
|
|
@ -21,10 +28,8 @@ OOB.md
|
|||
:maxdepth: 2
|
||||
|
||||
Multisession-modes.md
|
||||
Building-Permissions.md
|
||||
Guest-Logins.md
|
||||
Guests.md
|
||||
Banning.md
|
||||
|
||||
```
|
||||
|
||||
## Extending the Server
|
||||
|
|
@ -32,31 +37,7 @@ Banning.md
|
|||
```{toctree}
|
||||
:maxdepth: 2
|
||||
|
||||
Custom-Protocols.md
|
||||
Bootstrap-&-Evennia.md
|
||||
New-Models.md
|
||||
Protocols.md
|
||||
Models.md
|
||||
Zones.md
|
||||
|
||||
```
|
||||
|
||||
## Text processing
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
|
||||
Internationalization.md
|
||||
Text-Encodings.md
|
||||
Inline-Tags-and-Functions.md
|
||||
Change-Messages-Per-Receiver.md
|
||||
Clickable-Links.md
|
||||
Colors.md
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Web features
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
|
||||
Web-Features.md
|
||||
```
|
||||
|
||||
```
|
||||
22
docs/source/Concepts/Inline-Functions.md
Normal file
22
docs/source/Concepts/Inline-Functions.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Inline functions
|
||||
|
||||
```{sidebar}
|
||||
For much more information about inline functions, see the [FuncParser](../Components/FuncParser.md) documentation
|
||||
```
|
||||
_Inline functions_, also known as _funcparser functions_ are embedded strings on the form
|
||||
|
||||
$funcname(args, kwargs)
|
||||
|
||||
For example
|
||||
|
||||
> say the answer is $eval(24 * 12)!
|
||||
You say, "the answer is 288!"
|
||||
|
||||
General processing of outgoing strings is disabled by default. To activate inline-function parsing of outgoing strings, add this to your settings file:
|
||||
|
||||
FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED=True
|
||||
|
||||
Inline functions are provided by the [FuncParser](../Components/FuncParser.md). It is enabled in a few other situations:
|
||||
|
||||
- Processing of [Prototypes](../Components/Prototypes.md); these 'prototypefuncs' allow for prototypes whose values change dynamically upon spawning. For example, you would set `{key: '$choice(["Bo", "Anne", "Tom"])'` and spawn a random-named character every time.
|
||||
- Processing of strings to the `msg_contents` method. This allows for [sending different messages depending on who will see them](./Change-Message-Per-Receiver.md).
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# In-text tags parsed by Evennia
|
||||
|
||||
Evennia understands various extra information embedded in text:
|
||||
|
||||
- [Colors](./Colors.md) - Using `|r`, `|n` etc can be used to mark parts of text with a color. The color will
|
||||
become ANSI/XTerm256 color tags for Telnet connections and CSS information for the webclient.
|
||||
- [Clickable links](./Clickable-Links.md) - This allows you to provide a text the user can click to execute an
|
||||
in-game command. This is on the form `|lc command |lt text |le`.
|
||||
- [FuncParser callables](../Components/FuncParser.md) - These are full-fledged function calls on the form `$funcname(args, kwargs)`
|
||||
that lead to calls to Python functions. The parser can be run with different available callables in different circumstances. The parser is run on all outgoing messages if `settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED=True` (disabled by default).
|
||||
|
||||
```{toctree}
|
||||
:hidden"
|
||||
|
||||
Colors.md
|
||||
Clickable-Links.md
|
||||
../Components/FuncParser.md
|
||||
```
|
||||
|
|
@ -1,211 +1,192 @@
|
|||
# Messagepath
|
||||
# The Message path
|
||||
|
||||
```shell
|
||||
> look
|
||||
|
||||
The main functionality of Evennia is to communicate with clients connected to it; a player enters
|
||||
commands or their client queries for a gui update (ingoing data). The server responds or sends data
|
||||
on its own as the game changes (outgoing data). It's important to understand how this flow of
|
||||
information works in Evennia.
|
||||
A Meadow
|
||||
|
||||
## The ingoing message path
|
||||
This is a beautiful meadow. It is full of flowers.
|
||||
|
||||
We'll start by tracing data from the client to the server. Here it is in short:
|
||||
You see: a flower
|
||||
Exits: north, east
|
||||
```
|
||||
|
||||
Client ->
|
||||
PortalSession ->
|
||||
PortalSessionhandler ->
|
||||
(AMP) ->
|
||||
ServerSessionHandler ->
|
||||
ServerSession ->
|
||||
Inputfunc
|
||||
When you send a command like `look` into Evennia - what actually happens? How does that `look` string end up being handled by the `CmdLook` class? What happens when we use e.g. `caller.msg()` to send the message back
|
||||
|
||||
### Client (ingoing)
|
||||
Understanding this flow of data - the _message path_ is important in order to understand how Evennia works.
|
||||
|
||||
The client sends data to Evennia in two ways.
|
||||
## Ingoing message path
|
||||
|
||||
- When first connecting, the client can send data to the server about its
|
||||
capabilities. This is things like "I support xterm256 but not unicode" and is
|
||||
mainly used when a Telnet client connects. This is called a "handshake" and
|
||||
will generally set some flags on the [Portal Session](../Components/Portal-And-Server.md) that
|
||||
are later synced to the Server Session. Since this is not something the player
|
||||
controls, we'll not explore this further here.
|
||||
- The client can send an *inputcommand* to the server. Traditionally this only
|
||||
happens when the player enters text on the command line. But with a custom
|
||||
client GUI, a command could also come from the pressing of a button. Finally
|
||||
the client may send commands based on a timer or some trigger.
|
||||
```
|
||||
Internet│
|
||||
┌─────┐ │ ┌────────┐
|
||||
┌──────┐ │Text │ │ ┌────────────┐ ┌─────────┐ │Command │
|
||||
│Client├────┤JSON ├─┼──►commandtuple├────►Inputfunc├────►DB query│
|
||||
└──────┘ │etc │ │ └────────────┘ └─────────┘ │etc │
|
||||
└─────┘ │ └────────┘
|
||||
│Evennia
|
||||
```
|
||||
|
||||
Exactly how the inputcommand looks when it travels from the client to Evennia
|
||||
depends on the [Protocol](./Custom-Protocols.md) used:
|
||||
- Telnet: A string. If GMCP or MSDP OOB protocols are used, this string will
|
||||
be formatted in a special way, but it's still a raw string. If Telnet SSL is
|
||||
active, the string will be encrypted.
|
||||
- SSH: An encrypted string
|
||||
- Webclient: A JSON-serialized string.
|
||||
### Incoming command tuples
|
||||
|
||||
### Portal Session (ingoing)
|
||||
|
||||
Each client is connected to the game via a *Portal Session*, one per connection. This Session is
|
||||
different depending on the type of connection (telnet, webclient etc) and thus know how to handle
|
||||
that particular data type. So regardless of how the data arrives, the Session will identify the type
|
||||
of the instruction and any arguments it should have. For example, the telnet protocol will figure
|
||||
that anything arriving normally over the wire should be passed on as a "text" type.
|
||||
|
||||
### PortalSessionHandler (ingoing)
|
||||
|
||||
The *PortalSessionhandler* manages all connected Sessions in the Portal. Its `data_in` method
|
||||
(called by each Portal Session) will parse the command names and arguments from the protocols and
|
||||
convert them to a standardized form we call the *inputcommand*:
|
||||
Ingoing data from the client (coming in as raw strings or serialized JSON) is converted by Evennia to a `commandtuple`. Thesa are the same regardless of what client or connection was used. A `commandtuple` is a simple tuple with three elements:
|
||||
|
||||
```python
|
||||
(commandname, (args), {kwargs})
|
||||
(commandname, (args), {kwargs})
|
||||
```
|
||||
|
||||
All inputcommands must have a name, but they may or may not have arguments and keyword arguments -
|
||||
in fact no default inputcommands use kwargs at all. The most common inputcommand is "text", which
|
||||
has the argument the player input on the command line:
|
||||
For the `look`-command (and anything else written by the player), the `text` `commandtuple` is generated:
|
||||
|
||||
```python
|
||||
("text", ("look",), {})
|
||||
("text", ("look",), {})
|
||||
```
|
||||
|
||||
This inputcommand-structure is pickled together with the unique session-id of the Session to which
|
||||
it belongs. This is then sent over the AMP connection.
|
||||
### Inputfuncs
|
||||
|
||||
### ServerSessionHandler (ingoing)
|
||||
|
||||
On the Server side, the AMP unpickles the data and associates the session id with the server-side
|
||||
[Session](../Components/Sessions.md). Data and Session are passed to the server-side `SessionHandler.data_in`. This
|
||||
in turn calls `ServerSession.data_in()`
|
||||
|
||||
### ServerSession (ingoing)
|
||||
|
||||
The method `ServerSession.data_in` is meant to offer a single place to override if they want to
|
||||
examine *all* data passing into the server from the client. It is meant to call the
|
||||
`ssessionhandler.call_inputfuncs` with the (potentially processed) data (so this is technically a
|
||||
sort of detour back to the sessionhandler).
|
||||
|
||||
In `call_inputfuncs`, the inputcommand's name is compared against the names of all the *inputfuncs*
|
||||
registered with the server. The inputfuncs are named the same as the inputcommand they are supposed
|
||||
to handle, so the (default) inputfunc for handling our "look" command is called "text". These are
|
||||
just normal functions and one can plugin new ones by simply putting them in a module where Evennia
|
||||
looks for such functions.
|
||||
|
||||
If a matching inputfunc is found, it will be called with the Session and the inputcommand's
|
||||
arguments:
|
||||
On the Evennia server side, a list of [inputfucs](Inputuncs) are registered. You can add your own by extending `settings.INPUT_FUNC_MODULES`.
|
||||
|
||||
```python
|
||||
text(session, *("look",), **{})
|
||||
inputfunc_commandname(session, *args, **kwargs)
|
||||
```
|
||||
Here the `session` represents the unique client connection this is coming from (that is, it's identifying just _who_ is sending this input).
|
||||
|
||||
If no matching inputfunc is found, an inputfunc named "default" will be tried and if that is also
|
||||
not found, an error will be raised.
|
||||
|
||||
### Inputfunc
|
||||
|
||||
The [Inputfunc](../Components/Inputfuncs.md) must be on the form `func(session, *args, **kwargs)`. An exception is
|
||||
the `default` inputfunc which has form `default(session, cmdname, *args, **kwargs)`, where `cmdname`
|
||||
is the un-matched inputcommand string.
|
||||
|
||||
This is where the message's path diverges, since just what happens next depends on the type of
|
||||
inputfunc was triggered. In the example of sending "look", the inputfunc is named "text". It will
|
||||
pass the argument to the `cmdhandler` which will eventually lead to the `look` command being
|
||||
executed.
|
||||
|
||||
|
||||
## The outgoing message path
|
||||
|
||||
Next let's trace the passage from server to client.
|
||||
|
||||
msg ->
|
||||
ServerSession ->
|
||||
ServerSessionHandler ->
|
||||
(AMP) ->
|
||||
PortalSessionHandler ->
|
||||
PortalSession ->
|
||||
Client
|
||||
|
||||
### msg
|
||||
|
||||
All outgoing messages start in the `msg` method. This is accessible from three places:
|
||||
|
||||
- `Object.msg`
|
||||
- `Account.msg`
|
||||
- `Session.msg`
|
||||
|
||||
The call sign of the `msg` method looks like this:
|
||||
One such inputfunc is named `text`. For sending a `look`, it will be called as
|
||||
```{sidebar}
|
||||
If you know how `*args` and `**kwargs` work in Python, you'll see that this is the same as a call `text(session, "look")`
|
||||
```
|
||||
|
||||
```python
|
||||
msg(text=None, from_obj=None, session=None, options=None, **kwargs)
|
||||
text(session, *("look",), **{})
|
||||
```
|
||||
|
||||
For our purposes, what is important to know is that with the exception of `from_obj`, `session` and
|
||||
`options`, all keywords given to the `msg` method is the name of an *outputcommand* and its
|
||||
arguments. So `text` is actually such a command, taking a string as its argument. The reason `text`
|
||||
sits as the first keyword argument is that it's so commonly used (`caller.msg("Text")` for example).
|
||||
Here are some examples
|
||||
What an `inputfunc` does with this depends. For an [Out-of-band](./OOB.md) instruction, it could fetch the health of a player or tick down some counter.
|
||||
|
||||
```{sidebar} No text parsing happens before this
|
||||
If you send `look here`, the call would be `text(session, *("look here", **{})`. All parsing of the text input happens in the command-parser, after this step.
|
||||
```
|
||||
For the `text` `inputfunc` the Evennia [CommandHandler](../Components/Commands.md) is invoked and the argument is parsed further in order to figure which command was intended.
|
||||
|
||||
In the example of `look`, the `CmdLook` command-class will be invoked. This will retrieve the description of the current location.
|
||||
|
||||
## Outgoing message path
|
||||
|
||||
```
|
||||
Internet│
|
||||
┌─────┐ │
|
||||
┌──────┐ │Text │ │ ┌──────────┐ ┌────────────┐ ┌─────┐
|
||||
│Client◄────┤JSON ├─┼──┤outputfunc◄────┤commandtuple◄───┤msg()│
|
||||
└──────┘ │etc │ │ └──────────┘ └────────────┘ └─────┘
|
||||
└─────┘ │
|
||||
│Evennia
|
||||
```
|
||||
|
||||
### `msg` to outgoing commandtuple
|
||||
|
||||
When the `inputfunc` has finished whatever it is supposed to, the server may or may not decide to return a result (Some types of `inputcommands` may not expect or require a response at all). The server also often sends outgoing messages without any prior matching ingoing data.
|
||||
|
||||
Whenever data needs to be sent "out" of Evennia, we must generalize it into a (now outgoing) `commandtuple` `(commandname, (args), {kwargs})`. This we do with the `msg()` method. For convenience, this methods is available on every major entity, such as `Object.msg()` and `Account.msg()`. They all link back to `Session.msg()`.
|
||||
|
||||
```python
|
||||
msg("Hello!") # using the 'text' outputfunc
|
||||
msg(prompt=f"HP: {HP}, SP: {SP}, MP: {MP}")
|
||||
msg(mycommand=((1,2,3,4), {"foo": "bar"})
|
||||
|
||||
```
|
||||
Note the form of the `mycommand` outputfunction. This explicitly defines the arguments and keyword
|
||||
arguments for the function. In the case of the `text` and `prompt` calls we just specify a string -
|
||||
this works too: The system will convert this into a single argument for us later in the message
|
||||
path.
|
||||
|
||||
> Note: The `msg` method sits on your Object- and Account typeclasses. It means you can easily
|
||||
override `msg` and make custom- or per-object modifications to the flow of data as it passes
|
||||
through.
|
||||
|
||||
### ServerSession (outgoing)
|
||||
|
||||
Nothing is processed on the Session, it just serves as a gathering points for all different `msg`.
|
||||
It immediately passes the data on to ...
|
||||
|
||||
### ServerSessionHandler (outgoing)
|
||||
|
||||
In the *ServerSessionhandler*, the keywords from the `msg` method are collated into one or more
|
||||
*outputcommands* on a standardized form (identical to inputcommands):
|
||||
|
||||
```
|
||||
(commandname, (args), {kwargs})
|
||||
msg(text=None, from_obj=None, session=None, options=None, **kwargs)
|
||||
```
|
||||
|
||||
This will intelligently convert different input to the same form. So `msg("Hello")` will end up as
|
||||
an outputcommand `("text", ("Hello",), {})`.
|
||||
`text` is so common that it is given as the default:
|
||||
|
||||
This is also the point where the [FuncParser](../Components/FuncParser.md)) is applied, depending on the
|
||||
session to receive the data. Said data is pickled together with the Session id then sent over the
|
||||
AMP bridge.
|
||||
```python
|
||||
msg("A meadow\n\nThis is a beautiful meadow...")
|
||||
```
|
||||
|
||||
### PortalSessionHandler (outgoing)
|
||||
This is converted to a `commandtuple` looking like this:
|
||||
```python
|
||||
("text", ("A meadow\n\nThis is a beutiful meadow...",) {})
|
||||
```
|
||||
|
||||
After the AMP connection has unpickled the data and paired the session id to the matching
|
||||
PortalSession, the handler next determines if this Session has a suitable method for handling the
|
||||
outputcommand.
|
||||
The `msg()` method allows you to define the `commandtuple` directly, for whatever outgoing instruction you want to find:
|
||||
|
||||
The situation is analogous to how inputfuncs work, except that protocols are fixed things that don't
|
||||
need a plugin infrastructure like the inputfuncs are handled. So instead of an "outputfunc", the
|
||||
handler looks for methods on the PortalSession with names of the form `send_<commandname>`.
|
||||
```python
|
||||
msg(current_status=(("healthy", "charged"), {"hp": 12, "mp": 20}))
|
||||
```
|
||||
|
||||
For example, the common sending of text expects a PortalSession method `send_text`. This will be
|
||||
called as `send_text(*("Hello",), **{})`. If the "prompt" outputfunction was used, send_prompt is
|
||||
called. In all other cases the `send_default(cmdname, *args, **kwargs)` will be called - this is the
|
||||
case for all client-custom outputcommands, like when wanting to tell the client to update a graphic
|
||||
or play a sound.
|
||||
This will be converted to a `commandtuple` looking like this:
|
||||
|
||||
### PortalSession (outgoing)
|
||||
```python
|
||||
("current_status", ("healthy", "charged"), {"hp": 12, "mp": 20})
|
||||
```
|
||||
|
||||
At this point it is up to the session to convert the command into a form understood by this
|
||||
particular protocol. For telnet, `send_text` will just send the argument as a string (since that is
|
||||
what telnet clients expect when "text" is coming). If `send_default` was called (basically
|
||||
everything that is not traditional text or a prompt), it will pack the data as an GMCP or MSDP
|
||||
command packet if the telnet client supports either (otherwise it won't send at all). If sending to
|
||||
the webclient, the data will get packed into a JSON structure at all times.
|
||||
### outputfuncs
|
||||
|
||||
### Client (outgoing)
|
||||
```{sidebar}
|
||||
`outputfuncs` are tightly coupled to the protocol and you usually don't need to touch them, unless you are adding a new protocol entirely.
|
||||
```
|
||||
Since `msg()` is aware of which [Session](../Components/Sessions.md) to send to, the outgoing `commandtuple` is always end up pointed at the right client.
|
||||
|
||||
Once arrived at the client, the outputcommand is handled in the way supported by the client (or it
|
||||
may be quietly ignored if not). "text" commands will be displayed in the main window while others
|
||||
may trigger changes in the GUI or play a sound etc.
|
||||
Each supported Evennia Protocol (Telnet, SSH, Webclient etc) has their own `outputfunc`, which converts the generic `commandtuple` into a form that particular protocol understands, such as telnet instructions or JSON.
|
||||
|
||||
For telnet (no SSL), the `look` will return over the wire as plain text:
|
||||
|
||||
A meadow\n\nThis is a beautiful meadow...
|
||||
|
||||
When sending to the webclient, the `commandtuple` is converted as serialized JSON, like this:
|
||||
|
||||
'["look", ["A meadow\\n\\nThis is a beautiful meadow..."], {}]'
|
||||
|
||||
This is then sent to the client over the wire. It's then up to the client to interpret and handle the data properly.
|
||||
|
||||
|
||||
## Components along the path
|
||||
|
||||
### Ingoing
|
||||
|
||||
```
|
||||
┌──────┐ ┌─────────────────────────┐
|
||||
│Client│ │ │
|
||||
└──┬───┘ │ ┌────────────────────┐ │
|
||||
│ ┌──────┼─►│ServerSessionHandler│ │
|
||||
┌──────────────────┼──────┐ │ │ └───┬────────────────┘ │
|
||||
│ Portal │ │ │ │ │ │
|
||||
│ ┌─────────▼───┐ │ ┌─┴─┐ │ ┌───▼─────────┐ │
|
||||
│ │PortalSession│ │ │AMP│ │ │ServerSession│ │
|
||||
│ └─────────┬───┘ │ └─┬─┘ │ └───┬─────────┘ │
|
||||
│ │ │ │ │ │ │
|
||||
│ ┌────────────────▼───┐ │ │ │ ┌───▼─────┐ │
|
||||
│ │PortalSessionHandler├──┼──────┘ │ │Inputfunc│ │
|
||||
│ └────────────────────┘ │ │ └─────────┘ │
|
||||
│ │ │ Server │
|
||||
└─────────────────────────┘ └─────────────────────────┘
|
||||
```
|
||||
|
||||
1. Client - sends handshake or commands over the wire. This is received by the Evennia [Portal](../Components/Portal-And-Server.md).
|
||||
2. `PortalSession` represents one client connection. It understands the communiation protocol used. It converts the protocol-specific input to a generic `commandtuple` structure `(cmdname, (args), {kwargs})`.
|
||||
3. `PortalSessionHandler` handles all connections. It pickles the `commandtuple` together with the session-id.
|
||||
4. Pickled data is sent across the `AMP` (Asynchronous Message Protocol) connection to the [Server](Server-And-Portal) part of Evennia.
|
||||
5. `ServerSessionHandler` unpickles the `commandtuple` and matches the session-id to a matching `SessionSession`.
|
||||
6. `ServerSession` represents the session-connection on the Server side. It looks through its registry of [Inputfuncs](../Components/Inputfuncs.md) to find a match.
|
||||
7. The appropriate `Inputfunc` is called with the args/kwargs included in the `commandtuple`. Depending on `Inputfunc`, this could have different effects. For the `text` inputfunc, it fires the [CommandHandler](../Components/Commands.md).
|
||||
|
||||
### Outgoing
|
||||
|
||||
```
|
||||
┌──────┐ ┌─────────────────────────┐
|
||||
│Client│ │ │
|
||||
└──▲───┘ │ ┌────────────────────┐ │
|
||||
│ ┌──────┼──┤ServerSessionHandler│ │
|
||||
┌──────────────────┼──────┐ │ │ └───▲────────────────┘ │
|
||||
│ Portal │ │ │ │ │ │
|
||||
│ ┌─────────┴───┐ │ ┌─┴─┐ │ ┌───┴─────────┐ │
|
||||
│ │PortalSession│ │ │AMP│ │ │ServerSession│ │
|
||||
│ └─────────▲───┘ │ └─┬─┘ │ └───▲─────────┘ │
|
||||
│ │ │ │ │ │ │
|
||||
│ ┌────────────────┴───┐ │ │ │ ┌───┴──────┐ │
|
||||
│ │PortalSessionHandler◄──┼──────┘ │ │msg() call│ │
|
||||
│ └────────────────────┘ │ │ └──────────┘ │
|
||||
│ │ │ Server │
|
||||
└─────────────────────────┘ └─────────────────────────┘
|
||||
```
|
||||
|
||||
1. The `msg()` method is called
|
||||
2. `ServerSession` and in particular `ServerSession.msg()` is the central point through which all `msg()` calls are routed in order to send data to that [Session](../Components/Sessions.md).
|
||||
3. `ServerSessionHandler` converts the `msg` input to a proper `commandtuple` structure `(cmdname, (args), {kwargs})`. It pickles the `commandtuple` together with the session-id.
|
||||
4. Pickled data is sent across across the `AMP` (Asynchronous Message Protocol) connection to the [Portal](Server-And-Portal) part of Evennia.
|
||||
5. `PortalSessionHandler` unpickles the `commandtuple` and matches its session id to a matching `PortalSession`.
|
||||
6. The `PortalSession` is now responsible for converting the generic `commandtuple` to the communication protocol used by that particular connection.
|
||||
7. The Client receives the data and can act on it.
|
||||
|
|
@ -1,155 +1,118 @@
|
|||
# OOB
|
||||
# Out-of-Band messaging
|
||||
|
||||
OOB, or Out-Of-Band, means sending data between Evennia and the user's client without the user
|
||||
prompting it or necessarily being aware that it's being passed. Common uses would be to update
|
||||
client health-bars, handle client button-presses or to display certain tagged text in a different
|
||||
window pane.
|
||||
|
||||
## Briefly on input/outputcommands
|
||||
If you haven't, you should be familiar with the [Messagepath](./Messagepath.md), which describes how a message enters and leaves Evennia and how along the way, all messages are converted to a generic format called a `commandtuple`:
|
||||
|
||||
Inside Evennia, all server-client communication happens in the same way (so plain text is also an
|
||||
'OOB message' as far as Evennia is concerned). The message follows the [Message Path](./Messagepath.md).
|
||||
You should read up on that if you are unfamiliar with it. As the message travels along the path it
|
||||
has a standardized internal form: a tuple with a string, a tuple and a dict:
|
||||
|
||||
("cmdname", (args), {kwargs})
|
||||
|
||||
This is often referred to as an *inputcommand* or *outputcommand*, depending on the direction it's
|
||||
traveling. The end point for an inputcommand, (the 'Evennia-end' of the message path) is a matching
|
||||
[Inputfunc](../Components/Inputfuncs.md). This function is called as `cmdname(session, *args, **kwargs)` where
|
||||
`session` is the Session-source of the command. Inputfuncs can easily be added by the developer to
|
||||
support/map client commands to actions inside Evennia (see the [inputfunc](../Components/Inputfuncs.md) page for more
|
||||
details).
|
||||
|
||||
When a message is outgoing (at the 'Client-end' of the message path) the outputcommand is handled by
|
||||
a matching *Outputfunc*. This is responsible for converting the internal Evennia representation to a
|
||||
form suitable to send over the wire to the Client. Outputfuncs are hard-coded. Which is chosen and
|
||||
how it processes the outgoing data depends on the nature of the client it's connected to. The only
|
||||
time one would want to add new outputfuncs is as part of developing support for a new Evennia
|
||||
[Protocol](./Custom-Protocols.md).
|
||||
(commandname, (args), {kwargs})
|
||||
|
||||
## Sending and receiving an OOB message
|
||||
|
||||
Sending is simple. You just use the normal `msg` method of the object whose session you want to send
|
||||
to. For example in a Command:
|
||||
Sending is simple. You just use the normal `msg` method of the object whose session you want to send to.
|
||||
|
||||
```python
|
||||
caller.msg(cmdname=((args, ...), {key:value, ...}))
|
||||
caller.msg(commandname=((args, ...), {key:value, ...}))
|
||||
```
|
||||
|
||||
A special case is the `text` input/outputfunc. It's so common that it's the default of the `msg`
|
||||
method. So these are equivalent:
|
||||
The keyword becomes the command-name part of the `commandtuple` and the value its `args` and `kwargs` parts. You can also send multiple messages of different `commandname`s at the same time.
|
||||
|
||||
A special case is the `text` call. It's so common that it's the default of the `msg` method. So these are equivalent:
|
||||
|
||||
```python
|
||||
caller.msg("Hello")
|
||||
caller.msg(text="Hello")
|
||||
```
|
||||
|
||||
You don't have to specify the full output/input definition. So for example, if your particular
|
||||
command only needs kwargs, you can skip the `(args)` part. Like in the `text` case you can skip
|
||||
You don't have to specify the full `commandtuple` definition. So for example, if your particular command only needs kwargs, you can skip the `(args)` part. Like in the `text` case you can skip
|
||||
writing the tuple if there is only one arg ... and so on - the input is pretty flexible. If there
|
||||
are no args at all you need to give the empty tuple `msg(cmdname=(,)` (giving `None` would mean a
|
||||
single argument `None`).
|
||||
|
||||
Which commands you can send depends on the client. If the client does not support an explicit OOB
|
||||
protocol (like many old/legacy MUD clients) Evennia can only send `text` to them and will quietly
|
||||
drop any other types of outputfuncs.
|
||||
### Which command-names can I send?
|
||||
|
||||
> Remember that a given message may go to multiple clients with different capabilities. So unless
|
||||
you turn off telnet completely and only rely on the webclient, you should never rely on non-`text`
|
||||
OOB messages always reaching all targets.
|
||||
This depends on the client and protocol. If you use the Evennia [webclient](../Components/Webclient.md), you can modify it to have it support whatever command-names you like.
|
||||
|
||||
[Inputfuncs](../Components/Inputfuncs.md) lists the default inputfuncs available to handle incoming OOB messages. To
|
||||
accept more you need to add more inputfuncs (see that page for more info).
|
||||
Many third-party MUD clients support a range of OOB protocols listed below. If a client does not support a particular OOB instruction/command, Evennia will just send the `text` command to them and quietly drop all other OOB instructions.
|
||||
|
||||
> Note that a given message may go to multiple clients with different capabilities. So unless you turn off telnet completely and only rely on the webclient, you should never rely on non-`text` OOB messages always reaching all targets.
|
||||
|
||||
### Which command-names can I receive
|
||||
|
||||
This is decided by which [Inputfuncs](../Components/Inputfuncs.md) you define. You can extend Evennia's default as you like, but adding your own functions in a module pointed to by `settings.INPUT_FUNC_MODULES`.
|
||||
|
||||
## Supported OOB protocols
|
||||
|
||||
Evennia supports clients using one of the following protocols:
|
||||
|
||||
### Telnet
|
||||
|
||||
By default telnet (and telnet+SSL) supports only the plain `text` outputcommand. Evennia however
|
||||
detects if the Client supports one of two MUD-specific OOB *extensions* to the standard telnet
|
||||
protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the
|
||||
client uses. If the client supports both, GMCP will be used.
|
||||
By default telnet (and telnet+SSL) supports only the plain `text` outputcommand. Evennia detects if the Client supports one of two MUD-specific OOB *extensions* to the standard telnet protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the client uses. If the client supports both, GMCP will be used.
|
||||
|
||||
> Note that for Telnet, `text` has a special status as the "in-band" operation. So the `text`
|
||||
outputcommand sends the `text` argument directly over the wire, without going through the OOB
|
||||
translations described below.
|
||||
> Note that for Telnet, `text` has a special status as the "in-band" operation. So the `text` outputcommand sends the `text` argument directly over the wire, without going through the OOB translations described below.
|
||||
|
||||
#### Telnet + GMCP
|
||||
|
||||
[GMCP](https://www.gammon.com.au/gmcp), the *Generic Mud Communication Protocol* sends data on the
|
||||
form `cmdname + JSONdata`. Here the cmdname is expected to be on the form "Package.Subpackage".
|
||||
There could also be additional Sub-sub packages etc. The names of these 'packages' and 'subpackages'
|
||||
are not that well standardized beyond what individual MUDs or companies have chosen to go with over
|
||||
the years. You can decide on your own package names, but here are what others are using:
|
||||
[GMCP](https://www.gammon.com.au/gmcp), the *Generic Mud Communication Protocol* sends data on the form `cmdname + JSONdata`. Here the cmdname is expected to be on the form "Package.Subpackage". There could also be additional Sub-sub packages etc. The names of these 'packages' and 'subpackages' are not that well standardized beyond what individual MUDs or companies have chosen to go with over the years. You can decide on your own package names, but here are what others are using:
|
||||
|
||||
- [Aardwolf GMCP](https://www.aardwolf.com/wiki/index.php/Clients/GMCP)
|
||||
- [Discworld GMCP](https://discworld.starturtle.net/lpc/playing/documentation.c?path=/concepts/gmcp)
|
||||
- [Avatar GMCP](https://www.outland.org/infusions/wiclear/index.php?title=MUD%20Protocols&lang=en)
|
||||
- [IRE games GMCP](https://nexus.ironrealms.com/GMCP)
|
||||
|
||||
Evennia will translate underscores to `.` and capitalize to fit the specification. So the
|
||||
outputcommand `foo_bar` will become a GMCP command-name `Foo.Bar`. A GMCP command "Foo.Bar" will be
|
||||
come `foo_bar`. To send a GMCP command that turns into an Evennia inputcommand without an
|
||||
underscore, use the `Core` package. So `Core.Cmdname` becomes just `cmdname` in Evennia and vice
|
||||
versa.
|
||||
Evennia will translate underscores to `.` and capitalize to fit the specification. So the outputcommand `foo_bar` will become a GMCP command-name `Foo.Bar`. A GMCP command "Foo.Bar" will be come `foo_bar`. To send a GMCP command that turns into an Evennia inputcommand without an underscore, use the `Core` package. So `Core.Cmdname` becomes just `cmdname` in Evennia and vice versa.
|
||||
|
||||
On the wire, a GMCP instruction for `("cmdname", ("arg",), {})` will look like this:
|
||||
On the wire, the `commandtuple`
|
||||
|
||||
("cmdname", ("arg",), {})
|
||||
|
||||
will be sent over the wire as this GMCP telnet instruction
|
||||
|
||||
IAC SB GMCP "cmdname" "arg" IAC SE
|
||||
|
||||
where all the capitalized words are telnet character constants specified in
|
||||
`evennia/server/portal/telnet_oob.py`. These are parsed/added by the protocol and we don't include
|
||||
these in the listings below.
|
||||
where all the capitalized words are telnet character constants specified in ][evennia/server/portal/telnet_oob](evennia.server.portal/telnet_oob.py). These are parsed/added by the protocol and we don't include these in the listings below.
|
||||
|
||||
Input/Outputfunc | GMCP-Command
|
||||
`[cmd_name, [], {}]` | Cmd.Name
|
||||
`[cmd_name, [arg], {}]` | Cmd.Name arg
|
||||
`[cmd_na_me, [args],{}]` | Cmd.Na.Me [args]
|
||||
`[cmd_name, [], {kwargs}]` | Cmd.Name {kwargs}
|
||||
`[cmdname, [args, {kwargs}]` | Core.Cmdname [[args],{kwargs}]
|
||||
| `commandtuple` | GMCP-Command |
|
||||
| --- | ---|
|
||||
| `(cmd_name, (), {})` | `Cmd.Name` |
|
||||
| `(cmd_name, (arg,), {})` | `Cmd.Name arg` |
|
||||
| `(cmd_na_me, (args,...),{})` | `Cmd.Na.Me [arg, arg...]` |
|
||||
| `(cmd_name, (), {kwargs})` | `Cmd.Name {kwargs}` |
|
||||
| `(cmdname, (arg,), {kwargs})` | `Core.Cmdname [[args],{kwargs}]` |
|
||||
|
||||
Since Evennia already supplies default inputfuncs that don't match the names expected by the most
|
||||
common GMCP implementations we have a few hard-coded mappings for those:
|
||||
Since Evennia already supplies default Inputfuncs that don't match the names expected by the most common GMCP implementations we have a few hard-coded mappings for those:
|
||||
|
||||
GMCP command name | Input/Outputfunc name
|
||||
"Core.Hello" | "client_options"
|
||||
"Core.Supports.Get" | "client_options"
|
||||
"Core.Commands.Get" | "get_inputfuncs"
|
||||
"Char.Value.Get" | "get_value"
|
||||
"Char.Repeat.Update" | "repeat"
|
||||
"Char.Monitor.Update" | "monitor"
|
||||
| GMCP command name | `commandtuple` command name |
|
||||
| --- | --- |
|
||||
| `"Core.Hello"` | `"client_options"` |
|
||||
| `"Core.Supports.Get"` | `"client_options"` |
|
||||
| `"Core.Commands.Get"` | `"get_inputfuncs"` |
|
||||
| `"Char.Value.Get"` | `"get_value"` |
|
||||
| `"Char.Repeat.Update"` | `"repeat"` |
|
||||
| `"Char.Monitor.Update"`| `"monitor"` |
|
||||
|
||||
#### Telnet + MSDP
|
||||
|
||||
[MSDP](http://tintin.sourceforge.net/msdp/), the *Mud Server Data Protocol*, is a competing standard
|
||||
to GMCP. The MSDP protocol page specifies a range of "recommended" available MSDP command names.
|
||||
Evennia does *not* support those - since MSDP doesn't specify a special format for its command names
|
||||
(like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual
|
||||
name.
|
||||
[MSDP](http://tintin.sourceforge.net/msdp/), the *Mud Server Data Protocol*, is a competing standard to GMCP. The MSDP protocol page specifies a range of "recommended" available MSDP command names. Evennia does *not* support those - since MSDP doesn't specify a special format for its command names (like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual name.
|
||||
|
||||
MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports
|
||||
strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs
|
||||
needed. When sending MSDP for `("cmdname", ("arg",), {})` the resulting MSDP instruction will look
|
||||
like this:
|
||||
MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs needed. When sending MSDP for `("cmdname", ("arg",), {})` the resulting MSDP instruction will look like this:
|
||||
|
||||
IAC SB MSDP VAR cmdname VAL arg IAC SE
|
||||
|
||||
The various available MSDP constants like `VAR` (variable), `VAL` (value), `ARRAYOPEN`/`ARRAYCLOSE`
|
||||
and `TABLEOPEN`/`TABLECLOSE` are specified in `evennia/server/portal/telnet_oob`.
|
||||
|
||||
Outputfunc/Inputfunc | MSDP instruction
|
||||
`[cmdname, [], {}]` | VAR cmdname VAL
|
||||
`[cmdname, [arg], {}]` | VAR cmdname VAL arg
|
||||
`[cmdname, [args],{}]` | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE
|
||||
`[cmdname, [], {kwargs}]` | VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE
|
||||
`[cmdname, [args], {kwargs}]` | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE VAR cmdname
|
||||
VAL TABLEOPEN VAR key VAL val ... TABLECLOSE
|
||||
| `commandtuple` | MSDP instruction |
|
||||
| --- | --- |
|
||||
| `(cmdname, (), {})` | `VAR cmdname VAL` |
|
||||
| `(cmdname, (arg,), {})` | `VAR cmdname VAL arg` |
|
||||
| `(cmdname, (arg,...),{})` | `VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE` |
|
||||
| `(cmdname, (), {kwargs})` | `VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE` |
|
||||
| `(cmdname, (args,...), {kwargs})` | `VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE` |
|
||||
|
||||
Observe that `VAR ... VAL` always identifies cmdnames, so if there are multiple arrays/dicts tagged
|
||||
with the same cmdname they will be appended to the args, kwargs of that inputfunc. Vice-versa, a
|
||||
Observe that `VAR ... VAL` always identifies `cmdnames`, so if there are multiple arrays/dicts tagged with the same cmdname they will be appended to the args, kwargs of that inputfunc. Vice-versa, a
|
||||
different `VAR ... VAL` (outside a table) will come out as a second, different command input.
|
||||
|
||||
### SSH
|
||||
|
|
@ -158,10 +121,14 @@ SSH only supports the `text` input/outputcommand.
|
|||
|
||||
### Web client
|
||||
|
||||
Our web client uses pure JSON structures for all its communication, including `text`. This maps
|
||||
directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs. So the
|
||||
same example `("cmdname", ("arg",), {})` will be sent/received as a valid JSON structure
|
||||
Our web client uses pure [JSON](https://en.wikipedia.org/wiki/JSON) structures for all its communication, including `text`. This maps directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs.
|
||||
|
||||
["cmdname, ["arg"], {}]
|
||||
| `commandtuple` | Evennia Webclient JSON |
|
||||
| --- | --- |
|
||||
| `(cmdname, (), {})` | `["cmdname", [], {}]` |
|
||||
| `(cmdname, (arg,), {})` | `["cmdname", [arg], {}]` |
|
||||
| `(cmdname, (arg,...),{})` | `["cmdname", [arg, ...], {})` |
|
||||
| `(cmdname, (), {kwargs})` | `["cmdname", [], {kwargs})` |
|
||||
| `(cmdname, (arg,...), {kwargs})` | `["cmdname", [arg, ...], {kwargs})` |
|
||||
|
||||
Since JSON is native to Javascript, this becomes very easy for the webclient to handle.
|
||||
Since JSON is native to Javascript, this becomes very easy for the webclient to handle.
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
# Soft Code
|
||||
|
||||
|
||||
Softcode is a very simple programming language that was created for in-game development on TinyMUD derivatives such as MUX, PennMUSH, TinyMUSH, and RhostMUSH. The idea is that by providing a stripped down, minimalistic language for in-game use, you can allow quick and easy building and game development to happen without having to learn C/C++. There is an added benefit of not having to have to hand out shell access to all developers, and permissions can be used to alleviate many security problems.
|
||||
|
||||
Writing and installing softcode is done through a MUD client. Thus it is not a formatted language.
|
||||
Each softcode function is a single line of varying size. Some functions can be a half of a page long
|
||||
or more which is obviously not very readable nor (easily) maintainable over time.
|
||||
|
||||
## Examples of Softcode
|
||||
|
||||
Here is a simple 'Hello World!' command:
|
||||
|
||||
```bash
|
||||
@set me=HELLO_WORLD.C:$hello:@pemit %#=Hello World!
|
||||
```
|
||||
|
||||
Pasting this into a MUX/MUSH and typing 'hello' will theoretically yield 'Hello World!', assuming
|
||||
certain flags are not set on your account object.
|
||||
|
||||
Setting attributes is done via `@set`. Softcode also allows the use of the ampersand (`&`) symbol.
|
||||
This shorter version looks like this:
|
||||
|
||||
```bash
|
||||
&HELLO_WORLD.C me=$hello:@pemit %#=Hello World!
|
||||
```
|
||||
|
||||
Perhaps I want to break the Hello World into an attribute which is retrieved when emitting:
|
||||
|
||||
```bash
|
||||
&HELLO_VALUE.D me=Hello World
|
||||
&HELLO_WORLD.C me=$hello:@pemit %#=[v(HELLO_VALUE.D)]
|
||||
```
|
||||
|
||||
The `v()` function returns the `HELLO_VALUE.D` attribute on the object that the command resides
|
||||
(`me`, which is yourself in this case). This should yield the same output as the first example.
|
||||
|
||||
If you are still curious about how Softcode works, take a look at some external resources:
|
||||
|
||||
- https://wiki.tinymux.org/index.php/Softcode
|
||||
- https://www.duh.com/discordia/mushman/man2x1
|
||||
|
||||
## Problems with Softcode
|
||||
|
||||
Softcode is excellent at what it was intended for: *simple things*. It is a great tool for making an interactive object, a room with ambiance, simple global commands, simple economies and coded systems. However, once you start to try to write something like a complex combat system or a higher end economy, you're likely to find yourself buried under a mountain of functions that span multiple objects across your entire code.
|
||||
|
||||
Not to mention, softcode is not an inherently fast language. It is not compiled, it is parsed with each calling of a function. While MUX and MUSH parsers have jumped light years ahead of where they once were they can still stutter under the weight of more complex systems if not designed properly.
|
||||
|
||||
## Changing Times
|
||||
|
||||
Now that starting text-based games is easy and an option for even the most technically inarticulate, new projects are a dime a dozen. People are starting new MUDs every day with varying levels of commitment and ability. Because of this shift from fewer, larger, well-staffed games to a bunch of small, one or two developer games, some of the benefit of softcode fades.
|
||||
|
||||
Softcode is great in that it allows a mid to large sized staff all work on the same game without stepping on one another's toes. As mentioned before, shell access is not necessary to develop a MUX or a MUSH. However, now that we are seeing a lot more small, one or two-man shops, the issue of shell access and stepping on each other's toes is a lot less.
|
||||
|
||||
## Our Solution
|
||||
|
||||
Evennia shuns in-game softcode for on-disk Python modules. Python is a popular, mature and
|
||||
professional programming language. You code it using the conveniences of modern text editors.
|
||||
Evennia developers have access to the entire library of Python modules out there in the wild - not
|
||||
to mention the vast online help resources available. Python code is not bound to one-line functions
|
||||
on objects but complex systems may be organized neatly into real source code modules, sub-modules, or even broken out into entire Python packages as desired.
|
||||
|
||||
So what is *not* included in Evennia is a MUX/MOO-like online player-coding system. Advanced coding in Evennia is primarily intended to be done outside the game, in full-fledged Python modules. Advanced building is best handled by extending Evennia's command system with your own sophisticated building commands. We feel that with a small development team you are better off using a professional source-control system (svn, git, bazaar, mercurial etc) anyway.
|
||||
|
||||
## Your Solution
|
||||
|
||||
Adding advanced and flexible building commands to your game is easy and will probably be enough to satisfy most creative builders. However, if you really, *really* want to offer online coding, there is of course nothing stopping you from adding that to Evennia, no matter our recommendations. You could even re-implement MUX' softcode in Python should you be very ambitious. The [in-game-python](../Contribs/Contrib-Ingame-Python.md) is an optional pseudo-softcode plugin aimed at developers wanting to script their game from inside it.
|
||||
27
docs/source/Concepts/Tags-Parsed-By-Evennia.md
Normal file
27
docs/source/Concepts/Tags-Parsed-By-Evennia.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# In-text tags parsed by Evennia
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
|
||||
Colors.md
|
||||
Clickable-Links.md
|
||||
Inline-Functions.md
|
||||
```
|
||||
|
||||
Evennia will parse various special tags and markers embedded in text and convert it dynamically depending on if the data is going in or out of the server.
|
||||
|
||||
- _Colors_ - Using `|r`, `|n` etc can be used to mark parts of text with a color. The color will
|
||||
become ANSI/XTerm256 color tags for Telnet connections and CSS information for the webclient.
|
||||
```
|
||||
> say Hello, I'm wearing my |rred hat|n today.
|
||||
```
|
||||
- _Clickable links_ - This allows you to provide a text the user can click to execute an
|
||||
in-game command. This is on the form `|lc command |lt text |le`. Clickable links are generally only parsed in the _outgoing_ direction, since if users could provde them, they could be a potential security problem. To activate, `MXP_ENABLED=True` must be added to settings (disabled by default).
|
||||
```
|
||||
py self.msg("This is a |c look |ltclickable 'look' link|le")
|
||||
```
|
||||
- _FuncParser callables_ - These are full-fledged function calls on the form `$funcname(args, kwargs)` that lead to calls to Python functions. The parser can be run with different available callables in different circumstances. The parser is run on all outgoing messages if `settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED=True` (disabled by default).
|
||||
```
|
||||
> say The answer is $eval(40 + 2)!
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# Using MUX as a Standard
|
||||
|
||||
|
||||
Evennia allows for any command syntax. If you like the way DikuMUDs, LPMuds or MOOs handle things, you could emulate that with Evennia. If you are ambitious you could even design a whole new style, perfectly fitting your own dreams of the ideal game.
|
||||
|
||||
We do offer a default however. The default Evennia setup tends to *resemble* [MUX2](https://www.tinymux.org/), and its cousins [PennMUSH](https://www.pennmush.org), [TinyMUSH](https://github.com/TinyMUSH/TinyMUSH/wiki), and [RhostMUSH](http://www.rhostmush.com/). While the reason for this similarity is partly historical, these codebases offer very mature feature sets for administration and building.
|
||||
|
||||
Evennia is *not* a MUX system though. It works very differently in many ways. For example, Evennia
|
||||
deliberately lacks an online softcode language (a policy explained on our [softcode policy
|
||||
page](./Soft-Code.md)). Evennia also does not shy from using its own syntax when deemed appropriate: the
|
||||
MUX syntax has grown organically over a long time and is, frankly, rather arcane in places. All in
|
||||
all the default command syntax should at most be referred to as "MUX-like" or "MUX-inspired".
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
# Web Features
|
||||
|
||||
|
||||
Evennia is its own webserver and hosts a default website and browser webclient.
|
||||
|
||||
## Web site
|
||||
|
||||
The Evennia website is a Django application that ties in with the MUD database. Since the website
|
||||
shares this database you could, for example, tell website visitors how many accounts are logged into
|
||||
the game at the moment, how long the server has been up and any other database information you may
|
||||
want. During development you can access the website by pointing your browser to
|
||||
`http://localhost:4001`.
|
||||
|
||||
> You may also want to set `DEBUG = True` in your settings file for debugging the website. You will
|
||||
then see proper tracebacks in the browser rather than just error codes. Note however that this will
|
||||
*leak memory a lot* (it stores everything all the time) and is *not to be used in production*. It's
|
||||
recommended to only use `DEBUG` for active web development and to turn it off otherwise.
|
||||
|
||||
A Django (and thus Evennia) website basically consists of three parts, a
|
||||
[view](https://docs.djangoproject.com/en/1.9/topics/http/views/) an associated
|
||||
[template](https://docs.djangoproject.com/en/1.9/topics/templates/) and an `urls.py` file. Think of
|
||||
the view as the Python back-end and the template as the HTML files you are served, optionally filled
|
||||
with data from the back-end. The urls file is a sort of mapping that tells Django that if a specific
|
||||
URL is given in the browser, a particular view should be triggered. You are wise to review the
|
||||
Django documentation for details on how to use these components.
|
||||
|
||||
Evennia's default website is located in
|
||||
[evennia/web/website](https://github.com/evennia/evennia/tree/master/evennia/web/website). In this
|
||||
folder you'll find the simple default view as well as subfolders `templates` and `static`. Static
|
||||
files are things like images, CSS files and Javascript.
|
||||
|
||||
### Customizing the Website
|
||||
|
||||
You customize your website from your game directory. In the folder `web` you'll find folders
|
||||
`static`, `templates`, `static_overrides` and `templates_overrides`. The first two of those are
|
||||
populated automatically by Django and used to serve the website. You should not edit anything in
|
||||
them - the change will be lost. To customize the website you'll need to copy the file you want to
|
||||
change from the `web/website/template/` or `web/website/static/ path to the corresponding place
|
||||
under one of `_overrides` directories.
|
||||
|
||||
Example: To override or modify `evennia/web/website/template/website/index.html` you need to
|
||||
add/modify `mygame/web/template_overrides/website/index.html`.
|
||||
|
||||
The detailed description on how to customize the website is best described in tutorial form. See the
|
||||
[Web Tutorial](../Howtos/Web-Changing-Webpage.md) for more information.
|
||||
|
||||
### Overloading Django views
|
||||
|
||||
The Python backend for every HTML page is called a [Django
|
||||
view](https://docs.djangoproject.com/en/1.9/topics/http/views/). A view can do all sorts of
|
||||
functions, but the main one is to update variables data that the page can display, like how your
|
||||
out-of-the-box website will display statistics about number of users and database objects.
|
||||
|
||||
To re-point a given page to a `view.py` of your own, you need to modify `mygame/web/urls.py`. An
|
||||
[URL pattern](https://docs.djangoproject.com/en/1.9/topics/http/urls/) is a [regular
|
||||
expression](https://en.wikipedia.org/wiki/Regular_expression) that you need to enter in the address
|
||||
field of your web browser to get to the page in question. If you put your own URL pattern *before*
|
||||
the default ones, your own view will be used instead. The file `urls.py` even marks where you should
|
||||
put your change.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```python
|
||||
# mygame/web/urls.py
|
||||
|
||||
from django.conf.urls import url, include
|
||||
# default patterns
|
||||
from evennia.web.urls import urlpatterns
|
||||
|
||||
# our own view to use as a replacement
|
||||
from web.myviews import myview
|
||||
|
||||
# custom patterns to add
|
||||
patterns = [
|
||||
# overload the main page view
|
||||
url(r'^', myview, name='mycustomview'),
|
||||
]
|
||||
|
||||
urlpatterns = patterns + urlpatterns
|
||||
|
||||
```
|
||||
|
||||
Django will always look for a list named `urlpatterns` which consists of the results of `url()`
|
||||
calls. It will use the *first* match it finds in this list. Above, we add a new URL redirect from
|
||||
the root of the website. It will now our own function `myview` from a new module
|
||||
`mygame/web/myviews.py`.
|
||||
|
||||
> If our game is found on `http://mygame.com`, the regular expression `"^"` means we just entered
|
||||
`mygame.com` in the address bar. If we had wanted to add a view for `http://mygame.com/awesome`, the
|
||||
regular expression would have been `^/awesome`.
|
||||
|
||||
Look at
|
||||
[evennia/web/website/views.py](https://github.com/evennia/evennia/blob/master/evennia/web/website/views.py#L82)
|
||||
to see the inputs and outputs you must have to define a view. Easiest may be to copy the default
|
||||
file to `mygame/web` to have something to modify and expand on.
|
||||
|
||||
Restart the server and reload the page in the browser - the website will now use your custom view.
|
||||
If there are errors, consider turning on `settings.DEBUG` to see the full tracebacks - in debug mode
|
||||
you will also log all requests in `mygame/server/logs/http_requests.log`.
|
||||
|
||||
## Web client
|
||||
|
||||
|
||||
Evennia comes with a MUD client accessible from a normal web browser. During
|
||||
development you can try it at `http://localhost:4001/webclient`.
|
||||
[See the Webclient page](../Components/Webclient.md) for more details.
|
||||
|
||||
|
||||
## The Django 'Admin' Page
|
||||
|
||||
Django comes with a built-in [admin
|
||||
website](https://docs.djangoproject.com/en/1.10/ref/contrib/admin/). This is accessible by clicking
|
||||
the 'admin' button from your game website. The admin site allows you to see, edit and create objects
|
||||
in your database from a graphical interface.
|
||||
|
||||
The behavior of default Evennia models are controlled by files `admin.py` in the Evennia package.
|
||||
New database models you choose to add yourself (such as in the Web Character View Tutorial) can/will
|
||||
also have `admin.py` files. New models are registered to the admin website by a call of
|
||||
`admin.site.register(model class, admin class)` inside an admin.py file. It is an error to attempt
|
||||
to register a model that has already been registered.
|
||||
|
||||
To overload Evennia's admin files you don't need to modify Evennia itself. To customize you can call
|
||||
`admin.site.unregister(model class)`, then follow that with `admin.site.register` in one of your own
|
||||
admin.py files in a new app that you add.
|
||||
|
||||
## More reading
|
||||
|
||||
Evennia relies on Django for its web features. For details on expanding your web experience, the
|
||||
[Django documentation](https://docs.djangoproject.com/en) or the [Django
|
||||
Book](http://www.djangobook.com/en/2.0/index.html) are the main resources to look into. In Django
|
||||
lingo, the Evennia is a django "project" that consists of Django "applications". For the sake of web
|
||||
implementation, the relevant django "applications" in default Evennia are `web/website` or
|
||||
`web/webclient`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue