mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Doc refactor/renaming
This commit is contained in:
parent
9d8e8d7693
commit
b5b265ec3b
115 changed files with 518 additions and 434 deletions
234
docs/source/Concepts/Async-Process.md
Normal file
234
docs/source/Concepts/Async-Process.md
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
# Async Process
|
||||
|
||||
|
||||
*This is considered an advanced topic.*
|
||||
|
||||
## Synchronous versus Asynchronous
|
||||
|
||||
Most program code operates *synchronously*. This means that each statement in your code gets
|
||||
processed and finishes before the next can begin. This makes for easy-to-understand code. It is also
|
||||
a *requirement* in many cases - a subsequent piece of code often depend on something calculated or
|
||||
defined in a previous statement.
|
||||
|
||||
Consider this piece of code in a traditional Python program:
|
||||
|
||||
```python
|
||||
print("before call ...")
|
||||
long_running_function()
|
||||
print("after call ...")
|
||||
|
||||
```
|
||||
|
||||
When run, this will print `"before call ..."`, after which the `long_running_function` gets to work
|
||||
for however long time. Only once that is done, the system prints `"after call ..."`. Easy and
|
||||
logical to follow. Most of Evennia work in this way and often it's important that commands get
|
||||
executed in the same strict order they were coded.
|
||||
|
||||
Evennia, via Twisted, is a single-process multi-user server. In simple terms this means that it
|
||||
swiftly switches between dealing with player input so quickly that each player feels like they do
|
||||
things at the same time. This is a clever illusion however: If one user, say, runs a command
|
||||
containing that `long_running_function`, *all* other players are effectively forced to wait until it
|
||||
finishes.
|
||||
|
||||
Now, it should be said that on a modern computer system this is rarely an issue. Very few commands
|
||||
run so long that other users notice it. And as mentioned, most of the time you *want* to enforce
|
||||
all commands to occur in strict sequence.
|
||||
|
||||
When delays do become noticeable and you don't care in which order the command actually completes,
|
||||
you can run it *asynchronously*. This makes use of the `run_async()` function in
|
||||
`src/utils/utils.py`:
|
||||
|
||||
```python
|
||||
run_async(function, *args, **kwargs)
|
||||
```
|
||||
|
||||
Where `function` will be called asynchronously with `*args` and `**kwargs`. Example:
|
||||
|
||||
```python
|
||||
from evennia import utils
|
||||
print("before call ...")
|
||||
utils.run_async(long_running_function)
|
||||
print("after call ...")
|
||||
```
|
||||
|
||||
Now, when running this you will find that the program will not wait around for
|
||||
`long_running_function` to finish. In fact you will see `"before call ..."` and `"after call ..."`
|
||||
printed out right away. The long-running function will run in the background and you (and other
|
||||
users) can go on as normal.
|
||||
|
||||
## Customizing asynchronous operation
|
||||
|
||||
A complication with using asynchronous calls is what to do with the result from that call. What if
|
||||
`long_running_function` returns a value that you need? It makes no real sense to put any lines of
|
||||
code after the call to try to deal with the result from `long_running_function` above - as we saw
|
||||
the `"after call ..."` got printed long before `long_running_function` was finished, making that
|
||||
line quite pointless for processing any data from the function. Instead one has to use *callbacks*.
|
||||
|
||||
`utils.run_async` takes reserved kwargs that won't be passed into the long-running function:
|
||||
|
||||
- `at_return(r)` (the *callback*) is called when the asynchronous function (`long_running_function`
|
||||
above) finishes successfully. The argument `r` will then be the return value of that function (or
|
||||
`None`).
|
||||
|
||||
```python
|
||||
def at_return(r):
|
||||
print(r)
|
||||
```
|
||||
|
||||
- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the
|
||||
`at_return` callback.
|
||||
- `at_err(e)` (the *errback*) is called if the asynchronous function fails and raises an exception.
|
||||
This exception is passed to the errback wrapped in a *Failure* object `e`. If you do not supply an
|
||||
errback of your own, Evennia will automatically add one that silently writes errors to the evennia
|
||||
log. An example of an errback is found below:
|
||||
|
||||
```python
|
||||
def at_err(e):
|
||||
print("There was an error:", str(e))
|
||||
```
|
||||
|
||||
- `at_err_kwargs` - an optional dictionary that will be fed as keyword arguments to the `at_err`
|
||||
errback.
|
||||
|
||||
An example of making an asynchronous call from inside a [Command](../Components/Commands) definition:
|
||||
|
||||
```python
|
||||
from evennia import utils, Command
|
||||
|
||||
class CmdAsync(Command):
|
||||
|
||||
key = "asynccommand"
|
||||
|
||||
def func(self):
|
||||
|
||||
def long_running_function():
|
||||
#[... lots of time-consuming code ...]
|
||||
return final_value
|
||||
|
||||
def at_return_function(r):
|
||||
self.caller.msg("The final value is %s" % r)
|
||||
|
||||
def at_err_function(e):
|
||||
self.caller.msg("There was an error: %s" % e)
|
||||
|
||||
# do the async call, setting all callbacks
|
||||
utils.run_async(long_running_function, at_return=at_return_function,
|
||||
at_err=at_err_function)
|
||||
```
|
||||
|
||||
That's it - from here on we can forget about `long_running_function` and go on with what else need
|
||||
to be done. *Whenever* it finishes, the `at_return_function` function will be called and the final
|
||||
value will
|
||||
pop up for us to see. If not we will see an error message.
|
||||
|
||||
## delay
|
||||
|
||||
The `delay` function is a much simpler sibling to `run_async`. It is in fact just a way to delay the
|
||||
execution of a command until a future time. This is equivalent to something like `time.sleep()`
|
||||
except delay is asynchronous while `sleep` would lock the entire server for the duration of the
|
||||
sleep.
|
||||
|
||||
```python
|
||||
from evennia.utils import delay
|
||||
|
||||
# [...]
|
||||
# e.g. inside a Command, where `self.caller` is available
|
||||
def callback(obj):
|
||||
obj.msg("Returning!")
|
||||
delay(10, callback, self.caller)
|
||||
```
|
||||
|
||||
This will delay the execution of the callback for 10 seconds. This function is explored much more in
|
||||
the [Command Duration Tutorial](../Howto/Command-Duration).
|
||||
|
||||
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](http://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.
|
||||
144
docs/source/Concepts/Banning.md
Normal file
144
docs/source/Concepts/Banning.md
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# Banning
|
||||
|
||||
|
||||
Whether due to abuse, blatant breaking of your rules, or some other reason, you will eventually find
|
||||
no other recourse but to kick out a particularly troublesome player. The default command set has
|
||||
admin tools to handle this, primarily `@ban`, `@unban`, and `@boot`.
|
||||
|
||||
## Creating a ban
|
||||
|
||||
Say we have a troublesome player "YouSuck" - this is a person that refuses common courtesy - an
|
||||
abusive
|
||||
and spammy account that is clearly created by some bored internet hooligan only to cause grief. You
|
||||
have tried to be nice. Now you just want this troll gone.
|
||||
|
||||
### Name ban
|
||||
|
||||
The easiest recourse is to block the account YouSuck from ever connecting again.
|
||||
|
||||
@ban YouSuck
|
||||
|
||||
This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and
|
||||
next time they try to log in with this name the server will not let them!
|
||||
|
||||
You can also give a reason so you remember later why this was a good thing (the banned account will
|
||||
never see this)
|
||||
|
||||
@ban YouSuck:This is just a troll.
|
||||
|
||||
If you are sure this is just a spam account, you might even consider deleting the player account
|
||||
outright:
|
||||
|
||||
@delaccount YouSuck
|
||||
|
||||
Generally, banning the name is the easier and safer way to stop the use of an account -- if you
|
||||
change your mind you can always remove the block later whereas a deletion is permanent.
|
||||
|
||||
### IP ban
|
||||
|
||||
Just because you block YouSuck's name might not mean the trolling human behind that account gives
|
||||
up. They can just create a new account YouSuckMore and be back at it. One way to make things harder
|
||||
for them is to tell the server to not allow connections from their particular IP address.
|
||||
|
||||
First, when the offending account is online, check which IP address they use. This you can do with
|
||||
the `who` command, which will show you something like this:
|
||||
|
||||
Account Name On for Idle Room Cmds Host
|
||||
YouSuckMore 01:12 2m 22 212 237.333.0.223
|
||||
|
||||
The "Host" bit is the IP address from which the account is connecting. Use this to define the ban
|
||||
instead of the name:
|
||||
|
||||
@ban 237.333.0.223
|
||||
|
||||
This will stop YouSuckMore connecting from their computer. Note however that IP address might change
|
||||
easily - either due to how the player's Internet Service Provider operates or by the user simply
|
||||
changing computers. You can make a more general ban by putting asterisks `*` as wildcards for the
|
||||
groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from
|
||||
237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea
|
||||
to put down a ban like this to include any number in that subnet:
|
||||
|
||||
@ban 237.333.0.*
|
||||
|
||||
You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly
|
||||
locked regardless of where they connect from.
|
||||
|
||||
Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be
|
||||
blocking out innocent players who just happen to connect from the same subnet as the offender.
|
||||
|
||||
## Booting
|
||||
|
||||
YouSuck is not really noticing all this banning yet though - and won't until having logged out and
|
||||
trying to log back in again. Let's help the troll along.
|
||||
|
||||
@boot YouSuck
|
||||
|
||||
Good riddance. You can give a reason for booting too (to be echoed to the player before getting
|
||||
kicked out).
|
||||
|
||||
@boot YouSuck:Go troll somewhere else.
|
||||
|
||||
### Lifting a ban
|
||||
|
||||
Use the `@unban` (or `@ban`) command without any arguments and you will see a list of all currently
|
||||
active bans:
|
||||
|
||||
Active bans
|
||||
id name/ip date reason
|
||||
1 yousuck Fri Jan 3 23:00:22 2020 This is just a Troll.
|
||||
2 237.333.0.* Fri Jan 3 23:01:03 2020 YouSuck's IP.
|
||||
|
||||
Use the `id` from this list to find out which ban to lift.
|
||||
|
||||
@unban 2
|
||||
|
||||
Cleared ban 2: 237.333.0.*
|
||||
|
||||
## Summary of abuse-handling tools
|
||||
|
||||
Below are other useful commands for dealing with annoying players.
|
||||
|
||||
- **who** -- (as admin) Find the IP of a account. Note that one account can be connected to from
|
||||
multiple IPs depending on what you allow in your settings.
|
||||
- **examine/account thomas** -- Get all details about an account. You can also use `*thomas` to get
|
||||
the account. If not given, you will get the *Object* thomas if it exists in the same location, which
|
||||
is not what you want in this case.
|
||||
- **boot thomas** -- Boot all sessions of the given account name.
|
||||
- **boot 23** -- Boot one specific client session/IP by its unique id.
|
||||
- **ban** -- List all bans (listed with ids)
|
||||
- **ban thomas** -- Ban the user with the given account name
|
||||
- **ban/ip `134.233.2.111`** -- Ban by IP
|
||||
- **ban/ip `134.233.2.*`** -- Widen IP ban
|
||||
- **ban/ip `134.233.*.*`** -- Even wider IP ban
|
||||
- **unban 34** -- Remove ban with id #34
|
||||
|
||||
- **cboot mychannel = thomas** -- Boot a subscriber from a channel you control
|
||||
- **clock mychannel = control:perm(Admin);listen:all();send:all()** -- Fine control of access to
|
||||
your channel using [lock definitions](../Components/Locks).
|
||||
|
||||
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) or [Tag](../Components/Tags), your
|
||||
current location etc.
|
||||
2. **perm/account thomas = page_banned** -- Give the account the 'permission' which causes (in this
|
||||
case) the lock to fail.
|
||||
|
||||
- **perm/del/account thomas = page_banned** -- Remove the given permission
|
||||
|
||||
- **tel thomas = jail** -- Teleport a player to a specified location or #dbref
|
||||
- **type thomas = FlowerPot** -- Turn an annoying player into a flower pot (assuming you have a
|
||||
`FlowerPot` typeclass ready)
|
||||
- **userpassword thomas = fooBarFoo** -- Change a user's password
|
||||
- **delaccount thomas** -- Delete a player account (not recommended, use **ban** instead)
|
||||
|
||||
- **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are
|
||||
cached
|
||||
- **time** -- Gives server uptime, runtime, etc
|
||||
- **reload** -- Reloads the server without disconnecting anyone
|
||||
- **reset** -- Restarts the server, kicking all connections
|
||||
- **shutdown** -- Stops the server cold without it auto-starting again
|
||||
- **py** -- Executes raw Python code, allows for direct inspection of the database and account
|
||||
objects on the fly. For advanced users.
|
||||
101
docs/source/Concepts/Bootstrap-&-Evennia.md
Normal file
101
docs/source/Concepts/Bootstrap-&-Evennia.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# 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.
|
||||
72
docs/source/Concepts/Building-Permissions.md
Normal file
72
docs/source/Concepts/Building-Permissions.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Building Permissions
|
||||
|
||||
|
||||
*OBS: This gives only a brief introduction to the access system. Locks and permissions are fully
|
||||
detailed* [here](../Components/Locks).
|
||||
|
||||
## 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) 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.
|
||||
9
docs/source/Concepts/Command-System.md
Normal file
9
docs/source/Concepts/Command-System.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Command System
|
||||
|
||||
- [Commands](../Components/Commands)
|
||||
- [Command Sets](../Components/Command-Sets)
|
||||
- [Command Auto-help](../Components/Help-System#command-auto-help-system)
|
||||
|
||||
See also:
|
||||
- [Default Command Help](../Components/Default-Command-Help)
|
||||
- [Adding Command Tutorial](../Howto/Starting/Part1/Adding-Commands)
|
||||
29
docs/source/Concepts/Concepts-Overview.md
Normal file
29
docs/source/Concepts/Concepts-Overview.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Core Concepts
|
||||
|
||||
This documentation cover more over-arching concepts of Evennia, often involving many [Core Components](../Components/Components-Overview) acting together.
|
||||
|
||||
## General concepts
|
||||
|
||||
- [Asynchronous processing](./Async-Process)
|
||||
- [On Soft-Code](./Soft-Code)
|
||||
- [Using MUX as standard for default commands](./Using-MUX-as-a-Standard)
|
||||
|
||||
## Access
|
||||
|
||||
- [Permissions](./Building-Permissions)
|
||||
- [Banning](./Banning)
|
||||
|
||||
## Extending the Server
|
||||
- [Custom Protocols](./Custom-Protocols)
|
||||
- [Bootstrap](./Bootstrap-&-Evennia)
|
||||
- [Creating new models](./New-Models)
|
||||
|
||||
## Text processing
|
||||
|
||||
- [Change the language of the server](./Internationalization)
|
||||
- [Server text-encoding](./Text-Encodings)
|
||||
- [Text tags](./TextTags)
|
||||
|
||||
## Web features
|
||||
|
||||
- [Web features](./Web-Features)
|
||||
239
docs/source/Concepts/Custom-Protocols.md
Normal file
239
docs/source/Concepts/Custom-Protocols.md
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
# Custom Protocols
|
||||
|
||||
|
||||
*Note: This is considered an advanced topic and is mostly of interest to users planning to implement
|
||||
their own custom client protocol.*
|
||||
|
||||
|
||||
A [PortalSession](../Components/Sessions#Portal-and-Server-Sessions) is the basic data object representing an
|
||||
external
|
||||
connection to the Evennia [Portal](../Components/Portal-And-Server) -- usually a human player running a mud client
|
||||
of some kind. The way they connect (the language the player's client and Evennia use to talk to
|
||||
each other) is called the connection *Protocol*. The most common such protocol for MUD:s is the
|
||||
*Telnet* protocol. All Portal Sessions are stored and managed by the Portal's *sessionhandler*.
|
||||
|
||||
It's technically sometimes hard to separate the concept of *PortalSession* from the concept of
|
||||
*Protocol* since both depend heavily on the other (they are often created as the same class). When
|
||||
data flows through this part of the system, this is how it goes
|
||||
|
||||
```
|
||||
# In the Portal
|
||||
You <->
|
||||
Protocol + PortalSession <->
|
||||
PortalSessionHandler <->
|
||||
(AMP) <->
|
||||
ServerSessionHandler <->
|
||||
ServerSession <->
|
||||
InputFunc
|
||||
```
|
||||
|
||||
(See the [Message Path](./Messagepath) for the bigger picture of how data flows through Evennia). The
|
||||
parts that needs to be customized to make your own custom protocol is the `Protocol + PortalSession`
|
||||
(which translates between data coming in/out over the wire to/from Evennia internal representation)
|
||||
as well as the `InputFunc` (which handles incoming data).
|
||||
|
||||
## Adding custom Protocols
|
||||
|
||||
Evennia has a plugin-system that add the protocol as a new "service" to the application.
|
||||
|
||||
Take a look at `evennia/server/portal/portal.py`, notably the sections towards the end of that file.
|
||||
These are where the various in-built services like telnet, ssh, webclient etc are added to the
|
||||
Portal (there is an equivalent but shorter list in `evennia/server/server.py`).
|
||||
|
||||
To add a new service of your own (for example your own custom client protocol) to the Portal or
|
||||
Server, look at `mygame/server/conf/server_services_plugins` and `portal_services_plugins`. By
|
||||
default Evennia will look into these modules to find plugins. If you wanted to have it look for more
|
||||
modules, you could do the following:
|
||||
|
||||
```python
|
||||
# add to the Server
|
||||
SERVER_SERVICES_PLUGIN_MODULES.append('server.conf.my_server_plugins')
|
||||
# or, if you want to add to the Portal
|
||||
PORTAL_SERVICES_PLUGIN_MODULES.append('server.conf.my_portal_plugins')
|
||||
```
|
||||
|
||||
When adding a new connection you'll most likely only need to add new things to the
|
||||
`PORTAL_SERVICES_PLUGIN_MODULES`.
|
||||
|
||||
This module can contain whatever you need to define your protocol, but it *must* contain a function
|
||||
`start_plugin_services(app)`. This is called by the Portal as part of its upstart. The function
|
||||
`start_plugin_services` must contain all startup code the server need. The `app` argument is a
|
||||
reference to the Portal/Server application itself so the custom service can be added to it. The
|
||||
function should not return anything.
|
||||
|
||||
This is how it looks:
|
||||
|
||||
```python
|
||||
# mygame/server/conf/portal_services_plugins.py
|
||||
|
||||
# here the new Portal Twisted protocol is defined
|
||||
class MyOwnFactory( ... ):
|
||||
[...]
|
||||
|
||||
# some configs
|
||||
MYPROC_ENABLED = True # convenient off-flag to avoid having to edit settings all the time
|
||||
MY_PORT = 6666
|
||||
|
||||
def start_plugin_services(portal):
|
||||
"This is called by the Portal during startup"
|
||||
if not MYPROC_ENABLED:
|
||||
return
|
||||
# output to list this with the other services at startup
|
||||
print(" myproc: %s" % MY_PORT)
|
||||
|
||||
# some setup (simple example)
|
||||
factory = MyOwnFactory()
|
||||
my_service = internet.TCPServer(MY_PORT, factory)
|
||||
# all Evennia services must be uniquely named
|
||||
my_service.setName("MyService")
|
||||
# add to the main portal application
|
||||
portal.services.addService(my_service)
|
||||
```
|
||||
|
||||
Once the module is defined and targeted in settings, just reload the server and your new
|
||||
protocol/services should start with the others.
|
||||
|
||||
## Writing your own Protocol
|
||||
|
||||
Writing a stable communication protocol from scratch is not something we'll cover here, it's no
|
||||
trivial task. The good news is that Twisted offers implementations of many common protocols, ready
|
||||
for adapting.
|
||||
|
||||
Writing a protocol implementation in Twisted usually involves creating a class inheriting from an
|
||||
already existing Twisted protocol class and from `evennia.server.session.Session` (multiple
|
||||
inheritance), then overloading the methods that particular protocol uses to link them to the
|
||||
Evennia-specific inputs.
|
||||
|
||||
Here's a example to show the concept:
|
||||
|
||||
```python
|
||||
# In module that we'll later add to the system through PORTAL_SERVICE_PLUGIN_MODULES
|
||||
|
||||
# pseudo code
|
||||
from twisted.something import TwistedClient
|
||||
# this class is used both for Portal- and Server Sessions
|
||||
from evennia.server.session import Session
|
||||
|
||||
from evennia.server.portal.portalsessionhandler import PORTAL_SESSIONS
|
||||
|
||||
class MyCustomClient(TwistedClient, Session):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.sessionhandler = PORTAL_SESSIONS
|
||||
|
||||
# these are methods we must know that TwistedClient uses for
|
||||
# communication. Name and arguments could vary for different Twisted protocols
|
||||
def onOpen(self, *args, **kwargs):
|
||||
# let's say this is called when the client first connects
|
||||
|
||||
# we need to init the session and connect to the sessionhandler. The .factory
|
||||
# is available through the Twisted parents
|
||||
|
||||
client_address = self.getClientAddress() # get client address somehow
|
||||
|
||||
self.init_session("mycustom_protocol", client_address, self.factory.sessionhandler)
|
||||
self.sessionhandler.connect(self)
|
||||
|
||||
def onClose(self, reason, *args, **kwargs):
|
||||
# called when the client connection is dropped
|
||||
# link to the Evennia equivalent
|
||||
self.disconnect(reason)
|
||||
|
||||
def onMessage(self, indata, *args, **kwargs):
|
||||
# called with incoming data
|
||||
# convert as needed here
|
||||
self.data_in(data=indata)
|
||||
|
||||
def sendMessage(self, outdata, *args, **kwargs):
|
||||
# called to send data out
|
||||
# modify if needed
|
||||
super().sendMessage(self, outdata, *args, **kwargs)
|
||||
|
||||
# these are Evennia methods. They must all exist and look exactly like this
|
||||
# The above twisted-methods call them and vice-versa. This connects the protocol
|
||||
# the Evennia internals.
|
||||
|
||||
def disconnect(self, reason=None):
|
||||
"""
|
||||
Called when connection closes.
|
||||
This can also be called directly by Evennia when manually closing the connection.
|
||||
Do any cleanups here.
|
||||
"""
|
||||
self.sessionhandler.disconnect(self)
|
||||
|
||||
def at_login(self):
|
||||
"""
|
||||
Called when this session authenticates by the server (if applicable)
|
||||
"""
|
||||
|
||||
def data_in(self, **kwargs):
|
||||
"""
|
||||
Data going into the server should go through this method. It
|
||||
should pass data into `sessionhandler.data_in`. THis will be called
|
||||
by the sessionhandler with the data it gets from the approrpriate
|
||||
send_* method found later in this protocol.
|
||||
"""
|
||||
self.sessionhandler.data_in(self, text=kwargs['data'])
|
||||
|
||||
def data_out(self, **kwargs):
|
||||
"""
|
||||
Data going out from the server should go through this method. It should
|
||||
hand off to the protocol's send method, whatever it's called.
|
||||
"""
|
||||
# we assume we have a 'text' outputfunc
|
||||
self.onMessage(kwargs['text'])
|
||||
|
||||
# 'outputfuncs' are defined as `send_<outputfunc_name>`. From in-code, they are called
|
||||
# with `msg(outfunc_name=<data>)`.
|
||||
|
||||
def send_text(self, txt, *args, **kwargs):
|
||||
"""
|
||||
Send text, used with e.g. `session.msg(text="foo")`
|
||||
"""
|
||||
# we make use of the
|
||||
self.data_out(text=txt)
|
||||
|
||||
def send_default(self, cmdname, *args, **kwargs):
|
||||
"""
|
||||
Handles all outputfuncs without an explicit `send_*` method to handle them.
|
||||
"""
|
||||
self.data_out(**{cmdname: str(args)})
|
||||
|
||||
```
|
||||
The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to
|
||||
the Evennia-specific methods.
|
||||
|
||||
### Sending data out
|
||||
|
||||
To send data out through this protocol, you'd need to get its Session and then you could e.g.
|
||||
|
||||
```python
|
||||
session.msg(text="foo")
|
||||
```
|
||||
|
||||
The message will pass through the system such that the sessionhandler will dig out the session and
|
||||
check if it has a `send_text` method (it has). It will then pass the "foo" into that method, which
|
||||
in our case means sending "foo" across the network.
|
||||
|
||||
### Receiving data
|
||||
|
||||
Just because the protocol is there, does not mean Evennia knows what to do with it. An
|
||||
[Inputfunc](../Components/Inputfuncs) must exist to receive it. In the case of the `text` input exemplified above,
|
||||
Evennia alredy handles this input - it will parse it as a Command name followed by its inputs. So
|
||||
handle that you need to simply add a cmdset with commands on your receiving Session (and/or the
|
||||
Object/Character it is puppeting). If not you may need to add your own Inputfunc (see the
|
||||
[Inputfunc](../Components/Inputfuncs) page for how to do this.
|
||||
|
||||
These might not be as clear-cut in all protocols, but the principle is there. These four basic
|
||||
components - however they are accessed - links to the *Portal Session*, which is the actual common
|
||||
interface between the different low-level protocols and Evennia.
|
||||
|
||||
## Assorted notes
|
||||
|
||||
To take two examples, Evennia supports the *telnet* protocol as well as *webclient*, via ajax or
|
||||
websockets. You'll find that whereas telnet is a textbook example of a Twisted protocol as seen
|
||||
above, the ajax protocol looks quite different due to how it interacts with the
|
||||
webserver through long-polling (comet) style requests. All the necessary parts
|
||||
mentioned above are still there, but by necessity implemented in very different
|
||||
ways.
|
||||
29
docs/source/Concepts/Guest-Logins.md
Normal file
29
docs/source/Concepts/Guest-Logins.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Guest Logins
|
||||
|
||||
|
||||
Evennia supports *guest logins* out of the box. A guest login is an anonymous, low-access account
|
||||
and can be useful if you want users to have a chance to try out your game without committing to
|
||||
creating a real account.
|
||||
|
||||
Guest accounts are turned off by default. To activate, add this to your `game/settings.py` file:
|
||||
|
||||
GUEST_ENABLED = True
|
||||
|
||||
Henceforth users can use `connect guest` (in the default command set) to login with a guest account.
|
||||
You may need to change your [Connection Screen](../Components/Connection-Screen) to inform them of this
|
||||
possibility. Guest accounts work differently from normal accounts - they are automatically *deleted*
|
||||
whenever the user logs off or the server resets (but not during a reload). They are literally re-
|
||||
usable throw-away accounts.
|
||||
|
||||
You can add a few more variables to your `settings.py` file to customize your guests:
|
||||
|
||||
- `BASE_GUEST_TYPECLASS` - the python-path to the default [typeclass](../Components/Typeclasses) for guests.
|
||||
Defaults to `"typeclasses.accounts.Guest"`.
|
||||
- `PERMISSION_GUEST_DEFAULT` - [permission level](../Components/Locks) for guest accounts. Defaults to `"Guests"`,
|
||||
which is the lowest permission level in the hierarchy.
|
||||
- `GUEST_START_LOCATION` - the `#dbref` to the starting location newly logged-in guests should
|
||||
appear at. Defaults to `"#2` (Limbo).
|
||||
- `GUEST_HOME` - guest home locations. Defaults to Limbo as well.
|
||||
- `GUEST_LIST` - this is a list holding the possible guest names to use when entering the game. The
|
||||
length of this list also sets how many guests may log in at the same time. By default this is a list
|
||||
of nine names from `"Guest1"` to `"Guest9"`.
|
||||
88
docs/source/Concepts/Internationalization.md
Normal file
88
docs/source/Concepts/Internationalization.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Internationalization
|
||||
|
||||
|
||||
*Internationalization* (often abbreviated *i18n* since there are 18 characters between the first "i"
|
||||
and the last "n" in that word) allows Evennia's core server to return texts in other languages than
|
||||
English - without anyone having to edit the source code. Take a look at the `locale` directory of
|
||||
the Evennia installation, there you will find which languages are currently supported.
|
||||
|
||||
## Changing server language
|
||||
|
||||
Change language by adding the following to your `mygame/server/conf/settings.py` file:
|
||||
|
||||
```python
|
||||
USE_I18N = True
|
||||
LANGUAGE_CODE = 'en'
|
||||
```
|
||||
|
||||
Here `'en'` should be changed to the abbreviation for one of the supported languages found in
|
||||
`locale/`. Restart the server to activate i18n. The two-character international language codes are
|
||||
found [here](http://www.science.co.il/Language/Codes.asp).
|
||||
|
||||
> Windows Note: If you get errors concerning `gettext` or `xgettext` on Windows, see the [Django
|
||||
documentation](https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#gettext-on-windows). A
|
||||
self-installing and up-to-date version of gettext for Windows (32/64-bit) is available on
|
||||
[Github](https://github.com/mlocati/gettext-iconv-windows).
|
||||
|
||||
## Translating Evennia
|
||||
|
||||
> **Important Note:** Evennia offers translations of hard-coded strings in the server, things like
|
||||
"Connection closed" or "Server restarted", strings that end users will see and which game devs are
|
||||
not supposed to change on their own. Text you see in the log file or on the command line (like error
|
||||
messages) are generally *not* translated (this is a part of Python).
|
||||
|
||||
> In addition, text in default Commands and in default Typeclasses will *not* be translated by
|
||||
switching *i18n* language. To translate Commands and Typeclass hooks you must overload them in your
|
||||
game directory and translate their returns to the language you want. This is because from Evennia's
|
||||
perspective, adding *i18n* code to commands tend to add complexity to code that is *meant* to be
|
||||
changed anyway. One of the goals of Evennia is to keep the user-changeable code as clean and easy-
|
||||
to-read as possible.
|
||||
|
||||
If you cannot find your language in `evennia/locale/` it's because noone has translated it yet.
|
||||
Alternatively you might have the language but find the translation bad ... You are welcome to help
|
||||
improve the situation!
|
||||
|
||||
To start a new translation you need to first have cloned the Evennia repositry with GIT and
|
||||
activated a python virtualenv as described on the [Setup Quickstart](../Setup/Setup-Quickstart) page. You now
|
||||
need to `cd` to the `evennia/` directory. This is *not* your created game folder but the main
|
||||
Evennia library folder. If you see a folder `locale/` then you are in the right place. From here you
|
||||
run:
|
||||
|
||||
evennia makemessages <language-code>
|
||||
|
||||
where `<language-code>` is the [two-letter locale code](http://www.science.co.il/Language/Codes.asp)
|
||||
for the language you want, like 'sv' for Swedish or 'es' for Spanish. After a moment it will tell
|
||||
you the language has been processed. For instance:
|
||||
|
||||
evennia makemessages sv
|
||||
|
||||
If you started a new language a new folder for that language will have emerged in the `locale/`
|
||||
folder. Otherwise the system will just have updated the existing translation with eventual new
|
||||
strings found in the server. Running this command will not overwrite any existing strings so you can
|
||||
run it as much as you want.
|
||||
|
||||
> Note: in Django, the `makemessages` command prefixes the locale name by the `-l` option (`...
|
||||
makemessages -l sv` for instance). This syntax is not allowed in Evennia, due to the fact that `-l`
|
||||
is the option to tail log files. Hence, `makemessages` doesn't use the `-l` flag.
|
||||
|
||||
Next head to `locale/<language-code>/LC_MESSAGES` and edit the `**.po` file you find there. You can
|
||||
edit this with a normal text editor but it is easiest if you use a special po-file editor from the
|
||||
web (search the web for "po editor" for many free alternatives).
|
||||
|
||||
The concept of translating is simple, it's just a matter of taking the english strings you find in
|
||||
the `**.po` file and add your language's translation best you can. The `**.po` format (and many
|
||||
supporting editors) allow you to mark translations as "fuzzy". This tells the system (and future
|
||||
translators) that you are unsure about the translation, or that you couldn't find a translation that
|
||||
exactly matched the intention of the original text. Other translators will see this and might be
|
||||
able to improve it later.
|
||||
Finally, you need to compile your translation into a more efficient form. Do so from the `evennia`
|
||||
folder
|
||||
again:
|
||||
|
||||
evennia compilemessages
|
||||
|
||||
This will go through all languages and create/update compiled files (`**.mo`) for them. This needs
|
||||
to be done whenever a `**.po` file is updated.
|
||||
|
||||
When you are done, send the `**.po` and `*.mo` file to the Evennia developer list (or push it into
|
||||
your own repository clone) so we can integrate your translation into Evennia!
|
||||
211
docs/source/Concepts/Messagepath.md
Normal file
211
docs/source/Concepts/Messagepath.md
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
# Messagepath
|
||||
|
||||
|
||||
The main functionality of Evennia is to communicate with clients connected to it; a player enters
|
||||
commands or their client queries for a gui update (ingoing data). The server responds or sends data
|
||||
on its own as the game changes (outgoing data). It's important to understand how this flow of
|
||||
information works in Evennia.
|
||||
|
||||
## The ingoing message path
|
||||
|
||||
We'll start by tracing data from the client to the server. Here it is in short:
|
||||
|
||||
Client ->
|
||||
PortalSession ->
|
||||
PortalSessionhandler ->
|
||||
(AMP) ->
|
||||
ServerSessionHandler ->
|
||||
ServerSession ->
|
||||
Inputfunc
|
||||
|
||||
### Client (ingoing)
|
||||
|
||||
The client sends data to Evennia in two ways.
|
||||
|
||||
- When first connecting, the client can send data to the server about its
|
||||
capabilities. This is things like "I support xterm256 but not unicode" and is
|
||||
mainly used when a Telnet client connects. This is called a "handshake" and
|
||||
will generally set some flags on the [Portal Session](../Components/Portal-And-Server) that
|
||||
are later synced to the Server Session. Since this is not something the player
|
||||
controls, we'll not explore this further here.
|
||||
- The client can send an *inputcommand* to the server. Traditionally this only
|
||||
happens when the player enters text on the command line. But with a custom
|
||||
client GUI, a command could also come from the pressing of a button. Finally
|
||||
the client may send commands based on a timer or some trigger.
|
||||
|
||||
Exactly how the inputcommand looks when it travels from the client to Evennia
|
||||
depends on the [Protocol](./Custom-Protocols) used:
|
||||
- Telnet: A string. If GMCP or MSDP OOB protocols are used, this string will
|
||||
be formatted in a special way, but it's still a raw string. If Telnet SSL is
|
||||
active, the string will be encrypted.
|
||||
- SSH: An encrypted string
|
||||
- Webclient: A JSON-serialized string.
|
||||
|
||||
### Portal Session (ingoing)
|
||||
|
||||
Each client is connected to the game via a *Portal Session*, one per connection. This Session is
|
||||
different depending on the type of connection (telnet, webclient etc) and thus know how to handle
|
||||
that particular data type. So regardless of how the data arrives, the Session will identify the type
|
||||
of the instruction and any arguments it should have. For example, the telnet protocol will figure
|
||||
that anything arriving normally over the wire should be passed on as a "text" type.
|
||||
|
||||
### PortalSessionHandler (ingoing)
|
||||
|
||||
The *PortalSessionhandler* manages all connected Sessions in the Portal. Its `data_in` method
|
||||
(called by each Portal Session) will parse the command names and arguments from the protocols and
|
||||
convert them to a standardized form we call the *inputcommand*:
|
||||
|
||||
```python
|
||||
(commandname, (args), {kwargs})
|
||||
```
|
||||
|
||||
All inputcommands must have a name, but they may or may not have arguments and keyword arguments -
|
||||
in fact no default inputcommands use kwargs at all. The most common inputcommand is "text", which
|
||||
has the argument the player input on the command line:
|
||||
|
||||
```python
|
||||
("text", ("look",), {})
|
||||
```
|
||||
|
||||
This inputcommand-structure is pickled together with the unique session-id of the Session to which
|
||||
it belongs. This is then sent over the AMP connection.
|
||||
|
||||
### ServerSessionHandler (ingoing)
|
||||
|
||||
On the Server side, the AMP unpickles the data and associates the session id with the server-side
|
||||
[Session](../Components/Sessions). Data and Session are passed to the server-side `SessionHandler.data_in`. This
|
||||
in turn calls `ServerSession.data_in()`
|
||||
|
||||
### ServerSession (ingoing)
|
||||
|
||||
The method `ServerSession.data_in` is meant to offer a single place to override if they want to
|
||||
examine *all* data passing into the server from the client. It is meant to call the
|
||||
`ssessionhandler.call_inputfuncs` with the (potentially processed) data (so this is technically a
|
||||
sort of detour back to the sessionhandler).
|
||||
|
||||
In `call_inputfuncs`, the inputcommand's name is compared against the names of all the *inputfuncs*
|
||||
registered with the server. The inputfuncs are named the same as the inputcommand they are supposed
|
||||
to handle, so the (default) inputfunc for handling our "look" command is called "text". These are
|
||||
just normal functions and one can plugin new ones by simply putting them in a module where Evennia
|
||||
looks for such functions.
|
||||
|
||||
If a matching inputfunc is found, it will be called with the Session and the inputcommand's
|
||||
arguments:
|
||||
|
||||
```python
|
||||
text(session, *("look",), **{})
|
||||
```
|
||||
|
||||
If no matching inputfunc is found, an inputfunc named "default" will be tried and if that is also
|
||||
not found, an error will be raised.
|
||||
|
||||
### Inputfunc
|
||||
|
||||
The [Inputfunc](../Components/Inputfuncs) must be on the form `func(session, *args, **kwargs)`. An exception is
|
||||
the `default` inputfunc which has form `default(session, cmdname, *args, **kwargs)`, where `cmdname`
|
||||
is the un-matched inputcommand string.
|
||||
|
||||
This is where the message's path diverges, since just what happens next depends on the type of
|
||||
inputfunc was triggered. In the example of sending "look", the inputfunc is named "text". It will
|
||||
pass the argument to the `cmdhandler` which will eventually lead to the `look` command being
|
||||
executed.
|
||||
|
||||
|
||||
## The outgoing message path
|
||||
|
||||
Next let's trace the passage from server to client.
|
||||
|
||||
msg ->
|
||||
ServerSession ->
|
||||
ServerSessionHandler ->
|
||||
(AMP) ->
|
||||
PortalSessionHandler ->
|
||||
PortalSession ->
|
||||
Client
|
||||
|
||||
### msg
|
||||
|
||||
All outgoing messages start in the `msg` method. This is accessible from three places:
|
||||
|
||||
- `Object.msg`
|
||||
- `Account.msg`
|
||||
- `Session.msg`
|
||||
|
||||
The call sign of the `msg` method looks like this:
|
||||
|
||||
```python
|
||||
msg(text=None, from_obj=None, session=None, options=None, **kwargs)
|
||||
```
|
||||
|
||||
For our purposes, what is important to know is that with the exception of `from_obj`, `session` and
|
||||
`options`, all keywords given to the `msg` method is the name of an *outputcommand* and its
|
||||
arguments. So `text` is actually such a command, taking a string as its argument. The reason `text`
|
||||
sits as the first keyword argument is that it's so commonly used (`caller.msg("Text")` for example).
|
||||
Here are some examples
|
||||
|
||||
```python
|
||||
msg("Hello!") # using the 'text' outputfunc
|
||||
msg(prompt="HP:%i, SP: %i, MP: %i" % (HP, SP, MP))
|
||||
msg(mycommand=((1,2,3,4), {"foo": "bar"})
|
||||
|
||||
```
|
||||
Note the form of the `mycommand` outputfunction. This explicitly defines the arguments and keyword
|
||||
arguments for the function. In the case of the `text` and `prompt` calls we just specify a string -
|
||||
this works too: The system will convert this into a single argument for us later in the message
|
||||
path.
|
||||
|
||||
> Note: The `msg` method sits on your Object- and Account typeclasses. It means you can easily
|
||||
override `msg` and make custom- or per-object modifications to the flow of data as it passes
|
||||
through.
|
||||
|
||||
### ServerSession (outgoing)
|
||||
|
||||
Nothing is processed on the Session, it just serves as a gathering points for all different `msg`.
|
||||
It immediately passes the data on to ...
|
||||
|
||||
### ServerSessionHandler (outgoing)
|
||||
|
||||
In the *ServerSessionhandler*, the keywords from the `msg` method are collated into one or more
|
||||
*outputcommands* on a standardized form (identical to inputcommands):
|
||||
|
||||
```
|
||||
(commandname, (args), {kwargs})
|
||||
```
|
||||
|
||||
This will intelligently convert different input to the same form. So `msg("Hello")` will end up as
|
||||
an outputcommand `("text", ("Hello",), {})`.
|
||||
|
||||
This is also the point where [Inlinefuncs](./TextTags#inline-functions) are parsed, depending on the
|
||||
session to receive the data. Said data is pickled together with the Session id then sent over the
|
||||
AMP bridge.
|
||||
|
||||
### PortalSessionHandler (outgoing)
|
||||
|
||||
After the AMP connection has unpickled the data and paired the session id to the matching
|
||||
PortalSession, the handler next determines if this Session has a suitable method for handling the
|
||||
outputcommand.
|
||||
|
||||
The situation is analogous to how inputfuncs work, except that protocols are fixed things that don't
|
||||
need a plugin infrastructure like the inputfuncs are handled. So instead of an "outputfunc", the
|
||||
handler looks for methods on the PortalSession with names of the form `send_<commandname>`.
|
||||
|
||||
For example, the common sending of text expects a PortalSession method `send_text`. This will be
|
||||
called as `send_text(*("Hello",), **{})`. If the "prompt" outputfunction was used, send_prompt is
|
||||
called. In all other cases the `send_default(cmdname, *args, **kwargs)` will be called - this is the
|
||||
case for all client-custom outputcommands, like when wanting to tell the client to update a graphic
|
||||
or play a sound.
|
||||
|
||||
### PortalSession (outgoing)
|
||||
|
||||
At this point it is up to the session to convert the command into a form understood by this
|
||||
particular protocol. For telnet, `send_text` will just send the argument as a string (since that is
|
||||
what telnet clients expect when "text" is coming). If `send_default` was called (basically
|
||||
everything that is not traditional text or a prompt), it will pack the data as an GMCP or MSDP
|
||||
command packet if the telnet client supports either (otherwise it won't send at all). If sending to
|
||||
the webclient, the data will get packed into a JSON structure at all times.
|
||||
|
||||
### Client (outgoing)
|
||||
|
||||
Once arrived at the client, the outputcommand is handled in the way supported by the client (or it
|
||||
may be quietly ignored if not). "text" commands will be displayed in the main window while others
|
||||
may trigger changes in the GUI or play a sound etc.
|
||||
3
docs/source/Concepts/Multisession-modes.md
Normal file
3
docs/source/Concepts/Multisession-modes.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Multisession modes
|
||||
|
||||
TODO: This is covered in various places before.
|
||||
264
docs/source/Concepts/New-Models.md
Normal file
264
docs/source/Concepts/New-Models.md
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
# New Models
|
||||
|
||||
*Note: This is considered an advanced topic.*
|
||||
|
||||
Evennia offers many convenient ways to store object data, such as via Attributes or Scripts. This is
|
||||
sufficient for most use cases. But if you aim to build a large stand-alone system, trying to squeeze
|
||||
your storage requirements into those may be more complex than you bargain for. Examples may be to
|
||||
store guild data for guild members to be able to change, tracking the flow of money across a game-
|
||||
wide economic system or implement other custom game systems that requires the storage of custom data
|
||||
in a quickly accessible way. Whereas [Tags](../Components/Tags) or [Scripts](../Components/Scripts) can handle many situations,
|
||||
sometimes things may be easier to handle by adding your own database model.
|
||||
|
||||
## Overview of database tables
|
||||
|
||||
SQL-type databases (which is what Evennia supports) are basically highly optimized systems for
|
||||
retrieving text stored in tables. A table may look like this
|
||||
|
||||
```
|
||||
id | db_key | db_typeclass_path | db_permissions ...
|
||||
------------------------------------------------------------------
|
||||
1 | Griatch | evennia.DefaultCharacter | Developers ...
|
||||
2 | Rock | evennia.DefaultObject | None ...
|
||||
```
|
||||
|
||||
Each line is considerably longer in your database. Each column is referred to as a "field" and every
|
||||
row is a separate object. You can check this out for yourself. If you use the default sqlite3
|
||||
database, go to your game folder and run
|
||||
|
||||
evennia dbshell
|
||||
|
||||
You will drop into the database shell. While there, try:
|
||||
|
||||
sqlite> .help # view help
|
||||
|
||||
sqlite> .tables # view all tables
|
||||
|
||||
# show the table field names for objects_objectdb
|
||||
sqlite> .schema objects_objectdb
|
||||
|
||||
# show the first row from the objects_objectdb table
|
||||
sqlite> select * from objects_objectdb limit 1;
|
||||
|
||||
sqlite> .exit
|
||||
|
||||
Evennia uses [Django](https://docs.djangoproject.com), which abstracts away the database SQL
|
||||
manipulation and allows you to search and manipulate your database entirely in Python. Each database
|
||||
table is in Django represented by a class commonly called a *model* since it describes the look of
|
||||
the table. In Evennia, Objects, Scripts, Channels etc are examples of Django models that we then
|
||||
extend and build on.
|
||||
|
||||
## Adding a new database table
|
||||
|
||||
Here is how you add your own database table/models:
|
||||
|
||||
1. In Django lingo, we will create a new "application" - a subsystem under the main Evennia program.
|
||||
For this example we'll call it "myapp". Run the following (you need to have a working Evennia
|
||||
running before you do this, so make sure you have run the steps in [Setup Quickstart](Getting-
|
||||
Started) first):
|
||||
|
||||
cd mygame/world
|
||||
evennia startapp myapp
|
||||
|
||||
1. A new folder `myapp` is created. "myapp" will also be the name (the "app label") from now on. We
|
||||
chose to put it in the `world/` subfolder here, but you could put it in the root of your `mygame` if
|
||||
that makes more sense.
|
||||
1. The `myapp` folder contains a few empty default files. What we are
|
||||
interested in for now is `models.py`. In `models.py` you define your model(s). Each model will be a
|
||||
table in the database. See the next section and don't continue until you have added the models you
|
||||
want.
|
||||
1. You now need to tell Evennia that the models of your app should be a part of your database
|
||||
scheme. Add this line to your `mygame/server/conf/settings.py`file (make sure to use the path where
|
||||
you put `myapp` and don't forget the comma at the end of the tuple):
|
||||
|
||||
```
|
||||
INSTALLED_APPS = INSTALLED_APPS + ("world.myapp", )
|
||||
```
|
||||
|
||||
1. From `mygame/`, run
|
||||
|
||||
evennia makemigrations myapp
|
||||
evennia migrate
|
||||
|
||||
This will add your new database table to the database. If you have put your game under version
|
||||
control (if not, [you should](../Coding/Version-Control)), don't forget to `git add myapp/*` to add all items
|
||||
to version control.
|
||||
|
||||
## Defining your models
|
||||
|
||||
A Django *model* is the Python representation of a database table. It can be handled like any other
|
||||
Python class. It defines *fields* on itself, objects of a special type. These become the "columns"
|
||||
of the database table. Finally, you create new instances of the model to add new rows to the
|
||||
database.
|
||||
|
||||
We won't describe all aspects of Django models here, for that we refer to the vast [Django
|
||||
documentation](https://docs.djangoproject.com/en/2.2/topics/db/models/) on the subject. Here is a
|
||||
(very) brief example:
|
||||
|
||||
```python
|
||||
from django.db import models
|
||||
|
||||
class MyDataStore(models.Model):
|
||||
"A simple model for storing some data"
|
||||
db_key = models.CharField(max_length=80, db_index=True)
|
||||
db_category = models.CharField(max_length=80, null=True, blank=True)
|
||||
db_text = models.TextField(null=True, blank=True)
|
||||
# we need this one if we want to be
|
||||
# able to store this in an Evennia Attribute!
|
||||
db_date_created = models.DateTimeField('date created', editable=False,
|
||||
auto_now_add=True, db_index=True)
|
||||
```
|
||||
|
||||
We create four fields: two character fields of limited length and one text field which has no
|
||||
maximum length. Finally we create a field containing the current time of us creating this object.
|
||||
|
||||
> The `db_date_created` field, with exactly this name, is *required* if you want to be able to store
|
||||
instances of your custom model in an Evennia [Attribute](../Components/Attributes). It will automatically be set
|
||||
upon creation and can after that not be changed. Having this field will allow you to do e.g.
|
||||
`obj.db.myinstance = mydatastore`. If you know you'll never store your model instances in Attributes
|
||||
the `db_date_created` field is optional.
|
||||
|
||||
You don't *have* to start field names with `db_`, this is an Evennia convention. It's nevertheless
|
||||
recommended that you do use `db_`, partly for clarity and consistency with Evennia (if you ever want
|
||||
to share your code) and partly for the case of you later deciding to use Evennia's
|
||||
`SharedMemoryModel` parent down the line.
|
||||
|
||||
The field keyword `db_index` creates a *database index* for this field, which allows quicker
|
||||
lookups, so it's recommended to put it on fields you know you'll often use in queries. The
|
||||
`null=True` and `blank=True` keywords means that these fields may be left empty or set to the empty
|
||||
string without the database complaining. There are many other field types and keywords to define
|
||||
them, see django docs for more info.
|
||||
|
||||
Similar to using [django-admin](https://docs.djangoproject.com/en/2.2/howto/legacy-databases/) you
|
||||
are able to do `evennia inspectdb` to get an automated listing of model information for an existing
|
||||
database. As is the case with any model generating tool you should only use this as a starting
|
||||
point for your models.
|
||||
|
||||
## Creating a new model instance
|
||||
|
||||
To create a new row in your table, you instantiate the model and then call its `save()` method:
|
||||
|
||||
```python
|
||||
from evennia.myapp import MyDataStore
|
||||
|
||||
new_datastore = MyDataStore(db_key="LargeSword",
|
||||
db_category="weapons",
|
||||
db_text="This is a huge weapon!")
|
||||
# this is required to actually create the row in the database!
|
||||
new_datastore.save()
|
||||
|
||||
```
|
||||
|
||||
Note that the `db_date_created` field of the model is not specified. Its flag `at_now_add=True`
|
||||
makes sure to set it to the current date when the object is created (it can also not be changed
|
||||
further after creation).
|
||||
|
||||
When you update an existing object with some new field value, remember that you have to save the
|
||||
object afterwards, otherwise the database will not update:
|
||||
|
||||
```python
|
||||
my_datastore.db_key = "Larger Sword"
|
||||
my_datastore.save()
|
||||
```
|
||||
|
||||
Evennia's normal models don't need to explicitly save, since they are based on `SharedMemoryModel`
|
||||
rather than the raw django model. This is covered in the next section.
|
||||
|
||||
## Using the `SharedMemoryModel` parent
|
||||
|
||||
Evennia doesn't base most of its models on the raw `django.db.models` but on the Evennia base model
|
||||
`evennia.utils.idmapper.models.SharedMemoryModel`. There are two main reasons for this:
|
||||
|
||||
1. Ease of updating fields without having to explicitly call `save()`
|
||||
2. On-object memory persistence and database caching
|
||||
|
||||
The first (and least important) point means that as long as you named your fields `db_*`, Evennia
|
||||
will automatically create field wrappers for them. This happens in the model's
|
||||
[Metaclass](http://en.wikibooks.org/wiki/Python_Programming/Metaclasses) so there is no speed
|
||||
penalty for this. The name of the wrapper will be the same name as the field, minus the `db_`
|
||||
prefix. So the `db_key` field will have a wrapper property named `key`. You can then do:
|
||||
|
||||
```python
|
||||
my_datastore.key = "Larger Sword"
|
||||
```
|
||||
|
||||
and don't have to explicitly call `save()` afterwards. The saving also happens in a more efficient
|
||||
way under the hood, updating only the field rather than the entire model using django optimizations.
|
||||
Note that if you were to manually add the property or method `key` to your model, this will be used
|
||||
instead of the automatic wrapper and allows you to fully customize access as needed.
|
||||
|
||||
To explain the second and more important point, consider the following example using the default
|
||||
Django model parent:
|
||||
|
||||
```python
|
||||
shield = MyDataStore.objects.get(db_key="SmallShield")
|
||||
shield.cracked = True # where cracked is not a database field
|
||||
```
|
||||
|
||||
And then later:
|
||||
|
||||
```python
|
||||
shield = MyDataStore.objects.get(db_key="SmallShield")
|
||||
print(shield.cracked) # error!
|
||||
```
|
||||
|
||||
The outcome of that last print statement is *undefined*! It could *maybe* randomly work but most
|
||||
likely you will get an `AttributeError` for not finding the `cracked` property. The reason is that
|
||||
`cracked` doesn't represent an actual field in the database. It was just added at run-time and thus
|
||||
Django don't care about it. When you retrieve your shield-match later there is *no* guarantee you
|
||||
will get back the *same Python instance* of the model where you defined `cracked`, even if you
|
||||
search for the same database object.
|
||||
|
||||
Evennia relies heavily on on-model handlers and other dynamically created properties. So rather than
|
||||
using the vanilla Django models, Evennia uses `SharedMemoryModel`, which levies something called
|
||||
*idmapper*. The idmapper caches model instances so that we will always get the *same* instance back
|
||||
after the first lookup of a given object. Using idmapper, the above example would work fine and you
|
||||
could retrieve your `cracked` property at any time - until you rebooted when all non-persistent data
|
||||
goes.
|
||||
|
||||
Using the idmapper is both more intuitive and more efficient *per object*; it leads to a lot less
|
||||
reading from disk. The drawback is that this system tends to be more memory hungry *overall*. So if
|
||||
you know that you'll *never* need to add new properties to running instances or know that you will
|
||||
create new objects all the time yet rarely access them again (like for a log system), you are
|
||||
probably better off making "plain" Django models rather than using `SharedMemoryModel` and its
|
||||
idmapper.
|
||||
|
||||
To use the idmapper and the field-wrapper functionality you just have to have your model classes
|
||||
inherit from `evennia.utils.idmapper.models.SharedMemoryModel` instead of from the default
|
||||
`django.db.models.Model`:
|
||||
|
||||
```python
|
||||
from evennia.utils.idmapper.models import SharedMemoryModel
|
||||
|
||||
class MyDataStore(SharedMemoryModel):
|
||||
# the rest is the same as before, but db_* is important; these will
|
||||
# later be settable as .key, .category, .text ...
|
||||
db_key = models.CharField(max_length=80, db_index=True)
|
||||
db_category = models.CharField(max_length=80, null=True, blank=True)
|
||||
db_text = models.TextField(null=True, blank=True)
|
||||
db_date_created = models.DateTimeField('date created', editable=False,
|
||||
auto_now_add=True, db_index=True)
|
||||
```
|
||||
|
||||
## Searching for your models
|
||||
|
||||
To search your new custom database table you need to use its database *manager* to build a *query*.
|
||||
Note that even if you use `SharedMemoryModel` as described in the previous section, you have to use
|
||||
the actual *field names* in the query, not the wrapper name (so `db_key` and not just `key`).
|
||||
|
||||
```python
|
||||
from world.myapp import MyDataStore
|
||||
|
||||
# get all datastore objects exactly matching a given key
|
||||
matches = MyDataStore.objects.filter(db_key="Larger Sword")
|
||||
# get all datastore objects with a key containing "sword"
|
||||
# and having the category "weapons" (both ignoring upper/lower case)
|
||||
matches2 = MyDataStore.objects.filter(db_key__icontains="sword",
|
||||
db_category__iequals="weapons")
|
||||
# show the matching data (e.g. inside a command)
|
||||
for match in matches2:
|
||||
self.caller.msg(match.db_text)
|
||||
```
|
||||
|
||||
See the [Django query documentation](https://docs.djangoproject.com/en/2.2/topics/db/queries/) for a
|
||||
lot more information about querying the database.
|
||||
170
docs/source/Concepts/OOB.md
Normal file
170
docs/source/Concepts/OOB.md
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
# OOB
|
||||
|
||||
OOB, or Out-Of-Band, means sending data between Evennia and the user's client without the user
|
||||
prompting it or necessarily being aware that it's being passed. Common uses would be to update
|
||||
client health-bars, handle client button-presses or to display certain tagged text in a different
|
||||
window pane.
|
||||
|
||||
## Briefly on input/outputcommands
|
||||
|
||||
Inside Evennia, all server-client communication happens in the same way (so plain text is also an
|
||||
'OOB message' as far as Evennia is concerned). The message follows the [Message Path](./Messagepath).
|
||||
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). 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) 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).
|
||||
|
||||
## Sending and receiving an OOB message
|
||||
|
||||
Sending is simple. You just use the normal `msg` method of the object whose session you want to send
|
||||
to. For example in a Command:
|
||||
|
||||
```python
|
||||
caller.msg(cmdname=((args, ...), {key:value, ...}))
|
||||
```
|
||||
|
||||
A special case is the `text` input/outputfunc. It's so common that it's the default of the `msg`
|
||||
method. So these are equivalent:
|
||||
|
||||
```python
|
||||
caller.msg("Hello")
|
||||
caller.msg(text="Hello")
|
||||
```
|
||||
|
||||
You don't have to specify the full output/input definition. So for example, if your particular
|
||||
command only needs kwargs, you can skip the `(args)` part. Like in the `text` case you can skip
|
||||
writing the tuple if there is only one arg ... and so on - the input is pretty flexible. If there
|
||||
are no args at all you need to give the empty tuple `msg(cmdname=(,)` (giving `None` would mean a
|
||||
single argument `None`).
|
||||
|
||||
Which commands you can send depends on the client. If the client does not support an explicit OOB
|
||||
protocol (like many old/legacy MUD clients) Evennia can only send `text` to them and will quietly
|
||||
drop any other types of outputfuncs.
|
||||
|
||||
> Remember that a given message may go to multiple clients with different capabilities. So unless
|
||||
you turn off telnet completely and only rely on the webclient, you should never rely on non-`text`
|
||||
OOB messages always reaching all targets.
|
||||
|
||||
[Inputfuncs](../Components/Inputfuncs) lists the default inputfuncs available to handle incoming OOB messages. To
|
||||
accept more you need to add more inputfuncs (see that page for more info).
|
||||
|
||||
## Supported OOB protocols
|
||||
|
||||
Evennia supports clients using one of the following protocols:
|
||||
|
||||
### Telnet
|
||||
|
||||
By default telnet (and telnet+SSL) supports only the plain `text` outputcommand. Evennia however
|
||||
detects if the Client supports one of two MUD-specific OOB *extensions* to the standard telnet
|
||||
protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the
|
||||
client uses. If the client supports both, GMCP will be used.
|
||||
|
||||
> Note that for Telnet, `text` has a special status as the "in-band" operation. So the `text`
|
||||
outputcommand sends the `text` argument directly over the wire, without going through the OOB
|
||||
translations described below.
|
||||
|
||||
#### Telnet + GMCP
|
||||
|
||||
[GMCP](http://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](http://www.aardwolf.com/wiki/index.php/Clients/GMCP)
|
||||
- [Discworld GMCP](http://discworld.starturtle.net/lpc/playing/documentation.c?path=/concepts/gmcp)
|
||||
- [Avatar GMCP](http://www.outland.org/infusions/wiclear/index.php?title=MUD%20Protocols&lang=en)
|
||||
- [IRE games GMCP](http://nexus.ironrealms.com/GMCP)
|
||||
|
||||
Evennia will translate underscores to `.` and capitalize to fit the specification. So the
|
||||
outputcommand `foo_bar` will become a GMCP command-name `Foo.Bar`. A GMCP command "Foo.Bar" will be
|
||||
come `foo_bar`. To send a GMCP command that turns into an Evennia inputcommand without an
|
||||
underscore, use the `Core` package. So `Core.Cmdname` becomes just `cmdname` in Evennia and vice
|
||||
versa.
|
||||
|
||||
On the wire, a GMCP instruction for `("cmdname", ("arg",), {})` will look like this:
|
||||
|
||||
IAC SB GMCP "cmdname" "arg" IAC SE
|
||||
|
||||
where all the capitalized words are telnet character constants specified in
|
||||
`evennia/server/portal/telnet_oob.py`. These are parsed/added by the protocol and we don't include
|
||||
these in the listings below.
|
||||
|
||||
Input/Outputfunc | GMCP-Command
|
||||
------------------
|
||||
`[cmd_name, [], {}]` | Cmd.Name
|
||||
`[cmd_name, [arg], {}]` | Cmd.Name arg
|
||||
`[cmd_na_me, [args],{}]` | Cmd.Na.Me [args]
|
||||
`[cmd_name, [], {kwargs}]` | Cmd.Name {kwargs}
|
||||
`[cmdname, [args, {kwargs}]` | Core.Cmdname [[args],{kwargs}]
|
||||
|
||||
Since Evennia already supplies default inputfuncs that don't match the names expected by the most
|
||||
common GMCP implementations we have a few hard-coded mappings for those:
|
||||
|
||||
GMCP command name | Input/Outputfunc name
|
||||
-----------------
|
||||
"Core.Hello" | "client_options"
|
||||
"Core.Supports.Get" | "client_options"
|
||||
"Core.Commands.Get" | "get_inputfuncs"
|
||||
"Char.Value.Get" | "get_value"
|
||||
"Char.Repeat.Update" | "repeat"
|
||||
"Char.Monitor.Update" | "monitor"
|
||||
|
||||
#### Telnet + MSDP
|
||||
|
||||
[MSDP](http://tintin.sourceforge.net/msdp/), the *Mud Server Data Protocol*, is a competing standard
|
||||
to GMCP. The MSDP protocol page specifies a range of "recommended" available MSDP command names.
|
||||
Evennia does *not* support those - since MSDP doesn't specify a special format for its command names
|
||||
(like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual
|
||||
name.
|
||||
|
||||
MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports
|
||||
strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs
|
||||
needed. When sending MSDP for `("cmdname", ("arg",), {})` the resulting MSDP instruction will look
|
||||
like this:
|
||||
|
||||
IAC SB MSDP VAR cmdname VAL arg IAC SE
|
||||
|
||||
The various available MSDP constants like `VAR` (variable), `VAL` (value), `ARRAYOPEN`/`ARRAYCLOSE`
|
||||
and `TABLEOPEN`/`TABLECLOSE` are specified in `evennia/server/portal/telnet_oob`.
|
||||
|
||||
Outputfunc/Inputfunc | MSDP instruction
|
||||
-------------------------
|
||||
`[cmdname, [], {}]` | VAR cmdname VAL
|
||||
`[cmdname, [arg], {}]` | VAR cmdname VAL arg
|
||||
`[cmdname, [args],{}]` | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE
|
||||
`[cmdname, [], {kwargs}]` | VAR cmdname VAL TABLEOPEN VAR key VAL val ... TABLECLOSE
|
||||
`[cmdname, [args], {kwargs}]` | VAR cmdname VAL ARRAYOPEN VAL arg VAL arg ... ARRAYCLOSE VAR cmdname
|
||||
VAL TABLEOPEN VAR key VAL val ... TABLECLOSE
|
||||
|
||||
Observe that `VAR ... VAL` always identifies cmdnames, so if there are multiple arrays/dicts tagged
|
||||
with the same cmdname they will be appended to the args, kwargs of that inputfunc. Vice-versa, a
|
||||
different `VAR ... VAL` (outside a table) will come out as a second, different command input.
|
||||
|
||||
### SSH
|
||||
|
||||
SSH only supports the `text` input/outputcommand.
|
||||
|
||||
### Web client
|
||||
|
||||
Our web client uses pure JSON structures for all its communication, including `text`. This maps
|
||||
directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs. So the
|
||||
same example `("cmdname", ("arg",), {})` will be sent/received as a valid JSON structure
|
||||
|
||||
["cmdname, ["arg"], {}]
|
||||
|
||||
Since JSON is native to Javascript, this becomes very easy for the webclient to handle.
|
||||
94
docs/source/Concepts/Soft-Code.md
Normal file
94
docs/source/Concepts/Soft-Code.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# 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:
|
||||
|
||||
- http://www.tinymux.com/wiki/index.php/Softcode
|
||||
- http://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](../Contrib/Dialogues-in-events) is an optional
|
||||
pseudo-softcode plugin aimed at developers wanting to script their game from inside it.
|
||||
66
docs/source/Concepts/Text-Encodings.md
Normal file
66
docs/source/Concepts/Text-Encodings.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Text Encodings
|
||||
|
||||
|
||||
Evennia is a text-based game server. This makes it important to understand how
|
||||
it actually deals with data in the form of text.
|
||||
|
||||
Text *byte encodings* describe how a string of text is actually stored in the
|
||||
computer - that is, the particular sequence of bytes used to represent the
|
||||
letters of your particular alphabet. A common encoding used in English-speaking
|
||||
languages is the *ASCII* encoding. This describes the letters in the English
|
||||
alphabet (Aa-Zz) as well as a bunch of special characters. For describing other
|
||||
character sets (such as that of other languages with other letters than
|
||||
English), sets with names such as *Latin-1*, *ISO-8859-3* and *ARMSCII-8*
|
||||
are used. There are hundreds of different byte encodings in use around the
|
||||
world.
|
||||
|
||||
A string of letters in a byte encoding is represented with the `bytes` type.
|
||||
In contrast to the byte encoding is the *unicode representation*. In Python
|
||||
this is the `str` type. The unicode is an internationally agreed-upon table
|
||||
describing essentially all available letters you could ever want to print.
|
||||
Everything from English to Chinese alphabets and all in between. So what
|
||||
Evennia (as well as Python and Django) does is to store everything in Unicode
|
||||
internally, but then converts the data to one of the encodings whenever
|
||||
outputting data to the user.
|
||||
|
||||
An easy memory aid is that `bytes` are what are sent over the network wire. At
|
||||
all other times, `str` (unicode) is used. This means that we must convert
|
||||
between the two at the points where we send/receive network data.
|
||||
|
||||
The problem is that when receiving a string of bytes over the network it's
|
||||
impossible for Evennia to guess which encoding was used - it's just a bunch of
|
||||
bytes! Evennia must know the encoding in order to convert back and from the
|
||||
correct unicode representation.
|
||||
|
||||
## How to customize encodings
|
||||
|
||||
As long as you stick to the standard ASCII character set (which means the
|
||||
normal English characters, basically) you should not have to worry much
|
||||
about this section.
|
||||
|
||||
If you want to build your game in another language however, or expect your
|
||||
users to want to use special characters not in ASCII, you need to consider
|
||||
which encodings you want to support.
|
||||
|
||||
As mentioned, there are many, many byte-encodings used around the world. It
|
||||
should be clear at this point that Evennia can't guess but has to assume or
|
||||
somehow be told which encoding you want to use to communicate with the server.
|
||||
Basically the encoding used by your client must be the same encoding used by
|
||||
the server. This can be customized in two complementary ways.
|
||||
|
||||
1. Point users to the default `@encoding` command or the `@options` command.
|
||||
This allows them to themselves set which encoding they (and their client of
|
||||
choice) uses. Whereas data will remain stored as unicode strings internally in
|
||||
Evennia, all data received from and sent to this particular player will be
|
||||
converted to the given format before transmitting.
|
||||
1. As a back-up, in case the user-set encoding translation is erroneous or
|
||||
fails in some other way, Evennia will fall back to trying with the names
|
||||
defined in the settings variable `ENCODINGS`. This is a list of encoding
|
||||
names Evennia will try, in order, before giving up and giving an encoding
|
||||
error message.
|
||||
|
||||
Note that having to try several different encodings every input/output adds
|
||||
unneccesary overhead. Try to guess the most common encodings you players will
|
||||
use and make sure these are tried first. The International *UTF-8* encoding is
|
||||
what Evennia assumes by default (and also what Python/Django use normally). See
|
||||
the Wikipedia article [here](http://en.wikipedia.org/wiki/Text_encodings) for more help.
|
||||
340
docs/source/Concepts/TextTags.md
Normal file
340
docs/source/Concepts/TextTags.md
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
# TextTags
|
||||
|
||||
|
||||
This documentation details the various text tags supported by Evennia, namely *colours*, *command
|
||||
links* and *inline functions*.
|
||||
|
||||
There is also an [Understanding Color Tags](../Howto/Understanding-Color-Tags) tutorial which expands on the
|
||||
use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.
|
||||
|
||||
## Coloured text
|
||||
|
||||
*Note that the Documentation does not display colour the way it would look on the screen.*
|
||||
|
||||
Color can be a very useful tool for your game. It can be used to increase readability and make your
|
||||
game more appealing visually.
|
||||
|
||||
Remember however that, with the exception of the webclient, you generally don't control the client
|
||||
used to connect to the game. There is, for example, one special tag meaning "yellow". But exactly
|
||||
*which* hue of yellow is actually displayed on the user's screen depends on the settings of their
|
||||
particular mud client. They could even swap the colours around or turn them off altogether if so
|
||||
desired. Some clients don't even support color - text games are also played with special reading
|
||||
equipment by people who are blind or have otherwise diminished eyesight.
|
||||
|
||||
So a good rule of thumb is to use colour to enhance your game but don't *rely* on it to display
|
||||
critical information. If you are coding the game, you can add functionality to let users disable
|
||||
colours as they please, as described [here](../Howto/Manually-Configuring-Color).
|
||||
|
||||
To see which colours your client support, use the default `@color` command. This will list all
|
||||
available colours for ANSI and Xterm256 along with the codes you use for them. You can find a list
|
||||
of all the parsed `ANSI`-colour codes in `evennia/utils/ansi.py`.
|
||||
|
||||
### ANSI colours
|
||||
|
||||
Evennia supports the `ANSI` standard for text. This is by far the most supported MUD-color standard,
|
||||
available in all but the most ancient mud clients. The ANSI colours are **r**ed, **g**reen,
|
||||
**y**ellow, **b**lue, **m**agenta, **c**yan, **w**hite and black. They are abbreviated by their
|
||||
first letter except for black which is abbreviated with the letter **x**. In ANSI there are "bright"
|
||||
and "normal" (darker) versions of each color, adding up to a total of 16 colours to use for
|
||||
foreground text. There are also 8 "background" colours. These have no bright alternative in ANSI
|
||||
(but Evennia uses the [Xterm256](./TextTags#xterm256-colours) extension behind the scenes to offer
|
||||
them anyway).
|
||||
|
||||
To colour your text you put special tags in it. Evennia will parse these and convert them to the
|
||||
correct markup for the client used. If the user's client/console/display supports ANSI colour, they
|
||||
will see the text in the specified colour, otherwise the tags will be stripped (uncolored text).
|
||||
This works also for non-terminal clients, such as the webclient. For the webclient, Evennia will
|
||||
translate the codes to HTML RGB colors.
|
||||
|
||||
Here is an example of the tags in action:
|
||||
|
||||
|rThis text is bright red.|n This is normal text.
|
||||
|RThis is a dark red text.|n This is normal text.
|
||||
|[rThis text has red background.|n This is normal text.
|
||||
|b|[yThis is bright blue text on yellow background.|n This is normal text.
|
||||
|
||||
- `|n` - this tag will turn off all color formatting, including background colors.
|
||||
- `|#`- markup marks the start of foreground color. The case defines if the text is "bright" or
|
||||
"normal". So `|g` is a bright green and `|G` is "normal" (darker) green.
|
||||
- `|[#` is used to add a background colour to the text. The case again specifies if it is "bright"
|
||||
or "normal", so `|[c` starts a bright cyan background and `|[C` a darker cyan background.
|
||||
- `|!#` is used to add foreground color without any enforced brightness/normal information.
|
||||
These are normal-intensity and are thus always given as uppercase, such as
|
||||
`|!R` for red. The difference between e.g. `|!R` and `|R` is that
|
||||
`|!R` will "inherit" the brightness setting from previously set color tags, whereas `|R` will
|
||||
always reset to the normal-intensity red. The `|#` format contains an implicit `|h`/`|H` tag in it:
|
||||
disabling highlighting when switching to a normal color, and enabling it for bright ones. So `|btest
|
||||
|!Rtest2` will result in a bright red `test2` since the brightness setting from `|b` "bleeds over".
|
||||
You could use this to for example quickly switch the intensity of a multitude of color tags. There
|
||||
is no background-color equivalent to `|!` style tags.
|
||||
- `|h` is used to make any following foreground ANSI colors bright (it has no effect on Xterm
|
||||
colors). This is only relevant to use with `|!` type tags and will be valid until the next `|n`,
|
||||
`|H` or normal (upper-case) `|#` tag. This tag will never affect background colors, those have to be
|
||||
set bright/normal explicitly. Technically, `|h|!G` is identical to `|g`.
|
||||
- `|H` negates the effects `|h` and returns all ANSI foreground colors (`|!` and `|` types) to
|
||||
'normal' intensity. It has no effect on background and Xterm colors.
|
||||
|
||||
> Note: The ANSI standard does not actually support bright backgrounds like `|[r` - the standard
|
||||
only supports "normal" intensity backgrounds. To get around this Evennia instead implements these
|
||||
as [Xterm256 colours](./TextTags#xterm256-colours) behind the scenes. If the client does not support
|
||||
Xterm256 the ANSI colors will be used instead and there will be no visible difference between using
|
||||
upper- and lower-case background tags.
|
||||
|
||||
If you want to display an ANSI marker as output text (without having any effect), you need to escape
|
||||
it by preceding its `|` with another `|`:
|
||||
|
||||
```
|
||||
say The ||r ANSI marker changes text color to bright red.
|
||||
```
|
||||
|
||||
This will output the raw `|r` without any color change. This can also be necessary if you are doing
|
||||
ansi art that uses `|` with a letter directly following it.
|
||||
|
||||
Use the command
|
||||
|
||||
@color ansi
|
||||
|
||||
to get a list of all supported ANSI colours and the tags used to produce them.
|
||||
|
||||
A few additional ANSI codes are supported:
|
||||
|
||||
- `|/` A line break. You cannot put the normal Python `\n` line breaks in text entered inside the
|
||||
game (Evennia will filter this for security reasons). This is what you use instead: use the `|/`
|
||||
marker to format text with line breaks from the game command line.
|
||||
- `` This will translate into a `TAB` character. This will not always show (or show differently) to
|
||||
the client since it depends on their local settings. It's often better to use multiple spaces.
|
||||
- `|_` This is a space. You can usually use the normal space character, but if the space is *at the
|
||||
end of the line*, Evennia will likely crop it. This tag will not be cropped but always result in a
|
||||
space.
|
||||
- `|*` This will invert the current text/background colours. Can be useful to mark things (but see
|
||||
below).
|
||||
|
||||
##### Caveats of `|*`
|
||||
|
||||
The `|*` tag (inverse video) is an old ANSI standard and should usually not be used for more than to
|
||||
mark short snippets of text. If combined with other tags it comes with a series of potentially
|
||||
confusing behaviors:
|
||||
|
||||
* The `|*` tag will only work once in a row:, ie: after using it once it won't have an effect again
|
||||
until you declare another tag. This is an example:
|
||||
|
||||
```
|
||||
Normal text, |*reversed text|*, still reversed text.
|
||||
```
|
||||
|
||||
that is, it will not reverse to normal at the second `|*`. You need to reset it manually:
|
||||
|
||||
```
|
||||
Normal text, |*reversed text|n, normal again.
|
||||
```
|
||||
|
||||
* The `|*` tag does not take "bright" colors into account:
|
||||
|
||||
```
|
||||
|RNormal red, |hnow brightened. |*BG is normal red.
|
||||
```
|
||||
|
||||
So `|*` only considers the 'true' foreground color, ignoring any highlighting. Think of the bright
|
||||
state (`|h`) as something like like `<strong>` in HTML: it modifies the _appearance_ of a normal
|
||||
foreground color to match its bright counterpart, without changing its normal color.
|
||||
* Finally, after a `|*`, if the previous background was set to a dark color (via `|[`), `|!#`) will
|
||||
actually change the background color instead of the foreground:
|
||||
|
||||
```
|
||||
|*reversed text |!R now BG is red.
|
||||
```
|
||||
For a detailed explanation of these caveats, see the [Understanding Color Tags](Understanding-Color-
|
||||
Tags) tutorial. But most of the time you might be better off to simply avoid `|*` and mark your text
|
||||
manually instead.
|
||||
|
||||
### Xterm256 Colours
|
||||
|
||||
The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background.
|
||||
While this offers many more possibilities than traditional ANSI colours, be wary that too many text
|
||||
colors will be confusing to the eye. Also, not all clients support Xterm256 - these will instead see
|
||||
the closest equivalent ANSI color. You can mix Xterm256 tags with ANSI tags as you please.
|
||||
|
||||
|555 This is pure white text.|n This is normal text.
|
||||
|230 This is olive green text.
|
||||
|[300 This text has a dark red background.
|
||||
|005|[054 This is dark blue text on a bright cyan background.
|
||||
|=a This is a greyscale value, equal to black.
|
||||
|=m This is a greyscale value, midway between white and black.
|
||||
|=z This is a greyscale value, equal to white.
|
||||
|[=m This is a background greyscale value.
|
||||
|
||||
- `|###` - markup consists of three digits, each an integer from 0 to 5. The three digits describe
|
||||
the amount of **r**ed, **g**reen and **b**lue (RGB) components used in the colour. So `|500` means
|
||||
maximum red and none of the other colours - the result is a bright red. `|520` is red with a touch
|
||||
of green - the result is orange. As opposed to ANSI colors, Xterm256 syntax does not worry about
|
||||
bright/normal intensity, a brighter (lighter) color is just achieved by upping all RGB values with
|
||||
the same amount.
|
||||
- `|[###` - this works the same way but produces a coloured background.
|
||||
- `|=#` - markup produces the xterm256 gray scale tones, where `#` is a letter from `a` (black) to
|
||||
`z` (white). This offers many more nuances of gray than the normal `|###` markup (which only has
|
||||
four gray tones between solid black and white (`|000`, `|111`, `|222`, `|333` and `|444`)).
|
||||
- `|[=#` - this works in the same way but produces background gray scale tones.
|
||||
|
||||
If you have a client that supports Xterm256, you can use
|
||||
|
||||
@color xterm256
|
||||
|
||||
to get a table of all the 256 colours and the codes that produce them. If the table looks broken up
|
||||
into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement.
|
||||
You can use the `@options` command to see if xterm256 is active for you. This depends on if your
|
||||
client told Evennia what it supports - if not, and you know what your client supports, you may have
|
||||
to activate some features manually.
|
||||
|
||||
## Clickable links
|
||||
|
||||
Evennia supports clickable links for clients that supports it. This marks certain text so it can be
|
||||
clicked by a mouse and trigger a given Evennia command. To support clickable links, Evennia requires
|
||||
the webclient or an third-party telnet client with [MXP](http://www.zuggsoft.com/zmud/mxp.htm)
|
||||
support (*Note: Evennia only supports clickable links, no other MXP features*).
|
||||
|
||||
- `|lc` to start the link, by defining the command to execute.
|
||||
- `|lt` to continue with the text to show to the user (the link text).
|
||||
- `|le` to end the link text and the link definition.
|
||||
|
||||
All elements must appear in exactly this order to make a valid link. For example,
|
||||
|
||||
```
|
||||
"If you go |lcnorth|ltto the north|le you will find a cottage."
|
||||
```
|
||||
|
||||
This will display as "If you go __to the north__ you will find a cottage." where clicking the link
|
||||
will execute the command `north`. If the client does not support clickable links, only the link text
|
||||
will be shown.
|
||||
|
||||
## Inline functions
|
||||
|
||||
> Note: Inlinefuncs are **not** activated by default. To use them you need to add
|
||||
`INLINEFUNC_ENABLED=True` to your settings file.
|
||||
|
||||
Evennia has its own inline text formatting language, known as *inlinefuncs*. It allows the builder
|
||||
to include special function calls in code. They are executed dynamically by each session that
|
||||
receives them.
|
||||
|
||||
To add an inlinefunc, you embed it in a text string like this:
|
||||
|
||||
```
|
||||
"A normal string with $funcname(arg, arg, ...) embedded inside it."
|
||||
```
|
||||
|
||||
When this string is sent to a session (with the `msg()` method), these embedded inlinefuncs will be
|
||||
parsed. Their return value (which always is a string) replace their call location in the finalized
|
||||
string. The interesting thing with this is that the function called will have access to which
|
||||
session is seeing the string, meaning the string can end up looking different depending on who is
|
||||
looking. It could of course also vary depending on other factors like game time.
|
||||
|
||||
Any number of comma-separated arguments can be given (or none). No keywords are supported. You can
|
||||
also nest inlinefuncs by letting an argument itself also be another `$funcname(arg, arg, ...)` call
|
||||
(down to any depth of nesting). Function call resolution happens as in all programming languages
|
||||
inside-out, with the nested calls replacing the argument with their return strings before calling he
|
||||
parent.
|
||||
|
||||
```
|
||||
> say "This is $pad(a center-padded text, 30,c,-) of width 30."
|
||||
You say, "This is ---- a center-padded text----- of width 30."
|
||||
```
|
||||
|
||||
A special case happens if wanting to use an inlinefunc argument that itself includes a comma - this
|
||||
would be parsed as an argument separator. To escape commas you can either escape each comma manually
|
||||
with a backslash `\,`, or you can embed the entire string in python triple-quotes `"""` or `'''` -
|
||||
this will escape the entire argument, including commas and any nested inlinefunc calls within.
|
||||
|
||||
Only certain functions are available to use as inlinefuncs and the game developer may add their own
|
||||
functions as needed.
|
||||
|
||||
### New inlinefuncs
|
||||
|
||||
To add new inlinefuncs, edit the file `mygame/server/conf/inlinefuncs.py`.
|
||||
|
||||
*All globally defined functions in this module* are considered inline functions by the system. The
|
||||
only exception is functions whose name starts with an underscore `_`. An inlinefunc must be of the
|
||||
following form:
|
||||
|
||||
```python
|
||||
def funcname(*args, **kwargs):
|
||||
# ...
|
||||
return modified_text
|
||||
```
|
||||
|
||||
where `*args` denotes all the arguments this function will accept as an `$inlinefunc`. The inline
|
||||
function is expected to clean arguments and check that they are valid. If needed arguments are not
|
||||
given, default values should be used. The function should always return a string (even if it's
|
||||
empty). An inlinefunc should never cause a traceback regardless of the input (but it could log
|
||||
errors if desired).
|
||||
|
||||
Note that whereas the function should accept `**kwargs`, keyword inputs are *not* usable in the call
|
||||
to the inlinefunction. The `kwargs` part is instead intended for Evennia to be able to supply extra
|
||||
information. Currently Evennia sends a single keyword to every inline function and that is
|
||||
`session`, which holds the [serversession](../Components/Sessions) this text is targeted at. Through the session
|
||||
object, a lot of dynamic possibilities are opened up for your inline functions.
|
||||
|
||||
The `settings.INLINEFUNC_MODULES` configuration option is a list that decides which modules should
|
||||
be parsed for inline function definitions. This will include `mygame/server/conf/inlinefuncs.py` but
|
||||
more could be added. The list is read from left to right so if you want to overload default
|
||||
functions you just have to put your custom module-paths later in the list and name your functions
|
||||
the same as default ones.
|
||||
|
||||
Here is an example, the `crop` default inlinefunction:
|
||||
|
||||
```python
|
||||
from evennia.utils import utils
|
||||
|
||||
def crop(*args, **kwargs):
|
||||
"""
|
||||
Inlinefunc. Crops ingoing text to given widths.
|
||||
Args:
|
||||
text (str, optional): Text to crop.
|
||||
width (str, optional): Will be converted to an integer. Width of
|
||||
crop in characters.
|
||||
suffix (str, optional): End string to mark the fact that a part
|
||||
of the string was cropped. Defaults to `[...]`.
|
||||
Kwargs:
|
||||
session (Session): Session performing the crop.
|
||||
Example:
|
||||
`$crop(text, 50, [...])`
|
||||
|
||||
"""
|
||||
text, width, suffix = "", 78, "[...]"
|
||||
nargs = len(args)
|
||||
if nargs > 0:
|
||||
text = args[0]
|
||||
if nargs > 1:
|
||||
width = int(args[1]) if args[1].strip().isdigit() else 78
|
||||
if nargs > 2:
|
||||
suffix = args[2]
|
||||
return utils.crop(text, width=width, suffix=suffix)
|
||||
```
|
||||
Another example, making use of the Session:
|
||||
|
||||
```python
|
||||
def charactername(*args, **kwargs):
|
||||
"""
|
||||
Inserts the character name of whomever sees the string
|
||||
(so everyone will see their own name). Uses the account
|
||||
name for OOC communications.
|
||||
|
||||
Example:
|
||||
say "This means YOU, $charactername()!"
|
||||
|
||||
"""
|
||||
session = kwargs["session"]
|
||||
if session.puppet:
|
||||
return kwargs["session"].puppet.key
|
||||
else:
|
||||
return session.account.key
|
||||
```
|
||||
|
||||
Evennia itself offers the following default inline functions (mostly as examples):
|
||||
|
||||
* `crop(text, width, suffix)` - See above.
|
||||
* `pad(text, width, align, fillchar)` - this pads the text to `width` (default 78), alignment ("c",
|
||||
"l" or "r", defaulting to "c") and fill-in character (defaults to space). Example: `$pad(40,l,-)`
|
||||
* `clr(startclr, text, endclr)` - A programmatic way to enter colored text for those who don't want
|
||||
to use the normal `|c` type color markers for some reason. The `color` argument is the same as the
|
||||
color markers except without the actual pre-marker, so `|r` would be just `r`. If `endclr` is not
|
||||
given, it defaults to resetting the color (`n`). Example: `$clr(b, A blue text)`
|
||||
* `space(number)` - Inserts the given number of spaces. If no argument is given, use 4 spaces.
|
||||
85
docs/source/Concepts/Using-MUX-as-a-Standard.md
Normal file
85
docs/source/Concepts/Using-MUX-as-a-Standard.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# 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](http://www.tinymux.org/), and its cousins [PennMUSH](http://www.pennmush.org),
|
||||
[TinyMUSH](http://tinymush.sourceforge.net/), and [RhostMUSH](http://www.rhostmush.org/). 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)). Evennia also does not shy from using its own syntax when deemed appropriate: the
|
||||
MUX syntax has grown organically over a long time and is, frankly, rather arcane in places. All in
|
||||
all the default command syntax should at most be referred to as "MUX-like" or "MUX-inspired".
|
||||
|
||||
## Documentation policy
|
||||
|
||||
All the commands in the default command sets should have their doc-strings formatted on a similar
|
||||
form:
|
||||
|
||||
```python
|
||||
"""
|
||||
Short header
|
||||
|
||||
Usage:
|
||||
key[/switches, if any] <mandatory args> [optional] choice1||choice2||choice3
|
||||
|
||||
Switches:
|
||||
switch1 - description
|
||||
switch2 - description
|
||||
|
||||
Examples:
|
||||
usage example and output
|
||||
|
||||
Longer documentation detailing the command.
|
||||
|
||||
"""
|
||||
```
|
||||
|
||||
- Two spaces are used for *indentation* in all default commands.
|
||||
- Square brackets `[ ]` surround *optional, skippable arguments*.
|
||||
- Angled brackets `< >` surround a _description_ of what to write rather than the exact syntax.
|
||||
- *Explicit choices are separated by `|`. To avoid this being parsed as a color code, use `||` (this
|
||||
will come out as a single `|`) or put spaces around the character ("` | `") if there's plenty of
|
||||
room.
|
||||
- The `Switches` and `Examples` blocks are optional based on the Command.
|
||||
|
||||
Here is the `nick` command as an example:
|
||||
|
||||
```python
|
||||
"""
|
||||
Define a personal alias/nick
|
||||
|
||||
Usage:
|
||||
nick[/switches] <nickname> = [<string>]
|
||||
alias ''
|
||||
|
||||
Switches:
|
||||
object - alias an object
|
||||
account - alias an account
|
||||
clearall - clear all your aliases
|
||||
list - show all defined aliases (also "nicks" works)
|
||||
|
||||
Examples:
|
||||
nick hi = say Hello, I'm Sarah!
|
||||
nick/object tom = the tall man
|
||||
|
||||
A 'nick' is a personal shortcut you create for your own use [...]
|
||||
|
||||
"""
|
||||
```
|
||||
|
||||
For commands that *require arguments*, the policy is for it to return a `Usage:` string if the
|
||||
command is entered without any arguments. So for such commands, the Command body should contain
|
||||
something to the effect of
|
||||
|
||||
```python
|
||||
if not self.args:
|
||||
self.caller.msg("Usage: nick[/switches] <nickname> = [<string>]")
|
||||
return
|
||||
```
|
||||
133
docs/source/Concepts/Web-Features.md
Normal file
133
docs/source/Concepts/Web-Features.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# 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](../Howto/Starting/Part5/Web-Tutorial) 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) 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`.
|
||||
55
docs/source/Concepts/Zones.md
Normal file
55
docs/source/Concepts/Zones.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Zones
|
||||
|
||||
|
||||
Say you create a room named *Meadow* in your nice big forest MUD. That's all nice and dandy, but
|
||||
what if you, in the other end of that forest want another *Meadow*? As a game creator, this can
|
||||
cause all sorts of confusion. For example, teleporting to *Meadow* will now give you a warning that
|
||||
there are two *Meadow* s and you have to select which one. It's no problem to do that, you just
|
||||
choose for example to go to `2-meadow`, but unless you examine them you couldn't be sure which of
|
||||
the two sat in the magical part of the forest and which didn't.
|
||||
|
||||
Another issue is if you want to group rooms in geographic regions. Let's say the "normal" part of
|
||||
the forest should have separate weather patterns from the magical part. Or maybe a magical
|
||||
disturbance echoes through all magical-forest rooms. It would then be convenient to be able to
|
||||
simply find all rooms that are "magical" so you could send messages to them.
|
||||
|
||||
## Zones in Evennia
|
||||
|
||||
*Zones* try to separate rooms by global location. In our example we would separate the forest into
|
||||
two parts - the magical and the non-magical part. Each have a *Meadow* and rooms belonging to each
|
||||
part should be easy to retrieve.
|
||||
|
||||
Many MUD codebases hardcode zones as part of the engine and database. Evennia does no such
|
||||
distinction due to the fact that rooms themselves are meant to be customized to any level anyway.
|
||||
Below is a suggestion for how to implement zones in Evennia.
|
||||
|
||||
All objects in Evennia can hold any number of [Tags](../Components/Tags). Tags are short labels that you attach to
|
||||
objects. They make it very easy to retrieve groups of objects. An object can have any number of
|
||||
different tags. So let's attach the relevant tag to our forest:
|
||||
|
||||
```python
|
||||
forestobj.tags.add("magicalforest", category="zone")
|
||||
```
|
||||
|
||||
You could add this manually, or automatically during creation somehow (you'd need to modify your
|
||||
@dig command for this, most likely). You can also use the default `@tag` command during building:
|
||||
|
||||
@tag forestobj = magicalforest : zone
|
||||
|
||||
Henceforth you can then easily retrieve only objects with a given tag:
|
||||
|
||||
```python
|
||||
import evennia
|
||||
rooms = evennia.search_tag("magicalforest", category="zone")
|
||||
```
|
||||
|
||||
## Using typeclasses and inheritance for zoning
|
||||
|
||||
The tagging or aliasing systems above don't instill any sort of functional difference between a
|
||||
magical forest room and a normal one - they are just arbitrary ways to mark objects for quick
|
||||
retrieval later. Any functional differences must be expressed using [Typeclasses](../Components/Typeclasses).
|
||||
|
||||
Of course, an alternative way to implement zones themselves is to have all rooms/objects in a zone
|
||||
inherit from a given typeclass parent - and then limit your searches to objects inheriting from that
|
||||
given parent. The effect would be similar but you'd need to expand the search functionality to
|
||||
properly search the inheritance tree.
|
||||
Loading…
Add table
Add a link
Reference in a new issue