Add breadcrumbs. Format markdown files to char width 100

This commit is contained in:
Griatch 2020-06-16 16:53:35 +02:00
parent 10c1831aad
commit 78970e92b3
142 changed files with 10357 additions and 3417 deletions

View file

@ -4,24 +4,30 @@
"""
Format given files to a max width.
Usage:
python fmtwidth.py --width 79 ../source/*.md
"""
import glob
import textwrap
import argparse
_DEFAULT_WIDTH = 100
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("files")
parser.add_argument("-w", '--width', dest="width", type=int, default=79)
parser.add_argument("-w", '--width', dest="width", type=int, default=_DEFAULT_WIDTH)
args = parser.parse_args()
filepaths = glob.glob(args.files)
width = args.width
wrapper = textwrap.TextWrapper(
width=args.width,
width=width,
break_long_words=False,
expand_tabs=True,
)
@ -29,8 +35,13 @@ if __name__ == "__main__":
count = 0
for filepath in filepaths:
with open(filepath, 'r') as fil:
txt = fil.read()
txt = "\n".join(wrapper.wrap(txt))
lines = fil.readlines()
outlines = [
"\n".join(wrapper.wrap(line)) if len(line) > width else line.strip('\n')
for line in lines
]
txt = "\n".join(outlines)
with open(filepath, 'w') as fil:
fil.write(txt)
count += 1

View file

@ -3,13 +3,24 @@
- Previous tutorial: [Adding dialogues in events](Dialogues-in-events)
This tutorial will walk you through the steps to create a voice-operated elevator, using the [in-game Python system](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md). This tutorial assumes the in-game Python system is installed in your game. If it isn't, you can follow the installation steps given in [the documentation on in-game Python](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md), and come back on this tutorial once the system is installed. **You do not need to read** the entire documentation, it's a good reference, but not the easiest way to learn about it. Hence these tutorials.
This tutorial will walk you through the steps to create a voice-operated elevator, using the [in-
game Python
system](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md).
This tutorial assumes the in-game Python system is installed in your game. If it isn't, you can
follow the installation steps given in [the documentation on in-game
Python](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md), and
come back on this tutorial once the system is installed. **You do not need to read** the entire
documentation, it's a good reference, but not the easiest way to learn about it. Hence these
tutorials.
The in-game Python system allows to run code on individual objects in some situations. You don't have to modify the source code to add these features, past the installation. The entire system makes it easy to add specific features to some objects, but not all.
The in-game Python system allows to run code on individual objects in some situations. You don't
have to modify the source code to add these features, past the installation. The entire system
makes it easy to add specific features to some objects, but not all.
> What will we try to do?
In this tutorial, we are going to create a simple voice-operated elevator. In terms of features, we will:
In this tutorial, we are going to create a simple voice-operated elevator. In terms of features, we
will:
- Explore events with parameters.
- Work on more interesting callbacks.
@ -18,17 +29,27 @@ In this tutorial, we are going to create a simple voice-operated elevator. In t
## Our study case
Let's summarize what we want to achieve first. We would like to create a room that will represent the inside of our elevator. In this room, a character could just say "1", "2" or "3", and the elevator will start moving. The doors will close and open on the new floor (the exits leading in and out of the elevator will be modified).
Let's summarize what we want to achieve first. We would like to create a room that will represent
the inside of our elevator. In this room, a character could just say "1", "2" or "3", and the
elevator will start moving. The doors will close and open on the new floor (the exits leading in
and out of the elevator will be modified).
We will work on basic features first, and then will adjust some, showing you how easy and powerfully independent actions can be configured through the in-game Python system.
We will work on basic features first, and then will adjust some, showing you how easy and powerfully
independent actions can be configured through the in-game Python system.
## Creating the rooms and exits we need
We'll create an elevator right in our room (generally called "Limbo", of ID 2). You could easily adapt the following instructions if you already have some rooms and exits, of course, just remember to check the IDs.
We'll create an elevator right in our room (generally called "Limbo", of ID 2). You could easily
adapt the following instructions if you already have some rooms and exits, of course, just remember
to check the IDs.
> Note: the in-game Python system uses IDs for a lot of things. While it is not mandatory, it is good practice to know the IDs you have for your callbacks, because it will make manipulation much quicker. There are other ways to identify objects, but as they depend on many factors, IDs are usually the safest path in our callbacks.
> Note: the in-game Python system uses IDs for a lot of things. While it is not mandatory, it is
good practice to know the IDs you have for your callbacks, because it will make manipulation much
quicker. There are other ways to identify objects, but as they depend on many factors, IDs are
usually the safest path in our callbacks.
Let's go into limbo (`#2`) to add our elevator. We'll add it to the north. To create this room, in-game you could type:
Let's go into limbo (`#2`) to add our elevator. We'll add it to the north. To create this room,
in-game you could type:
tunnel n = Inside of an elevator
@ -49,9 +70,12 @@ Keep these IDs somewhere for the demonstration. You will shortly see why they a
> Why have we created exits to our elevator and back to Limbo? Isn't the elevator supposed to move?
It is. But we need to have exits that will represent the way inside the elevator and out. What we will do, at every floor, will be to change these exits so they become connected to the right room. You'll see this process a bit later.
It is. But we need to have exits that will represent the way inside the elevator and out. What we
will do, at every floor, will be to change these exits so they become connected to the right room.
You'll see this process a bit later.
We have two more rooms to create: our floor 2 and 3. This time, we'll use `dig`, because we don't need exits leading there, not yet anyway.
We have two more rooms to create: our floor 2 and 3. This time, we'll use `dig`, because we don't
need exits leading there, not yet anyway.
dig The second floor
dig The third floor
@ -67,23 +91,38 @@ Add these IDs to your list, we will use them too.
Let's go to the elevator (you could use `tel #3` if you have the same IDs I have).
This is our elevator room. It looks a bit empty, feel free to add a prettier description or other things to decorate it a bit.
This is our elevator room. It looks a bit empty, feel free to add a prettier description or other
things to decorate it a bit.
But what we want now is to be able to say "1", "2" or "3" and have the elevator move in that direction.
But what we want now is to be able to say "1", "2" or "3" and have the elevator move in that
direction.
If you have read [the previous tutorial about adding dialogues in events](Dialogues-in-events), you may remember what we need to do. If not, here's a summary: we need to run some code when somebody speaks in the room. So we need to create a callback (the callback will contain our lines of code). We just need to know on which event this should be set. You can enter `call here` to see the possible events in this room.
If you have read [the previous tutorial about adding dialogues in events](Dialogues-in-events), you
may remember what we need to do. If not, here's a summary: we need to run some code when somebody
speaks in the room. So we need to create a callback (the callback will contain our lines of code).
We just need to know on which event this should be set. You can enter `call here` to see the
possible events in this room.
In the table, you should see the "say" event, which is called when somebody says something in the room. So we'll need to add a callback to this event. Don't worry if you're a bit lost, just follow the following steps, the way they connect together will become more obvious.
In the table, you should see the "say" event, which is called when somebody says something in the
room. So we'll need to add a callback to this event. Don't worry if you're a bit lost, just follow
the following steps, the way they connect together will become more obvious.
call/add here = say 1, 2, 3
1. We need to add a callback. A callback contains the code that will be executed at a given time. So we use the `call/add` command and switch.
1. We need to add a callback. A callback contains the code that will be executed at a given time.
So we use the `call/add` command and switch.
2. `here` is our object, the room in which we are.
3. An equal sign.
4. The name of the event to which the callback should be connected. Here, the event is "say". Meaning this callback will be executed every time somebody says something in the room.
5. But we add an event parameter to indicate the keywords said in the room that should execute our callback. Otherwise, our callback would be called every time somebody speaks, no matter what. Here we limit, indicating our callback should be executed only if the spoken message contains "1", "2" or "3".
4. The name of the event to which the callback should be connected. Here, the event is "say".
Meaning this callback will be executed every time somebody says something in the room.
5. But we add an event parameter to indicate the keywords said in the room that should execute our
callback. Otherwise, our callback would be called every time somebody speaks, no matter what. Here
we limit, indicating our callback should be executed only if the spoken message contains "1", "2" or
"3".
An editor should open, inviting you to enter the Python code that should be executed. The first thing to remember is to read the text provided (it can contain important information) and, most of all, the list of variables that are available in this callback:
An editor should open, inviting you to enter the Python code that should be executed. The first
thing to remember is to read the text provided (it can contain important information) and, most of
all, the list of variables that are available in this callback:
```
Variables you can use in this event:
@ -97,25 +136,30 @@ Variables you can use in this event:
----------[l:01 w:000 c:0000]------------(:h for help)----------------------------
```
This is important, in order to know what variables we can use in our callback out-of-the-box. Let's write a single line to be sure our callback is called when we expect it to:
This is important, in order to know what variables we can use in our callback out-of-the-box. Let's
write a single line to be sure our callback is called when we expect it to:
```python
character.msg("You just said {}.".format(message))
```
You can paste this line in-game, then type the `:wq` command to exit the editor and save your modifications.
You can paste this line in-game, then type the `:wq` command to exit the editor and save your
modifications.
Let's check. Try to say "hello" in the room. You should see the standard message, but nothing more. Now try to say "1". Below the standard message, you should see:
Let's check. Try to say "hello" in the room. You should see the standard message, but nothing
more. Now try to say "1". Below the standard message, you should see:
You just said 1.
You can try it. Our callback is only called when we say "1", "2" or "3". Which is just what we want.
You can try it. Our callback is only called when we say "1", "2" or "3". Which is just what we
want.
Let's go back in our code editor and add something more useful.
call/edit here = say
> Notice that we used the "edit" switch this time, since the callback exists, we just want to edit it.
> Notice that we used the "edit" switch this time, since the callback exists, we just want to edit
it.
The editor opens again. Let's empty it first:
@ -125,16 +169,19 @@ And turn off automatic indentation, which will help us:
:=
> Auto-indentation is an interesting feature of the code editor, but we'd better not use it at this point, it will make copy/pasting more complicated.
> Auto-indentation is an interesting feature of the code editor, but we'd better not use it at this
point, it will make copy/pasting more complicated.
## Our entire callback in the elevator
So here's the time to truly code our callback in-game. Here's a little reminder:
1. We have all the IDs of our three rooms and two exits.
2. When we say "1", "2" or "3", the elevator should move to the right room, that is change the exits. Remember, we already have the exits, we just need to change their location and destination.
2. When we say "1", "2" or "3", the elevator should move to the right room, that is change the
exits. Remember, we already have the exits, we just need to change their location and destination.
It's a good idea to try to write this callback yourself, but don't feel bad about checking the solution right now. Here's a possible code that you could paste in the code editor:
It's a good idea to try to write this callback yourself, but don't feel bad about checking the
solution right now. Here's a possible code that you could paste in the code editor:
```python
# First let's have some constants
@ -164,32 +211,52 @@ else:
Let's review this longer callback:
1. We first obtain the objects of both exits and our three floors. We use the `get()` eventfunc, which is a shortcut to obtaining objects. We usually use it to retrieve specific objects with an ID. We put the floors in a dictionary. The keys of the dictionary are the floor number (as str), the values are room objects.
2. Remember, the `message` variable contains the message spoken in the room. So either "1", "2", or "3". We still need to check it, however, because if the character says something like "1 2" in the room, our callback will be executed. Let's be sure what she says is a floor number.
3. We then check if the elevator is already at this floor. Notice that we use `TO_EXIT.location`. `TO_EXIT` contains our "north" exit, leading inside of our elevator. Therefore, its `location` will be the room where the elevator currently is.
4. If the floor is a different one, have the elevator "move", changing just the location and destination of both exits.
- The `BACK_EXIT` (that is "north") should change its location. The elevator shouldn't be accessible through our old floor.
- The `TO_EXIT` (that is "south", the exit leading out of the elevator) should have a different destination. When we go out of the elevator, we should find ourselves in the new floor, not the old one.
1. We first obtain the objects of both exits and our three floors. We use the `get()` eventfunc,
which is a shortcut to obtaining objects. We usually use it to retrieve specific objects with an
ID. We put the floors in a dictionary. The keys of the dictionary are the floor number (as str),
the values are room objects.
2. Remember, the `message` variable contains the message spoken in the room. So either "1", "2", or
"3". We still need to check it, however, because if the character says something like "1 2" in the
room, our callback will be executed. Let's be sure what she says is a floor number.
3. We then check if the elevator is already at this floor. Notice that we use `TO_EXIT.location`.
`TO_EXIT` contains our "north" exit, leading inside of our elevator. Therefore, its `location` will
be the room where the elevator currently is.
4. If the floor is a different one, have the elevator "move", changing just the location and
destination of both exits.
- The `BACK_EXIT` (that is "north") should change its location. The elevator shouldn't be
accessible through our old floor.
- The `TO_EXIT` (that is "south", the exit leading out of the elevator) should have a different
destination. When we go out of the elevator, we should find ourselves in the new floor, not the old
one.
Feel free to expand on this example, changing messages, making further checks. Usage and practice are keys.
Feel free to expand on this example, changing messages, making further checks. Usage and practice
are keys.
You can quit the editor as usual with `:wq` and test it out.
## Adding a pause in our callback
Let's improve our callback. One thing that's worth adding would be a pause: for the time being, when we say the floor number in the elevator, the doors close and open right away. It would be better to have a pause of several seconds. More logical.
Let's improve our callback. One thing that's worth adding would be a pause: for the time being,
when we say the floor number in the elevator, the doors close and open right away. It would be
better to have a pause of several seconds. More logical.
This is a great opportunity to learn about chained events. Chained events are very useful to create pauses. Contrary to the events we have seen so far, chained events aren't called automatically. They must be called by you, and can be called after some time.
This is a great opportunity to learn about chained events. Chained events are very useful to create
pauses. Contrary to the events we have seen so far, chained events aren't called automatically.
They must be called by you, and can be called after some time.
- Chained events always have the name "chain_X". Usually, X is a number, but you can give the chained event a more explicit name.
- Chained events always have the name "chain_X". Usually, X is a number, but you can give the
chained event a more explicit name.
- In our original callback, we will call our chained events in, say, 15 seconds.
- We'll also have to make sure the elevator isn't already moving.
Other than that, a chained event can be connected to a callback as usual. We'll create a chained event in our elevator, that will only contain the code necessary to open the doors to the new floor.
Other than that, a chained event can be connected to a callback as usual. We'll create a chained
event in our elevator, that will only contain the code necessary to open the doors to the new floor.
call/add here = chain_1
The callback is added to the "chain_1" event, an event that will not be automatically called by the system when something happens. Inside this event, you can paste the code to open the doors at the new floor. You can notice a few differences:
The callback is added to the "chain_1" event, an event that will not be automatically called by the
system when something happens. Inside this event, you can paste the code to open the doors at the
new floor. You can notice a few differences:
```python
TO_EXIT.location = floor
@ -248,20 +315,37 @@ else:
What changed?
1. We added a little test to make sure the elevator wasn't already moving. If it is, the `BACK_EXIT.location` (the "south" exit leading out of the elevator) should be `None`. We'll remove the exit while the elevator is moving.
2. When the doors close, we set both exits' `location` to `None`. Which "removes" them from their room but doesn't destroy them. The exits still exist but they don't connect anything. If you say "2" in the elevator and look around while the elevator is moving, you won't see any exits.
3. Instead of opening the doors immediately, we call `call_event`. We give it the object containing the event to be called (here, our elevator), the name of the event to be called (here, "chain_1") and the number of seconds from now when the event should be called (here, `15`).
4. The `chain_1` callback we have created contains the code to "re-open" the elevator doors. That is, besides displaying a message, it reset the exits' `location` and `destination`.
1. We added a little test to make sure the elevator wasn't already moving. If it is, the
`BACK_EXIT.location` (the "south" exit leading out of the elevator) should be `None`. We'll remove
the exit while the elevator is moving.
2. When the doors close, we set both exits' `location` to `None`. Which "removes" them from their
room but doesn't destroy them. The exits still exist but they don't connect anything. If you say
"2" in the elevator and look around while the elevator is moving, you won't see any exits.
3. Instead of opening the doors immediately, we call `call_event`. We give it the object containing
the event to be called (here, our elevator), the name of the event to be called (here, "chain_1")
and the number of seconds from now when the event should be called (here, `15`).
4. The `chain_1` callback we have created contains the code to "re-open" the elevator doors. That
is, besides displaying a message, it reset the exits' `location` and `destination`.
If you try to say "3" in the elevator, you should see the doors closing. Look around you and you won't see any exit. Then, 15 seconds later, the doors should open, and you can leave the elevator to go to the third floor. While the elevator is moving, the exit leading to it will be inaccessible.
If you try to say "3" in the elevator, you should see the doors closing. Look around you and you
won't see any exit. Then, 15 seconds later, the doors should open, and you can leave the elevator
to go to the third floor. While the elevator is moving, the exit leading to it will be
inaccessible.
> Note: we don't define the variables again in our chained event, we just call them. When we execute `call_event`, a copy of our current variables is placed in the database. These variables will be restored and accessible again when the chained event is called.
> Note: we don't define the variables again in our chained event, we just call them. When we
execute `call_event`, a copy of our current variables is placed in the database. These variables
will be restored and accessible again when the chained event is called.
You can use the `call/tasks` command to see the tasks waiting to be executed. For instance, say "2" in the room, notice the doors closing, and then type the `call/tasks` command. You will see a task in the elevator, waiting to call the `chain_1` event.
You can use the `call/tasks` command to see the tasks waiting to be executed. For instance, say "2"
in the room, notice the doors closing, and then type the `call/tasks` command. You will see a task
in the elevator, waiting to call the `chain_1` event.
## Changing exit messages
Here's another nice little feature of events: you can modify the message of a single exit without altering the others. In this case, when someone goes north into our elevator, we'd like to see something like: "someone walks into the elevator." Something similar for the back exit would be great too.
Here's another nice little feature of events: you can modify the message of a single exit without
altering the others. In this case, when someone goes north into our elevator, we'd like to see
something like: "someone walks into the elevator." Something similar for the back exit would be
great too.
Inside of the elevator, you can look at the available events on the exit leading outside (south).
@ -276,11 +360,14 @@ You should see two interesting rows in this table:
| | | through this exit. |
```
So we can change the message others see when a character leaves, by editing the "msg_leave" event. Let's do that:
So we can change the message others see when a character leaves, by editing the "msg_leave" event.
Let's do that:
call/add south = msg_leave
Take the time to read the help. It gives you all the information you should need. We'll need to change the "message" variable, and use custom mapping (between braces) to alter the message. We're given an example, let's use it. In the code editor, you can paste the following line:
Take the time to read the help. It gives you all the information you should need. We'll need to
change the "message" variable, and use custom mapping (between braces) to alter the message. We're
given an example, let's use it. In the code editor, you can paste the following line:
```python
message = "{character} walks out of the elevator."
@ -295,11 +382,13 @@ Again, save and quit the editor by entering `:wq`. You can create a new charact
py self.search("beggar").move_to(self.search("south"))
This is a crude way to force our beggar out of the elevator, but it allows us to test. You should see:
This is a crude way to force our beggar out of the elevator, but it allows us to test. You should
see:
A beggar(#8) walks out of the elevator.
Great! Let's do the same thing for the exit leading inside of the elevator. Follow the beggar, then edit "msg_leave" of "north":
Great! Let's do the same thing for the exit leading inside of the elevator. Follow the beggar,
then edit "msg_leave" of "north":
call/add north = msg_leave
@ -307,21 +396,41 @@ Great! Let's do the same thing for the exit leading inside of the elevator. Fo
message = "{character} walks into the elevator."
```
Again, you can force our beggar to move and see the message we have just set. This modification applies to these two exits, obviously: the custom message won't be used for other exits. Since we use the same exits for every floor, this will be available no matter at what floor the elevator is, which is pretty neat!
Again, you can force our beggar to move and see the message we have just set. This modification
applies to these two exits, obviously: the custom message won't be used for other exits. Since we
use the same exits for every floor, this will be available no matter at what floor the elevator is,
which is pretty neat!
## Tutorial F.A.Q.
- **Q:** what happens if the game reloads or shuts down while a task is waiting to happen?
- **A:** if your game reloads while a task is in pause (like our elevator between floors), when the game is accessible again, the task will be called (if necessary, with a new time difference to take into account the reload). If the server shuts down, obviously, the task will not be called, but will be stored and executed when the server is up again.
- **A:** if your game reloads while a task is in pause (like our elevator between floors), when the
game is accessible again, the task will be called (if necessary, with a new time difference to take
into account the reload). If the server shuts down, obviously, the task will not be called, but
will be stored and executed when the server is up again.
- **Q:** can I use all kinds of variables in my callback? Whether chained or not?
- **A:** you can use every variable type you like in your original callback. However, if you execute `call_event`, since your variables are stored in the database, they will need to respect the constraints on persistent attributes. A callback will not be stored in this way, for instance. This variable will not be available in your chained event.
- **Q:** when you say I can call my chained events something else than "chain_1", "chain_2" and such, what is the naming convention?
- **A:** chained events have names beginning by "chain_". This is useful for you and for the system. But after the underscore, you can give a more useful name, like "chain_open_doors" in our case.
- **A:** you can use every variable type you like in your original callback. However, if you
execute `call_event`, since your variables are stored in the database, they will need to respect the
constraints on persistent attributes. A callback will not be stored in this way, for instance.
This variable will not be available in your chained event.
- **Q:** when you say I can call my chained events something else than "chain_1", "chain_2" and
such, what is the naming convention?
- **A:** chained events have names beginning by "chain_". This is useful for you and for the
system. But after the underscore, you can give a more useful name, like "chain_open_doors" in our
case.
- **Q:** do I have to pause several seconds to call a chained event?
- **A:** no, you can call it right away. Just leave the third parameter of `call_event` out (it will default to 0, meaning the chained event will be called right away). This will not create a task.
- **A:** no, you can call it right away. Just leave the third parameter of `call_event` out (it
will default to 0, meaning the chained event will be called right away). This will not create a
task.
- **Q:** can I have chained events calling themselves?
- **A:** you can. There's no limitation. Just be careful, a callback that calls itself, particularly without delay, might be a good recipe for an infinite loop. However, in some cases, it is useful to have chained events calling themselves, to do the same repeated action every X seconds for instance.
- **A:** you can. There's no limitation. Just be careful, a callback that calls itself,
particularly without delay, might be a good recipe for an infinite loop. However, in some cases, it
is useful to have chained events calling themselves, to do the same repeated action every X seconds
for instance.
- **Q:** what if I need several elevators, do I need to copy/paste these callbacks each time?
- **A:** not advisable. There are definitely better ways to handle this situation. One of them is to consider adding the code in the source itself. Another possibility is to call chained events with the expected behavior, which makes porting code very easy. This side of chained events will be shown in the next tutorial.
- **A:** not advisable. There are definitely better ways to handle this situation. One of them is
to consider adding the code in the source itself. Another possibility is to call chained events
with the expected behavior, which makes porting code very easy. This side of chained events will be
shown in the next tutorial.
- Previous tutorial: [Adding dialogues in events](Dialogues-in-events)
- Previous tutorial: [Adding dialogues in events](Dialogues-in-events)

View file

@ -1,20 +1,33 @@
# API refactoring
Building up to Evennia 1.0 and beyond, it's time to comb through the Evennia API for old cruft. This whitepage is for anyone interested to contribute with their views on what part of the API needs refactoring, cleanup or clarification (or extension!)
Building up to Evennia 1.0 and beyond, it's time to comb through the Evennia API for old cruft. This
whitepage is for anyone interested to contribute with their views on what part of the API needs
refactoring, cleanup or clarification (or extension!)
Note that this is not a forum. To keep things clean, each opinion text should ideally present a clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be held in chat or forum.
Note that this is not a forum. To keep things clean, each opinion text should ideally present a
clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be
held in chat or forum.
---
### Griatch (Aug 13, 2019)
This is how to enter an opinion. Use any markdown needed but stay within your section. Also remember to copy your text to the clipboard before saving since if someone else edited the wiki in the meantime you'll have to start over.
This is how to enter an opinion. Use any markdown needed but stay within your section. Also remember
to copy your text to the clipboard before saving since if someone else edited the wiki in the
meantime you'll have to start over.
### Griatch (Sept 2, 2019)
I don't agree with removing explicit keywords as suggested by [Johnny on Aug 29 below](API-refactoring#reduce-usage-of-optionalpositional-arguments-aug-29-2019). Overriding such a method can still be done by `get(self, **kwargs)` if so desired, making the kwargs explicit helps IMO readability of the API. If just giving a generic `**kwargs`, one must read the docstring or even the code to see which keywords are valid.
I don't agree with removing explicit keywords as suggested by [Johnny on Aug 29 below](API-
refactoring#reduce-usage-of-optionalpositional-arguments-aug-29-2019). Overriding such a method can
still be done by `get(self, **kwargs)` if so desired, making the kwargs explicit helps IMO
readability of the API. If just giving a generic `**kwargs`, one must read the docstring or even the
code to see which keywords are valid.
On the other hand, I think it makes sense to as a standard offer an extra `**kwargs` at the end of arg-lists for common methods that are expected to be over-ridden. This make the API more flexible by hinting to the dev that they could expand their own over-ridden implementation with their own keyword arguments if so desired.
On the other hand, I think it makes sense to as a standard offer an extra `**kwargs` at the end of
arg-lists for common methods that are expected to be over-ridden. This make the API more flexible by
hinting to the dev that they could expand their own over-ridden implementation with their own
keyword arguments if so desired.
---
@ -27,4 +40,7 @@ def get(self, key=None, default=None, category=None, return_obj=False,
strattr=False, raise_exception=False, accessing_obj=None,
default_access=True, return_list=False):
```
Many classes have methods requiring lengthy positional argument lists, which are tedious and error-prone to extend and override especially in cases where not all arguments are even required. It would be useful if arguments were reserved for required inputs and anything else relegated to kwargs for easier passthrough on extension.
Many classes have methods requiring lengthy positional argument lists, which are tedious and error-
prone to extend and override especially in cases where not all arguments are even required. It would
be useful if arguments were reserved for required inputs and anything else relegated to kwargs for
easier passthrough on extension.

View file

@ -85,7 +85,8 @@ Account also has the following custom properties:
- `sessions` - an instance of
[ObjectSessionHandler](github:evennia.objects.objects#objectsessionhandler)
managing all connected Sessions (physical connections) this object listens to (Note: In older
versions of Evennia, this was a list). The so-called `session-id` (used in many places) is found as
versions of Evennia, this was a list). The so-called `session-id` (used in many places) is found
as
a property `sessid` on each Session instance.
- `is_superuser` (bool: True/False) - if this account is a superuser.
@ -93,7 +94,8 @@ Special handlers:
- `cmdset` - This holds all the current [Commands](Commands) of this Account. By default these are
the commands found in the cmdset defined by `settings.CMDSET_ACCOUNT`.
- `nicks` - This stores and handles [Nicks](Nicks), in the same way as nicks it works on Objects.
For Accounts, nicks are primarily used to store custom aliases for [Channels](Communications#Channels).
For Accounts, nicks are primarily used to store custom aliases for
[Channels](Communications#Channels).
Selection of special methods (see `evennia.DefaultAccount` for details):
- `get_puppet` - get a currently puppeted object connected to the Account and a given session id, if
@ -102,4 +104,4 @@ Selection of special methods (see `evennia.DefaultAccount` for details):
- `unpuppet_object` - disconnect a session from a puppetable Object.
- `msg` - send text to the Account
- `execute_cmd` - runs a command as if this Account did it.
- `search` - search for Accounts.
- `search` - search for Accounts.

View file

@ -97,4 +97,4 @@ instance. The first argument to `url` is the pattern of the url we want to find
a regular expression if you are familiar with those) and then our view function we want to direct
to.
That should be it. Reload Evennia and you should be able to browse to your new story page!
That should be it. Reload Evennia and you should be able to browse to your new story page!

View file

@ -10,9 +10,12 @@ Fortunately, you don't have to create the features manually, since it has been d
we can integrate their work quite easily with Django. I have decided to focus on
the [Django-wiki](http://django-wiki.readthedocs.io/).
> Note: this article has been updated for Evennia 0.9. If you're not yet using this version, be careful, as the django wiki doesn't support Python 2 anymore. (Remove this note when enough time has passed.)
> Note: this article has been updated for Evennia 0.9. If you're not yet using this version, be
careful, as the django wiki doesn't support Python 2 anymore. (Remove this note when enough time
has passed.)
The [Django-wiki](http://django-wiki.readthedocs.io/) offers a lot of features associated with wikis, is
The [Django-wiki](http://django-wiki.readthedocs.io/) offers a lot of features associated with
wikis, is
actively maintained (at this time, anyway), and isn't too difficult to install in Evennia. You can
see a [demonstration of Django-wiki here](https://demo.django.wiki).
@ -37,14 +40,17 @@ Install the wiki using pip:
pip install wiki
> Note: this will install the last version of Django wiki. Version >0.4 doesn't support Python 2, so install wiki 0.3 if you haven't updated to Python 3 yet.
> Note: this will install the last version of Django wiki. Version >0.4 doesn't support Python 2, so
install wiki 0.3 if you haven't updated to Python 3 yet.
It might take some time, the Django-wiki having some dependencies.
### Adding the wiki in the settings
You will need to add a few settings to have the wiki app on your website. Open your
`server/conf/settings.py` file and add the following at the bottom (but before importing `secret_settings`). Here's what you'll find in my own setting file (add the whole Django-wiki section):
`server/conf/settings.py` file and add the following at the bottom (but before importing
`secret_settings`). Here's what you'll find in my own setting file (add the whole Django-wiki
section):
```python
r"""
@ -144,7 +150,8 @@ who can write, a specific article.
These settings must be placed, as usual, in your `server/conf/settings.py` file. They take a
function as argument, said function (or callback) will be called with the article and the user.
Remember, a Django user, for us, is an account. So we could check lockstrings on them if needed.
Here is a default setting to restrict the wiki: only builders can write in it, but anyone (including non-logged in users) can read it. The superuser has some additional privileges.
Here is a default setting to restrict the wiki: only builders can write in it, but anyone (including
non-logged in users) can read it. The superuser has some additional privileges.
```python
# In server/conf/settings.py
@ -181,7 +188,9 @@ WIKI_CAN_READ = is_anyone
```
Here, we have created three functions: one to return `True` if the user is the superuser, one to
return `True` if the user is a builder, one to return `True` no matter what (this includes if the user is anonymous, E.G. if it's not logged-in). We then change settings to allow either the superuser or
return `True` if the user is a builder, one to return `True` no matter what (this includes if the
user is anonymous, E.G. if it's not logged-in). We then change settings to allow either the
superuser or
each builder to moderate, read, write, delete, and more. You can, of course, add more functions,
adapting them to your need. This is just a demonstration.
@ -191,21 +200,33 @@ need something more custom, you will have to expand on the functions you use.
### Managing wiki pages from Evennia
Unfortunately, Django wiki doesn't provide a clear and clean entry point to read and write articles from Evennia and it doesn't seem to be a very high priority. If you really need to keep Django wiki and to create and manage wiki pages from your code, you can do so, but this article won't elaborate, as this is somewhat more technical.
Unfortunately, Django wiki doesn't provide a clear and clean entry point to read and write articles
from Evennia and it doesn't seem to be a very high priority. If you really need to keep Django wiki
and to create and manage wiki pages from your code, you can do so, but this article won't elaborate,
as this is somewhat more technical.
However, it is a good opportunity to present a small project that has been created more recently: [evennia-wiki](https://github.com/vincent-lg/evennia-wiki) has been created to provide a simple wiki, more tailored to Evennia and easier to connect. It doesn't, as yet, provide as many options as does Django wiki, but it's perfectly usable:
However, it is a good opportunity to present a small project that has been created more recently:
[evennia-wiki](https://github.com/vincent-lg/evennia-wiki) has been created to provide a simple
wiki, more tailored to Evennia and easier to connect. It doesn't, as yet, provide as many options
as does Django wiki, but it's perfectly usable:
- Pages have an inherent and much-easier to understand hierarchy based on URLs.
- Article permissions are connected to Evennia groups and are much easier to accommodate specific requirements.
- Article permissions are connected to Evennia groups and are much easier to accommodate specific
requirements.
- Articles can easily be created, read or updated from the Evennia code itself.
- Markdown is fully-supported with a default integration to Bootstrap to look good on an Evennia website. Tables and table of contents are supported as well as wiki links.
- Markdown is fully-supported with a default integration to Bootstrap to look good on an Evennia
website. Tables and table of contents are supported as well as wiki links.
- The process to override wiki templates makes full use of the `template_overrides` directory.
However evennia-wiki doesn't yet support:
- Images in markdown and the uploading schema. If images are important to you, please consider contributing to this new project.
- Images in markdown and the uploading schema. If images are important to you, please consider
contributing to this new project.
- Modifying permissions on a per page/setting basis.
- Moving pages to new locations.
- Viewing page history.
Considering the list of features in Django wiki, obviously other things could be added to the list. However, these features may be the most important and useful. Additional ones might not be that necessary. If you're interested in supporting this little project, you are more than welcome to [contribute to it](https://github.com/vincent-lg/evennia-wiki). Thanks!
Considering the list of features in Django wiki, obviously other things could be added to the list.
However, these features may be the most important and useful. Additional ones might not be that
necessary. If you're interested in supporting this little project, you are more than welcome to
[contribute to it](https://github.com/vincent-lg/evennia-wiki). Thanks!

View file

@ -3,15 +3,19 @@
This is a quick first-time tutorial expanding on the [Commands](Commands) documentation.
Let's assume you have just downloaded Evennia, installed it and created your game folder (let's call
it just `mygame` here). Now you want to try to add a new command. This is the fastest way to do it.
it just `mygame` here). Now you want to try to add a new command. This is the fastest way to do it.
## Step 1: Creating a custom command
1. Open `mygame/commands/command.py` in a text editor. This is just one place commands could be placed but you get it setup from the onset as an easy place to start. It also already contains some example code.
1. Open `mygame/commands/command.py` in a text editor. This is just one place commands could be
placed but you get it setup from the onset as an easy place to start. It also already contains some
example code.
1. Create a new class in `command.py` inheriting from `default_cmds.MuxCommand`. Let's call it
`CmdEcho` in this example.
1. Set the class variable `key` to a good command name, like `echo`.
1. Give your class a useful _docstring_. A docstring is the string at the very top of a class or function/method. The docstring at the top of the command class is read by Evennia to become the help entry for the Command (see
1. Give your class a useful _docstring_. A docstring is the string at the very top of a class or
function/method. The docstring at the top of the command class is read by Evennia to become the help
entry for the Command (see
[Command Auto-help](Help-System#command-auto-help-system)).
1. Define a class method `func(self)` that echoes your input back to you.
@ -79,8 +83,11 @@ the game. Use `help echo` to see the documentation for the command.
If you have trouble, make sure to check the log for error messages (probably due to syntax errors in
your command definition).
> Note: Typing `echotest` will also work. It will be handled as the command `echo` directly followed by
its argument `test` (which will end up in `self.args). To change this behavior, you can add the `arg_regex` property alongside `key`, `help_category` etc. [See the arg_regex documentation](Commands#on-arg_regex) for more info.
> Note: Typing `echotest` will also work. It will be handled as the command `echo` directly followed
by
its argument `test` (which will end up in `self.args). To change this behavior, you can add the
`arg_regex` property alongside `key`, `help_category` etc. [See the arg_regex
documentation](Commands#on-arg_regex) for more info.
If you want to overload existing default commands (such as `look` or `get`), just add your new
command with the same key as the old one - it will then replace it. Just remember that you must use
@ -145,7 +152,8 @@ using that typeclass, they will not have been initiated the same way. There are
them; since it's a one-time update you can usually just simply loop through them. As superuser, try
the following:
@py from typeclasses.objects import MyObject; [o.cmdset.add("mycmdset.MyCmdSet") for o in MyObject.objects.all()]
@py from typeclasses.objects import MyObject; [o.cmdset.add("mycmdset.MyCmdSet") for o in
MyObject.objects.all()]
This goes through all objects in your database having the right typeclass, adding the new cmdset to
each. The good news is that you only have to do this if you want to post-add *cmdsets*. If you just
@ -160,4 +168,4 @@ default character cmdset defaults to being defined as
CMDSET_CHARACTER="commands.default_cmdset.CharacterCmdSet"
See `evennia/settings_default.py` for the other settings.
See `evennia/settings_default.py` for the other settings.

View file

@ -20,7 +20,7 @@ are found `mygame/typeclasses/objects.py`, `mygame/typeclasses/rooms.py` etc.
For your own game you will most likely want to expand on these very simple beginnings. It's normal
to want your Characters to have various attributes, for example. Maybe Rooms should hold extra
information or even *all* Objects in your game should have properties not included in basic Evennia.
information or even *all* Objects in your game should have properties not included in basic Evennia.
## Change Default Rooms, Exits, Character Typeclass
@ -106,4 +106,4 @@ objects you can use `@py` to loop over the objects you need:
```
@py from typeclasses.objects import Heavy; [obj.at_object_creation() for obj in Heavy.objects.all()]
```
```

View file

@ -1,6 +1,7 @@
# Administrative Docs
The following pages are aimed at game administrators -- the higher-ups that possess shell access and are responsible for managing the game.
The following pages are aimed at game administrators -- the higher-ups that possess shell access and
are responsible for managing the game.
### Installation and Early Life
@ -13,12 +14,14 @@ The following pages are aimed at game administrators -- the higher-ups that poss
- [Making your game available online](Online-Setup)
- [Hosting options](Online-Setup#hosting-options)
- [Securing your server with SSL/Let's Encrypt](Online-Setup#ssl)
- [Listing your game](Evennia-Game-Index) at the online [Evennia game index](http://games.evennia.com)
- [Listing your game](Evennia-Game-Index) at the online [Evennia game
index](http://games.evennia.com)
### Customizing the server
- [Changing the Settings](Server-Conf#Settings-file)
- [Available Master Settings](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py)
- [Available Master
Settings](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py)
- [Change Evennia's language](Internationalization) (internationalization)
- [Apache webserver configuration](Apache-Config) (optional)
- [Changing text encodings used by the server](Text-Encodings)
@ -40,4 +43,4 @@ The following pages are aimed at game administrators -- the higher-ups that poss
- [Setting up your work environment with version control](Version-Control)
- [First steps coding with Evennia](First-Steps-Coding)
- [Setting up a continuous integration build environment](Continuous-Integration)
- [Setting up a continuous integration build environment](Continuous-Integration)

View file

@ -19,13 +19,15 @@ you would like covered, please let us know.
### Install `mod_wsgi`
- *Fedora/RHEL* - Apache HTTP Server and `mod_wsgi` are available in the standard package repositories for Fedora and RHEL:
- *Fedora/RHEL* - Apache HTTP Server and `mod_wsgi` are available in the standard package
repositories for Fedora and RHEL:
```
$ dnf install httpd mod_wsgi
or
$ yum install httpd mod_wsgi
```
- *Ubuntu/Debian* - Apache HTTP Server and `mod_wsgi` are available in the standard package repositories for Ubuntu and Debian:
- *Ubuntu/Debian* - Apache HTTP Server and `mod_wsgi` are available in the standard package
repositories for Ubuntu and Debian:
```
$ apt-get update
$ apt-get install apache2 libapache2-mod-wsgi
@ -67,7 +69,8 @@ Ubuntu), you may tell `mod_wsgi` to reload by using the `touch` command on
changed, it will force a code reload. Any modifications to the code will not be propagated to the
live instance of your site until reloaded.
If you are not running in daemon mode or want to force the issue, simply restart or reload apache2 to apply your changes.
If you are not running in daemon mode or want to force the issue, simply restart or reload apache2
to apply your changes.
### Further notes and hints:
@ -87,20 +90,24 @@ Not confirmed, but worth trying if there are trouble.
## `mod_proxy` and `mod_ssl` setup
Below are steps on running Evennia using a front-end proxy (Apache HTTP), `mod_proxy_http`,
`mod_proxy_wstunnel`, and `mod_ssl`. `mod_proxy_http` and `mod_proxy_wstunnel` will simply be referred to as
`mod_proxy_wstunnel`, and `mod_ssl`. `mod_proxy_http` and `mod_proxy_wstunnel` will simply be
referred to as
`mod_proxy` below.
### Install `mod_ssl`
- *Fedora/RHEL* - Apache HTTP Server and `mod_ssl` are available in the standard package repositories for Fedora and RHEL:
- *Fedora/RHEL* - Apache HTTP Server and `mod_ssl` are available in the standard package
repositories for Fedora and RHEL:
```
$ dnf install httpd mod_ssl
or
$ yum install httpd mod_ssl
```
- *Ubuntu/Debian* - Apache HTTP Server and `mod_sslj`kl are installed together in the `apache2` package and available in the
standard package repositories for Ubuntu and Debian. `mod_ssl` needs to be enabled after installation:
- *Ubuntu/Debian* - Apache HTTP Server and `mod_sslj`kl are installed together in the `apache2`
package and available in the
standard package repositories for Ubuntu and Debian. `mod_ssl` needs to be enabled after
installation:
```
$ apt-get update
$ apt-get install apache2
@ -161,4 +168,4 @@ The setting above is what the client's browser will actually use. Note the use o
because our client will be communicating over an encrypted connection ("wss" indicates websocket
over SSL/TLS). Also, especially note the additional path `/ws` at the end of the URL. This is how
Apache HTTP Server identifies that a particular request should be proxied to Evennia's websocket
port but this should be applicable also to other types of proxies (like nginx).
port but this should be applicable also to other types of proxies (like nginx).

View file

@ -22,8 +22,12 @@ better match with the vanilla Evennia install.
Firstly, set aside a folder/directory on your drive for everything to follow.
You need to start by installing [Evennia](http://www.evennia.com) by following most of the [Getting Started
Instructions](Getting-Started) for your OS. The difference is that you need to `git clone https://github.com/TehomCD/evennia.git` instead of Evennia's repo because Arx uses TehomCD's older Evennia 0.8 [fork](https://github.com/TehomCD/evennia), notably still using Python2. This detail is important if referring to newer Evennia documentation.
You need to start by installing [Evennia](http://www.evennia.com) by following most of the [Getting
Started
Instructions](Getting-Started) for your OS. The difference is that you need to `git clone
https://github.com/TehomCD/evennia.git` instead of Evennia's repo because Arx uses TehomCD's older
Evennia 0.8 [fork](https://github.com/TehomCD/evennia), notably still using Python2. This detail is
important if referring to newer Evennia documentation.
If you are new to Evennia it's *highly* recommended that you run through the
instructions in full - including initializing and starting a new empty game and connecting to it.
@ -33,7 +37,8 @@ operating system. You can also drop into our
[forums](https://groups.google.com/forum/#%21forum/evennia), join `#evennia` on `irc.freenode.net`
or chat from the linked [Discord Server](https://discord.gg/NecFePw).
After installing you should have a `virtualenv` running and you should have the following file structure in your set-aside folder:
After installing you should have a `virtualenv` running and you should have the following file
structure in your set-aside folder:
```
vienv/
@ -58,7 +63,8 @@ Cd to the root of your directory and clone the released source code from github:
A new folder `myarx` should appear next to the ones you already had. You could rename this to
something else if you want.
Cd into `myarx`. If you wonder about the structure of the game dir, you can [read more about it here](Directory-Overview).
Cd into `myarx`. If you wonder about the structure of the game dir, you can [read more about it
here](Directory-Overview).
### Clean up settings
@ -84,7 +90,8 @@ except ImportError:
```
> Note: Indents and capitalization matter in Python. Make indents 4 spaces (not tabs) for your own
> sanity. If you want a starter on Python in Evennia, [you can look here](Python-basic-introduction).
> sanity. If you want a starter on Python in Evennia, [you can look here](Python-basic-
introduction).
This will import Arx' base settings and override them with the Evennia-default telnet port and give
the game a name. The slogan changes the sub-text shown under the name of your game in the website
@ -172,7 +179,8 @@ run steps 7-8 and 10 to create and connect to your in-came Character.
account. Move to where you want the new staffer character to appear.
8. In the game client, run `@create/drop <staffername>:typeclasses.characters.Character`, where
`<staffername>` is usually the same name you used for the Staffer account you created in the
Admin earlier (if you are creating a Character for your superuser, use your superuser account name).
Admin earlier (if you are creating a Character for your superuser, use your superuser account
name).
This creates a new in-game Character and places it in your current location.
9. Have the new Admin player log into the game.
10. Have the new Admin puppet the character with `@ic StafferName`.
@ -182,8 +190,10 @@ Now that you have a Character and an Account object, there's a few additional th
do in order for some commands to function properly. You can either execute these as in-game commands
while `@ic` (controlling your character object).
1. `@py from web.character.models import RosterEntry;RosterEntry.objects.create(player=self.player, character=self)`
2. `@py from world.dominion.models import PlayerOrNpc, AssetOwner;dompc = PlayerOrNpc.objects.create(player = self.player);AssetOwner.objects.create(player=dompc)`
1. `@py from web.character.models import RosterEntry;RosterEntry.objects.create(player=self.player,
character=self)`
2. `@py from world.dominion.models import PlayerOrNpc, AssetOwner;dompc =
PlayerOrNpc.objects.create(player = self.player);AssetOwner.objects.create(player=dompc)`
Those steps will give you a 'RosterEntry', 'PlayerOrNpc', and 'AssetOwner' objects. RosterEntry
explicitly connects a character and account object together, even while offline, and contains
@ -195,7 +205,9 @@ AssetOwner holds information about a character or organization's money and resou
## Alternate guide by Pax for installing on Windows
If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions identical to the ones above), it's possible to get Evennia running under Anaconda for Windows. The process is a little bit trickier.
If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions
identical to the ones above), it's possible to get Evennia running under Anaconda for Windows. The
process is a little bit trickier.
Make sure you have:
* Git for Windows https://git-scm.com/download/win

View file

@ -75,7 +75,8 @@ line quite pointless for processing any data from the function. Instead one has
print(r)
```
- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the `at_return` callback.
- `at_return_kwargs` - an optional dictionary that will be fed as keyword arguments to the
`at_return` callback.
- `at_err(e)` (the *errback*) is called if the asynchronous function fails and raises an exception.
This exception is passed to the errback wrapped in a *Failure* object `e`. If you do not supply an
errback of your own, Evennia will automatically add one that silently writes errors to the evennia
@ -111,11 +112,13 @@ An example of making an asynchronous call from inside a [Command](Commands) defi
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)
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
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
@ -147,7 +150,8 @@ 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/)
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.
@ -223,7 +227,8 @@ 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
[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.
your own liking.

View file

@ -15,7 +15,8 @@ specific names and require very specific types of data (for example you couldn't
*list* to the `key` property no matter how hard you tried). `Attributes` come into play when you
want to assign arbitrary data to arbitrary names.
**Attributes are _not_ secure by default and any player may be able to change them unless you [prevent this behavior](Attributes#locking-and-checking-attributes).**
**Attributes are _not_ secure by default and any player may be able to change them unless you
[prevent this behavior](Attributes#locking-and-checking-attributes).**
## The .db and .ndb shortcuts
@ -52,7 +53,8 @@ is tracked by the server and will not be purged in various cache-cleanup operati
while it runs. Data stored on `ndb` (as well as `db`) will also be easily listed by example the
`@examine` command.
You can also `del` properties on `db` and `ndb` as normal. This will for example delete an `Attribute`:
You can also `del` properties on `db` and `ndb` as normal. This will for example delete an
`Attribute`:
```python
del rose.db.has_thorns
@ -116,12 +118,15 @@ An Attribute object is stored in the database. It has the following properties:
to `attrname`.
- `value` - this is the value of the Attribute. This value can be anything which can be pickled -
objects, lists, numbers or what have you (see
[this section](Attributes#What_types_of_data_can_I_save_in_an_Attribute) for more info). In the example
[this section](Attributes#What_types_of_data_can_I_save_in_an_Attribute) for more info). In the
example
`obj.db.attrname = value`, the `value` is stored here.
- `category` - this is an optional property that is set to None for most Attributes. Setting this
allows to use Attributes for different functionality. This is usually not needed unless you want
to use Attributes for very different functionality ([Nicks](Nicks) is an example of using Attributes
in this way). To modify this property you need to use the [Attribute Handler](Attributes#The_Attribute_Handler).
to use Attributes for very different functionality ([Nicks](Nicks) is an example of using
Attributes
in this way). To modify this property you need to use the [Attribute
Handler](Attributes#The_Attribute_Handler).
- `strvalue` - this is a separate value field that only accepts strings. This severely limits the
data possible to store, but allows for easier database lookups. This property is usually not used
except when re-using Attributes for some other purpose ([Nicks](Nicks) use it). It is only
@ -151,13 +156,15 @@ useful in a few situations though.
this is not an issue unless you are reading *and* writing to your Attribute very often (like many
times per second). Reading from an already cached Attribute is as fast as reading any Python
property. But even then this is not likely something to worry about: Apart from Evennia's own
caching, modern database systems themselves also cache data very efficiently for speed. Our default
caching, modern database systems themselves also cache data very efficiently for speed. Our
default
database even runs completely in RAM if possible, alleviating much of the need to write to disk
during heavy loads.
- A more valid reason for using non-persistent data is if you *want* to lose your state when logging
off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you
are implementing some caching of your own. Or maybe you are testing a buggy [Script](Scripts) that
does potentially harmful stuff to your character object. With non-persistent storage you can be sure
does potentially harmful stuff to your character object. With non-persistent storage you can be
sure
that whatever is messed up, it's nothing a server reboot can't clear up.
- NAttributes have no restrictions at all on what they can store (see next section), since they
don't need to worry about being saved to the database - they work very well for temporary storage.
@ -185,11 +192,13 @@ not a big deal. But if you are accessing the Attribute as part of some big loop
amount of reads/writes you should first extract it to a temporary variable, operate on *that* and
then save the result back to the Attribute. If you are storing a more complex structure like a
`dict` or a `list` you should make sure to "disconnect" it from the database before looping over it,
as mentioned in the [Retrieving Mutable Objects](Attributes#retrieving-mutable-objects) section below.
as mentioned in the [Retrieving Mutable Objects](Attributes#retrieving-mutable-objects) section
below.
### Storing single objects
With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class instances without the `__iter__` method.
With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class
instances without the `__iter__` method.
* You can generally store any non-iterable Python entity that can be
[pickled](http://docs.python.org/library/pickle.html).
@ -223,17 +232,24 @@ This means storing objects in a collection of some kind and are examples of *ite
entities you can loop over in a for-loop. Attribute-saving supports the following iterables:
* [Tuples](https://docs.python.org/2/library/functions.html#tuple), like `(1,2,"test", <dbobj>)`.
* [Lists](https://docs.python.org/2/tutorial/datastructures.html#more-on-lists), like `[1,2,"test", <dbobj>]`.
* [Dicts](https://docs.python.org/2/tutorial/datastructures.html#dictionaries), like `{1:2, "test":<dbobj>]`.
* [Lists](https://docs.python.org/2/tutorial/datastructures.html#more-on-lists), like `[1,2,"test",
<dbobj>]`.
* [Dicts](https://docs.python.org/2/tutorial/datastructures.html#dictionaries), like `{1:2,
"test":<dbobj>]`.
* [Sets](https://docs.python.org/2/tutorial/datastructures.html#sets), like `{1,2,"test",<dbobj>}`.
* [collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict), like `OrderedDict((1,2), ("test", <dbobj>))`.
* [collections.Deque](https://docs.python.org/2/library/collections.html#collections.deque), like `deque((1,2,"test",<dbobj>))`.
* *Nestings* of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each containing dicts, etc.
*
[collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict),
like `OrderedDict((1,2), ("test", <dbobj>))`.
* [collections.Deque](https://docs.python.org/2/library/collections.html#collections.deque), like
`deque((1,2,"test",<dbobj>))`.
* *Nestings* of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each
containing dicts, etc.
* All other iterables (i.e. entities with the `__iter__` method) will be converted to a *list*.
Since you can use any combination of the above iterables, this is generally not much of a
limitation.
Any entity listed in the [Single object](Attributes#Storing-Single-Objects) section above can be stored in the iterable.
Any entity listed in the [Single object](Attributes#Storing-Single-Objects) section above can be
stored in the iterable.
> As mentioned in the previous section, database entities (aka typeclasses) are not possible to
> pickle. So when storing an iterable, Evennia must recursively traverse the iterable *and all its
@ -339,7 +355,8 @@ already disconnected from the database from the onset.
Attributes are normally not locked down by default, but you can easily change that for individual
Attributes (like those that may be game-sensitive in games with user-level building).
First you need to set a *lock string* on your Attribute. Lock strings are specified [Locks](Locks). The relevant lock types are
First you need to set a *lock string* on your Attribute. Lock strings are specified [Locks](Locks).
The relevant lock types are
- `attrread` - limits who may read the value of the Attribute
- `attredit` - limits who may set/change this Attribute
@ -375,4 +392,4 @@ Attribute).
```
The same keywords are available to use with `obj.attributes.set()` and `obj.attributes.remove()`,
those will check for the `attredit` lock type.
those will check for the `attredit` lock type.

View file

@ -7,7 +7,8 @@ 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
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.
@ -17,52 +18,70 @@ The easiest recourse is to block the account YouSuck from ever connecting again.
@ban YouSuck
This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and next time they try to log in with this name the server will not let them!
This will lock the name YouSuck (as well as 'yousuck' and any other capitalization combination), and
next time they try to log in with this name the server will not let them!
You can also give a reason so you remember later why this was a good thing (the banned account will never see this)
You can also give a reason so you remember later why this was a good thing (the banned account will
never see this)
@ban YouSuck:This is just a troll.
If you are sure this is just a spam account, you might even consider deleting the player account outright:
If you are sure this is just a spam account, you might even consider deleting the player account
outright:
@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.
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.
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:
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:
The "Host" bit is the IP address from which the account is connecting. Use this to define the ban
instead of the name:
@ban 237.333.0.223
This will stop YouSuckMore connecting from their computer. Note however that IP address might change easily - either due to how the player's Internet Service Provider operates or by the user simply changing computers. You can make a more general ban by putting asterisks `*` as wildcards for the groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from 237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea to put down a ban like this to include any number in that subnet:
This will stop YouSuckMore connecting from their computer. Note however that IP address might change
easily - either due to how the player's Internet Service Provider operates or by the user simply
changing computers. You can make a more general ban by putting asterisks `*` as wildcards for the
groups of three digits in the address. So if you figure out that !YouSuckMore mainly connects from
237.333.0.223, 237.333.0.225, and 237.333.0.256 (only changes in their subnet), it might be an idea
to put down a ban like this to include any number in that subnet:
@ban 237.333.0.*
You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly locked regardless of where they connect from.
You should combine the IP ban with a name-ban too of course, so the account YouSuckMore is truly
locked regardless of where they connect from.
Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be blocking out innocent players who just happen to connect from the same subnet as the offender.
Be careful with too general IP bans however (more asterisks above). If you are unlucky you could be
blocking out innocent players who just happen to connect from the same subnet as the offender.
## Booting
YouSuck is not really noticing all this banning yet though - and won't until having logged out and trying to log back in again. Let's help the troll along.
YouSuck is not really noticing all this banning yet though - and won't until having logged out and
trying to log back in again. Let's help the troll along.
@boot YouSuck
Good riddance. You can give a reason for booting too (to be echoed to the player before getting kicked out).
Good riddance. You can give a reason for booting too (to be echoed to the player before getting
kicked out).
@boot YouSuck:Go troll somewhere else.
### Lifting a ban
Use the `@unban` (or `@ban`) command without any arguments and you will see a list of all currently active bans:
Use the `@unban` (or `@ban`) command without any arguments and you will see a list of all currently
active bans:
Active bans
id name/ip date reason
@ -79,8 +98,11 @@ Use the `id` from this list to find out which ban to lift.
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.
- **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)
@ -91,22 +113,32 @@ Below are other useful commands for dealing with annoying players.
- **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](Locks).
- **clock mychannel = control:perm(Admin);listen:all();send:all()** -- Fine control of access to
your channel using [lock definitions](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](Attributes) or [Tag](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.
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](Attributes) or [Tag](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)
- **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
- **server** -- Show server statistics, such as CPU load, memory usage, and how many objects are
cached
- **time** -- Gives server uptime, runtime, etc
- **reload** -- Reloads the server without disconnecting anyone
- **reset** -- Restarts the server, kicking all connections
- **shutdown** -- Stops the server cold without it auto-starting again
- **py** -- Executes raw Python code, allows for direct inspection of the database and account objects on the fly. For advanced users.
- **py** -- Executes raw Python code, allows for direct inspection of the database and account
objects on the fly. For advanced users.

View file

@ -1,7 +1,9 @@
# Batch Code Processor
For an introduction and motivation to using batch processors, see [here](Batch-Processors). This page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command-Processor).
For an introduction and motivation to using batch processors, see [here](Batch-Processors). This
page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command-
Processor).
## Basic Usage
@ -9,28 +11,51 @@ The batch-code processor is a superuser-only function, invoked by
> @batchcode path.to.batchcodefile
Where `path.to.batchcodefile` is the path to a *batch-code file*. Such a file should have a name ending in "`.py`" (but you shouldn't include that in the path). The path is given like a python path relative to a folder you define to hold your batch files, set by `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is called "mygame") `mygame/world/`. So if you want to run the example batch file in `mygame/world/batch_code.py`, you could simply use
Where `path.to.batchcodefile` is the path to a *batch-code file*. Such a file should have a name
ending in "`.py`" (but you shouldn't include that in the path). The path is given like a python path
relative to a folder you define to hold your batch files, set by `BATCH_IMPORT_PATH` in your
settings. Default folder is (assuming your game is called "mygame") `mygame/world/`. So if you want
to run the example batch file in `mygame/world/batch_code.py`, you could simply use
> @batchcode batch_code
This will try to run through the entire batch file in one go. For more gradual, *interactive* control you can use the `/interactive` switch. The switch `/debug` will put the processor in *debug* mode. Read below for more info.
This will try to run through the entire batch file in one go. For more gradual, *interactive*
control you can use the `/interactive` switch. The switch `/debug` will put the processor in
*debug* mode. Read below for more info.
## The batch file
A batch-code file is a normal Python file. The difference is that since the batch processor loads and executes the file rather than importing it, you can reliably update the file, then call it again, over and over and see your changes without needing to `@reload` the server. This makes for easy testing. In the batch-code file you have also access to the following global variables:
A batch-code file is a normal Python file. The difference is that since the batch processor loads
and executes the file rather than importing it, you can reliably update the file, then call it
again, over and over and see your changes without needing to `@reload` the server. This makes for
easy testing. In the batch-code file you have also access to the following global variables:
- `caller` - This is a reference to the object running the batchprocessor.
- `DEBUG` - This is a boolean that lets you determine if this file is currently being run in debug-mode or not. See below how this can be useful.
- `DEBUG` - This is a boolean that lets you determine if this file is currently being run in debug-
mode or not. See below how this can be useful.
Running a plain Python file through the processor will just execute the file from beginning to end. If you want to get more control over the execution you can use the processor's *interactive* mode. This runs certain code blocks on their own, rerunning only that part until you are happy with it. In order to do this you need to add special markers to your file to divide it up into smaller chunks. These take the form of comments, so the file remains valid Python.
Running a plain Python file through the processor will just execute the file from beginning to end.
If you want to get more control over the execution you can use the processor's *interactive* mode.
This runs certain code blocks on their own, rerunning only that part until you are happy with it. In
order to do this you need to add special markers to your file to divide it up into smaller chunks.
These take the form of comments, so the file remains valid Python.
Here are the rules of syntax of the batch-code `*.py` file.
- `#CODE` as the first on a line marks the start of a *code* block. It will last until the beginning of another marker or the end of the file. Code blocks contain functional python code. Each `#CODE` block will be run in complete isolation from other parts of the file, so make sure it's self-contained.
- `#HEADER` as the first on a line marks the start of a *header* block. It lasts until the next marker or the end of the file. This is intended to hold imports and variables you will need for all other blocks .All python code defined in a header block will always be inserted at the top of every `#CODE` blocks in the file. You may have more than one `#HEADER` block, but that is equivalent to having one big one. Note that you can't exchange data between code blocks, so editing a header-variable in one code block won't affect that variable in any other code block!
- `#CODE` as the first on a line marks the start of a *code* block. It will last until the beginning
of another marker or the end of the file. Code blocks contain functional python code. Each `#CODE`
block will be run in complete isolation from other parts of the file, so make sure it's self-
contained.
- `#HEADER` as the first on a line marks the start of a *header* block. It lasts until the next
marker or the end of the file. This is intended to hold imports and variables you will need for all
other blocks .All python code defined in a header block will always be inserted at the top of every
`#CODE` blocks in the file. You may have more than one `#HEADER` block, but that is equivalent to
having one big one. Note that you can't exchange data between code blocks, so editing a header-
variable in one code block won't affect that variable in any other code block!
- `#INSERT path.to.file` will insert another batchcode (Python) file at that position.
- A `#` that is not starting a `#HEADER`, `#CODE` or `#INSERT` instruction is considered a comment.
- Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as a separate python module.
- Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as
a separate python module.
Below is a version of the example file found in `evennia/contrib/tutorial_examples/`.
@ -83,13 +108,23 @@ Try to run the example script with
> @batchcode/debug tutorial_examples.example_batch_code
The batch script will run to the end and tell you it completed. You will also get messages that the button and the two pieces of furniture were created. Look around and you should see the button there. But you won't see any chair nor a table! This is because we ran this with the `/debug` switch, which is directly visible as `DEBUG==True` inside the script. In the above example we handled this state by deleting the chair and table again.
The batch script will run to the end and tell you it completed. You will also get messages that the
button and the two pieces of furniture were created. Look around and you should see the button
there. But you won't see any chair nor a table! This is because we ran this with the `/debug`
switch, which is directly visible as `DEBUG==True` inside the script. In the above example we
handled this state by deleting the chair and table again.
The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for bugs in your code or try to see if things behave as they should. Running the script over and over would then create an ever-growing stack of chairs and tables, all with the same name. You would have to go back and painstakingly delete them later.
The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for
bugs in your code or try to see if things behave as they should. Running the script over and over
would then create an ever-growing stack of chairs and tables, all with the same name. You would have
to go back and painstakingly delete them later.
## Interactive mode
Interactive mode works very similar to the [batch-command processor counterpart](Batch-Command-Processor). It allows you more step-wise control over how the batch file is executed. This is useful for debugging or for picking and choosing only particular blocks to run. Use `@batchcode` with the `/interactive` flag to enter interactive mode.
Interactive mode works very similar to the [batch-command processor counterpart](Batch-Command-
Processor). It allows you more step-wise control over how the batch file is executed. This is useful
for debugging or for picking and choosing only particular blocks to run. Use `@batchcode` with the
`/interactive` flag to enter interactive mode.
> @batchcode/interactive tutorial_examples.batch_code
@ -97,9 +132,11 @@ You should see the following:
01/02: red_button = create_object(red_button.RedButton, [...] (hh for help)
This shows that you are on the first `#CODE` block, the first of only two commands in this batch file. Observe that the block has *not* actually been executed at this point!
This shows that you are on the first `#CODE` block, the first of only two commands in this batch
file. Observe that the block has *not* actually been executed at this point!
To take a look at the full code snippet you are about to run, use `ll` (a batch-processor version of `look`).
To take a look at the full code snippet you are about to run, use `ll` (a batch-processor version of
`look`).
```python
from evennia.utils import create, search
@ -115,26 +152,52 @@ To take a look at the full code snippet you are about to run, use `ll` (a batch-
caller.msg("A red button was created.")
```
Compare with the example code given earlier. Notice how the content of `#HEADER` has been pasted at the top of the `#CODE` block. Use `pp` to actually execute this block (this will create the button and give you a message). Use `nn` (next) to go to the next command. Use `hh` for a list of commands.
Compare with the example code given earlier. Notice how the content of `#HEADER` has been pasted at
the top of the `#CODE` block. Use `pp` to actually execute this block (this will create the button
and give you a message). Use `nn` (next) to go to the next command. Use `hh` for a list of commands.
If there are tracebacks, fix them in the batch file, then use `rr` to reload the file. You will still be at the same code block and can rerun it easily with `pp` as needed. This makes for a simple debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large batch file this can be very useful (don't forget the `/debug` mode either).
If there are tracebacks, fix them in the batch file, then use `rr` to reload the file. You will
still be at the same code block and can rerun it easily with `pp` as needed. This makes for a simple
debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large
batch file this can be very useful (don't forget the `/debug` mode either).
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward (without processing any blocks in between). All normal commands of Evennia should work too while working in interactive mode.
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward
(without processing any blocks in between). All normal commands of Evennia should work too while
working in interactive mode.
## Limitations and Caveats
The batch-code processor is by far the most flexible way to build a world in Evennia. There are however some caveats you need to keep in mind.
The batch-code processor is by far the most flexible way to build a world in Evennia. There are
however some caveats you need to keep in mind.
### Safety
Or rather the lack of it. There is a reason only *superusers* are allowed to run the batch-code processor by default. The code-processor runs **without any Evennia security checks** and allows full access to Python. If an untrusted party could run the code-processor they could execute arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to allow other users to access the batch-code processor you should make sure to run Evennia as a separate and very limited-access user on your machine (i.e. in a 'jail'). By comparison, the batch-command processor is much safer since the user running it is still 'inside' the game and can't really do anything outside what the game commands allow them to.
Or rather the lack of it. There is a reason only *superusers* are allowed to run the batch-code
processor by default. The code-processor runs **without any Evennia security checks** and allows
full access to Python. If an untrusted party could run the code-processor they could execute
arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to
allow other users to access the batch-code processor you should make sure to run Evennia as a
separate and very limited-access user on your machine (i.e. in a 'jail'). By comparison, the batch-
command processor is much safer since the user running it is still 'inside' the game and can't
really do anything outside what the game commands allow them to.
### No communication between code blocks
Global variables won't work in code batch files, each block is executed as stand-alone environments. `#HEADER` blocks are literally pasted on top of each `#CODE` block so updating some header-variable in your block will not make that change available in another block. Whereas a python execution limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode - this would be a classical example of "spaghetti code".
Global variables won't work in code batch files, each block is executed as stand-alone environments.
`#HEADER` blocks are literally pasted on top of each `#CODE` block so updating some header-variable
in your block will not make that change available in another block. Whereas a python execution
limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode
- this would be a classical example of "spaghetti code".
The main practical issue with this is when building e.g. a room in one code block and later want to connect that room with a room you built in the current block. There are two ways to do this:
The main practical issue with this is when building e.g. a room in one code block and later want to
connect that room with a room you built in the current block. There are two ways to do this:
- Perform a database search for the name of the room you created (since you cannot know in advance which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of "A dark forest" rooms). There is an easy way to handle this though - use [Tags](Tags) or *Aliases*. You can assign any number of tags and/or aliases to any object. Make sure that one of those tags or aliases is unique to the room (like "room56") and you will henceforth be able to always uniquely search and find it later.
- Use the `caller` global property as an inter-block storage. For example, you could have a dictionary of room references in an `ndb`:
- Perform a database search for the name of the room you created (since you cannot know in advance
which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of "A
dark forest" rooms). There is an easy way to handle this though - use [Tags](Tags) or *Aliases*. You
can assign any number of tags and/or aliases to any object. Make sure that one of those tags or
aliases is unique to the room (like "room56") and you will henceforth be able to always uniquely
search and find it later.
- Use the `caller` global property as an inter-block storage. For example, you could have a
dictionary of room references in an `ndb`:
```python
#HEADER
if caller.ndb.all_rooms is None:
@ -149,10 +212,18 @@ The main practical issue with this is when building e.g. a room in one code bloc
# in another node we want to access the castle
castle = caller.ndb.all_rooms.get("castle")
```
Note how we check in `#HEADER` if `caller.ndb.all_rooms` doesn't already exist before creating the dict. Remember that `#HEADER` is copied in front of every `#CODE` block. Without that `if` statement we'd be wiping the dict every block!
Note how we check in `#HEADER` if `caller.ndb.all_rooms` doesn't already exist before creating the
dict. Remember that `#HEADER` is copied in front of every `#CODE` block. Without that `if` statement
we'd be wiping the dict every block!
### Don't treat a batchcode file like any Python file
Despite being a valid Python file, a batchcode file should *only* be run by the batchcode processor. You should not do things like define Typeclasses or Commands in them, or import them into other code. Importing a module in Python will execute base level of the module, which in the case of your average batchcode file could mean creating a lot of new objects every time.
Despite being a valid Python file, a batchcode file should *only* be run by the batchcode processor.
You should not do things like define Typeclasses or Commands in them, or import them into other
code. Importing a module in Python will execute base level of the module, which in the case of your
average batchcode file could mean creating a lot of new objects every time.
### Don't let code rely on the batch-file's real file path
When you import things into your batchcode file, don't use relative imports but always import with paths starting from the root of your game directory or evennia library. Code that relies on the batch file's "actual" location *will fail*. Batch code files are read as text and the strings executed. When the code runs it has no knowledge of what file those strings where once a part of.
When you import things into your batchcode file, don't use relative imports but always import with
paths starting from the root of your game directory or evennia library. Code that relies on the
batch file's "actual" location *will fail*. Batch code files are read as text and the strings
executed. When the code runs it has no knowledge of what file those strings where once a part of.

View file

@ -1,7 +1,9 @@
# Batch Command Processor
For an introduction and motivation to using batch processors, see [here](Batch-Processors). This page describes the Batch-*command* processor. The Batch-*code* one is covered [here](Batch-Code-Processor).
For an introduction and motivation to using batch processors, see [here](Batch-Processors). This
page describes the Batch-*command* processor. The Batch-*code* one is covered [here](Batch-Code-
Processor).
## Basic Usage
@ -9,24 +11,44 @@ The batch-command processor is a superuser-only function, invoked by
> @batchcommand path.to.batchcmdfile
Where `path.to.batchcmdfile` is the path to a *batch-command file* with the "`.ev`" file ending. This path is given like a python path relative to a folder you define to hold your batch files, set with `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is in the `mygame` folder) `mygame/world`. So if you want to run the example batch file in `mygame/world/batch_cmds.ev`, you could use
Where `path.to.batchcmdfile` is the path to a *batch-command file* with the "`.ev`" file ending.
This path is given like a python path relative to a folder you define to hold your batch files, set
with `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is in the `mygame`
folder) `mygame/world`. So if you want to run the example batch file in
`mygame/world/batch_cmds.ev`, you could use
> @batchcommand batch_cmds
A batch-command file contains a list of Evennia in-game commands separated by comments. The processor will run the batch file from beginning to end. Note that *it will not stop if commands in it fail* (there is no universal way for the processor to know what a failure looks like for all different commands). So keep a close watch on the output, or use *Interactive mode* (see below) to run the file in a more controlled, gradual manner.
A batch-command file contains a list of Evennia in-game commands separated by comments. The
processor will run the batch file from beginning to end. Note that *it will not stop if commands in
it fail* (there is no universal way for the processor to know what a failure looks like for all
different commands). So keep a close watch on the output, or use *Interactive mode* (see below) to
run the file in a more controlled, gradual manner.
## The batch file
The batch file is a simple plain-text file containing Evennia commands. Just like you would write them in-game, except you have more freedom with line breaks.
The batch file is a simple plain-text file containing Evennia commands. Just like you would write
them in-game, except you have more freedom with line breaks.
Here are the rules of syntax of an `*.ev` file. You'll find it's really, really simple:
- All lines having the `#` (hash)-symbol *as the first one on the line* are considered *comments*. All non-comment lines are treated as a command and/or their arguments.
- Comment lines have an actual function -- they mark the *end of the previous command definition*. So never put two commands directly after one another in the file - separate them with a comment, or the second of the two will be considered an argument to the first one. Besides, using plenty of comments is good practice anyway.
- A line that starts with the word `#INSERT` is a comment line but also signifies a special instruction. The syntax is `#INSERT <path.batchfile>` and tries to import a given batch-cmd file into this one. The inserted batch file (file ending `.ev`) will run normally from the point of the `#INSERT` instruction.
- Extra whitespace in a command definition is *ignored*. - A completely empty line translates in to a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant for commands accepting such formatting, such as the `@desc` command).
- All lines having the `#` (hash)-symbol *as the first one on the line* are considered *comments*.
All non-comment lines are treated as a command and/or their arguments.
- Comment lines have an actual function -- they mark the *end of the previous command definition*.
So never put two commands directly after one another in the file - separate them with a comment, or
the second of the two will be considered an argument to the first one. Besides, using plenty of
comments is good practice anyway.
- A line that starts with the word `#INSERT` is a comment line but also signifies a special
instruction. The syntax is `#INSERT <path.batchfile>` and tries to import a given batch-cmd file
into this one. The inserted batch file (file ending `.ev`) will run normally from the point of the
`#INSERT` instruction.
- Extra whitespace in a command definition is *ignored*. - A completely empty line translates in to
a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant
for commands accepting such formatting, such as the `@desc` command).
- The very last command in the file is not required to end with a comment.
- You *cannot* nest another `@batchcommand` statement into your batch file. If you want to link many batch-files together, use the `#INSERT` batch instruction instead. You also cannot launch the `@batchcode` command from your batch file, the two batch processors are not compatible.
- You *cannot* nest another `@batchcommand` statement into your batch file. If you want to link many
batch-files together, use the `#INSERT` batch instruction instead. You also cannot launch the
`@batchcode` command from your batch file, the two batch processors are not compatible.
Below is a version of the example file found in `evennia/contrib/tutorial_examples/batch_cmds.ev`.
@ -77,13 +99,19 @@ To test this, run `@batchcommand` on the file:
> @batchcommand contrib.tutorial_examples.batch_cmds
A button will be created, described and dropped in Limbo. All commands will be executed by the user calling the command.
A button will be created, described and dropped in Limbo. All commands will be executed by the user
calling the command.
> Note that if you interact with the button, you might find that its description changes, loosing your custom-set description above. This is just the way this particular object works.
> Note that if you interact with the button, you might find that its description changes, loosing
your custom-set description above. This is just the way this particular object works.
## Interactive mode
Interactive mode allows you to more step-wise control over how the batch file is executed. This is useful for debugging and also if you have a large batch file and is only updating a small part of it -- running the entire file again would be a waste of time (and in the case of `@create`-ing objects you would to end up with multiple copies of same-named objects, for example). Use `@batchcommand` with the `/interactive` flag to enter interactive mode.
Interactive mode allows you to more step-wise control over how the batch file is executed. This is
useful for debugging and also if you have a large batch file and is only updating a small part of it
-- running the entire file again would be a waste of time (and in the case of `@create`-ing objects
you would to end up with multiple copies of same-named objects, for example). Use `@batchcommand`
with the `/interactive` flag to enter interactive mode.
> @batchcommand/interactive tutorial_examples.batch_cmds
@ -91,31 +119,64 @@ You will see this:
01/04: @create button:tutorial_examples.red_button.RedButton (hh for help)
This shows that you are on the `@create` command, the first out of only four commands in this batch file. Observe that the command `@create` has *not* been actually processed at this point!
This shows that you are on the `@create` command, the first out of only four commands in this batch
file. Observe that the command `@create` has *not* been actually processed at this point!
To take a look at the full command you are about to run, use `ll` (a batch-processor version of `look`). Use `pp` to actually process the current command (this will actually `@create` the button) -- and make sure it worked as planned. Use `nn` (next) to go to the next command. Use `hh` for a list of commands.
To take a look at the full command you are about to run, use `ll` (a batch-processor version of
`look`). Use `pp` to actually process the current command (this will actually `@create` the button)
-- and make sure it worked as planned. Use `nn` (next) to go to the next command. Use `hh` for a
list of commands.
If there are errors, fix them in the batch file, then use `rr` to reload the file. You will still be at the same command and can rerun it easily with `pp` as needed. This makes for a simple debug cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g. if `@create` in the example above had failed, the following commands would have had nothing to operate on).
If there are errors, fix them in the batch file, then use `rr` to reload the file. You will still be
at the same command and can rerun it easily with `pp` as needed. This makes for a simple debug
cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch
file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g.
if `@create` in the example above had failed, the following commands would have had nothing to
operate on).
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward (without processing any command in between). All normal commands of Evennia should work too while working in interactive mode.
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward
(without processing any command in between). All normal commands of Evennia should work too while
working in interactive mode.
## Limitations and Caveats
The batch-command processor is great for automating smaller builds or for testing new commands and objects repeatedly without having to write so much. There are several caveats you have to be aware of when using the batch-command processor for building larger, complex worlds though.
The batch-command processor is great for automating smaller builds or for testing new commands and
objects repeatedly without having to write so much. There are several caveats you have to be aware
of when using the batch-command processor for building larger, complex worlds though.
The main issue is that when you run a batch-command script you (*you*, as in your superuser character) are actually moving around in the game creating and building rooms in sequence, just as if you had been entering those commands manually, one by one. You have to take this into account when creating the file, so that you can 'walk' (or teleport) to the right places in order.
The main issue is that when you run a batch-command script you (*you*, as in your superuser
character) are actually moving around in the game creating and building rooms in sequence, just as
if you had been entering those commands manually, one by one. You have to take this into account
when creating the file, so that you can 'walk' (or teleport) to the right places in order.
This also means there are several pitfalls when designing and adding certain types of objects. Here are some examples:
This also means there are several pitfalls when designing and adding certain types of objects. Here
are some examples:
- *Rooms that change your [Command Set](Command-Sets)*: Imagine that you build a 'dark' room, which severely limits the cmdsets of those entering it (maybe you have to find the light switch to proceed). In your batch script you would create this room, then teleport to it - and promptly be shifted into the dark state where none of your normal build commands work ...
- *Auto-teleportation*: Rooms that automatically teleport those that enter them to another place (like a trap room, for example). You would be teleported away too.
- *Mobiles*: If you add aggressive mobs, they might attack you, drawing you into combat. If they have AI they might even follow you around when building - or they might move away from you before you've had time to finish describing and equipping them!
- *Rooms that change your [Command Set](Command-Sets)*: Imagine that you build a 'dark' room, which
severely limits the cmdsets of those entering it (maybe you have to find the light switch to
proceed). In your batch script you would create this room, then teleport to it - and promptly be
shifted into the dark state where none of your normal build commands work ...
- *Auto-teleportation*: Rooms that automatically teleport those that enter them to another place
(like a trap room, for example). You would be teleported away too.
- *Mobiles*: If you add aggressive mobs, they might attack you, drawing you into combat. If they
have AI they might even follow you around when building - or they might move away from you before
you've had time to finish describing and equipping them!
The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever effects are in play. Add an on/off switch to objects and make sure it's always set to *off* upon creation. It's all doable, one just needs to keep it in mind.
The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever
effects are in play. Add an on/off switch to objects and make sure it's always set to *off* upon
creation. It's all doable, one just needs to keep it in mind.
## Assorted notes
The fact that you build as 'yourself' can also be considered an advantage however, should you ever decide to change the default command to allow others than superusers to call the processor. Since normal access-checks are still performed, a malevolent builder with access to the processor should not be able to do all that much damage (this is the main drawback of the [Batch Code Processor](Batch-Code-Processor))
The fact that you build as 'yourself' can also be considered an advantage however, should you ever
decide to change the default command to allow others than superusers to call the processor. Since
normal access-checks are still performed, a malevolent builder with access to the processor should
not be able to do all that much damage (this is the main drawback of the [Batch Code
Processor](Batch-Code-Processor))
- [GNU Emacs](https://www.gnu.org/software/emacs/) users might find it interesting to use emacs' *evennia mode*. This is an Emacs major mode found in `evennia/utils/evennia-mode.el`. It offers correct syntax highlighting and indentation with `<tab>` when editing `.ev` files in Emacs. See the header of that file for installation instructions.
- [VIM](http://www.vim.org/) users can use amfl's [vim-evennia](https://github.com/amfl/vim-evennia) mode instead, see its readme for install instructions.
- [GNU Emacs](https://www.gnu.org/software/emacs/) users might find it interesting to use emacs'
*evennia mode*. This is an Emacs major mode found in `evennia/utils/evennia-mode.el`. It offers
correct syntax highlighting and indentation with `<tab>` when editing `.ev` files in Emacs. See the
header of that file for installation instructions.
- [VIM](http://www.vim.org/) users can use amfl's [vim-evennia](https://github.com/amfl/vim-evennia)
mode instead, see its readme for install instructions.

View file

@ -1,39 +1,82 @@
# Batch Processors
Building a game world is a lot of work, especially when starting out. Rooms should be created, descriptions have to be written, objects must be detailed and placed in their proper places. In many traditional MUD setups you had to do all this online, line by line, over a telnet session.
Building a game world is a lot of work, especially when starting out. Rooms should be created,
descriptions have to be written, objects must be detailed and placed in their proper places. In many
traditional MUD setups you had to do all this online, line by line, over a telnet session.
Evennia already moves away from much of this by shifting the main coding work to external Python modules. But also building would be helped if one could do some or all of it externally. Enter Evennia's *batch processors* (there are two of them). The processors allows you, as a game admin, to build your game completely offline in normal text files (*batch files*) that the processors understands. Then, when you are ready, you use the processors to read it all into Evennia (and into the database) in one go.
Evennia already moves away from much of this by shifting the main coding work to external Python
modules. But also building would be helped if one could do some or all of it externally. Enter
Evennia's *batch processors* (there are two of them). The processors allows you, as a game admin, to
build your game completely offline in normal text files (*batch files*) that the processors
understands. Then, when you are ready, you use the processors to read it all into Evennia (and into
the database) in one go.
You can of course still build completely online should you want to - this is certainly the easiest way to go when learning and for small build projects. But for major building work, the advantages of using the batch-processors are many:
- It's hard to compete with the comfort of a modern desktop text editor; Compared to a traditional MUD line input, you can get much better overview and many more features. Also, accidentally pressing Return won't immediately commit things to the database.
- You might run external spell checkers on your batch files. In the case of one of the batch-processors (the one that deals with Python code), you could also run external debuggers and code analyzers on your file to catch problems before feeding it to Evennia.
- The batch files (as long as you keep them) are records of your work. They make a natural starting point for quickly re-building your world should you ever decide to start over.
- If you are an Evennia developer, using a batch file is a fast way to setup a test-game after having reset the database.
- The batch files might come in useful should you ever decide to distribute all or part of your world to others.
You can of course still build completely online should you want to - this is certainly the easiest
way to go when learning and for small build projects. But for major building work, the advantages of
using the batch-processors are many:
- It's hard to compete with the comfort of a modern desktop text editor; Compared to a traditional
MUD line input, you can get much better overview and many more features. Also, accidentally pressing
Return won't immediately commit things to the database.
- You might run external spell checkers on your batch files. In the case of one of the batch-
processors (the one that deals with Python code), you could also run external debuggers and code
analyzers on your file to catch problems before feeding it to Evennia.
- The batch files (as long as you keep them) are records of your work. They make a natural starting
point for quickly re-building your world should you ever decide to start over.
- If you are an Evennia developer, using a batch file is a fast way to setup a test-game after
having reset the database.
- The batch files might come in useful should you ever decide to distribute all or part of your
world to others.
There are two batch processors, the Batch-*command* processor and the Batch-*code* processor. The first one is the simpler of the two. It doesn't require any programming knowledge - you basically just list in-game commands in a text file. The code-processor on the other hand is much more powerful but also more complex - it lets you use Evennia's API to code your world in full-fledged Python code.
There are two batch processors, the Batch-*command* processor and the Batch-*code* processor. The
first one is the simpler of the two. It doesn't require any programming knowledge - you basically
just list in-game commands in a text file. The code-processor on the other hand is much more
powerful but also more complex - it lets you use Evennia's API to code your world in full-fledged
Python code.
- The [Batch Command Processor](Batch-Command-Processor)
- The [Batch Code Processor](Batch-Code-Processor)
If you plan to use international characters in your batchfiles you are wise to read about *file encodings* below.
If you plan to use international characters in your batchfiles you are wise to read about *file
encodings* below.
## A note on File Encodings
As mentioned, both the processors take text files as input and then proceed to process them. As long as you stick to the standard [ASCII](http://en.wikipedia.org/wiki/Ascii) character set (which means the normal English characters, basically) you should not have to worry much about this section.
As mentioned, both the processors take text files as input and then proceed to process them. As long
as you stick to the standard [ASCII](http://en.wikipedia.org/wiki/Ascii) character set (which means
the normal English characters, basically) you should not have to worry much about this section.
Many languages however use characters outside the simple `ASCII` table. Common examples are various apostrophes and umlauts but also completely different symbols like those of the greek or cyrillic alphabets.
Many languages however use characters outside the simple `ASCII` table. Common examples are various
apostrophes and umlauts but also completely different symbols like those of the greek or cyrillic
alphabets.
First, we should make it clear that Evennia itself handles international characters just fine. It (and Django) uses [unicode](http://en.wikipedia.org/wiki/Unicode) strings internally.
First, we should make it clear that Evennia itself handles international characters just fine. It
(and Django) uses [unicode](http://en.wikipedia.org/wiki/Unicode) strings internally.
The problem is that when reading a text file like the batchfile, we need to know how to decode the byte-data stored therein to universal unicode. That means we need an *encoding* (a mapping) for how the file stores its data. There are many, many byte-encodings used around the world, with opaque names such as `Latin-1`, `ISO-8859-3` or `ARMSCII-8` to pick just a few examples. Problem is that it's practially impossible to determine which encoding was used to save a file just by looking at it (it's just a bunch of bytes!). You have to *know*.
The problem is that when reading a text file like the batchfile, we need to know how to decode the
byte-data stored therein to universal unicode. That means we need an *encoding* (a mapping) for how
the file stores its data. There are many, many byte-encodings used around the world, with opaque
names such as `Latin-1`, `ISO-8859-3` or `ARMSCII-8` to pick just a few examples. Problem is that
it's practially impossible to determine which encoding was used to save a file just by looking at it
(it's just a bunch of bytes!). You have to *know*.
With this little introduction it should be clear that Evennia can't guess but has to *assume* an encoding when trying to load a batchfile. The text editor and Evennia must speak the same "language" so to speak. Evennia will by default first try the international `UTF-8` encoding, but you can have Evennia try any sequence of different encodings by customizing the `ENCODINGS` list in your settings file. Evennia will use the first encoding in the list that do not raise any errors. Only if none work will the server give up and return an error message.
With this little introduction it should be clear that Evennia can't guess but has to *assume* an
encoding when trying to load a batchfile. The text editor and Evennia must speak the same "language"
so to speak. Evennia will by default first try the international `UTF-8` encoding, but you can have
Evennia try any sequence of different encodings by customizing the `ENCODINGS` list in your settings
file. Evennia will use the first encoding in the list that do not raise any errors. Only if none
work will the server give up and return an error message.
You can often change the text editor encoding (this depends on your editor though), otherwise you need to add the editor's encoding to Evennia's `ENCODINGS` list. If you are unsure, write a test file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works as it should.
You can often change the text editor encoding (this depends on your editor though), otherwise you
need to add the editor's encoding to Evennia's `ENCODINGS` list. If you are unsure, write a test
file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works
as it should.
More help with encodings can be found in the entry [Text Encodings](Text-Encodings) and also in the Wikipedia article [here](http://en.wikipedia.org/wiki/Text_encodings).
More help with encodings can be found in the entry [Text Encodings](Text-Encodings) and also in the
Wikipedia article [here](http://en.wikipedia.org/wiki/Text_encodings).
**A footnote for the batch-code processor**: Just because *Evennia* can parse your file and your fancy special characters, doesn't mean that *Python* allows their use. Python syntax only allows international characters inside *strings*. In all other source code only `ASCII` set characters are allowed.
**A footnote for the batch-code processor**: Just because *Evennia* can parse your file and your
fancy special characters, doesn't mean that *Python* allows their use. Python syntax only allows
international characters inside *strings*. In all other source code only `ASCII` set characters are
allowed.

View file

@ -1,35 +1,56 @@
# 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.
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/).
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).
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.
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.
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.
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)
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:
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">
@ -47,7 +68,8 @@ To use the grid system, first create a container for your content, then add your
```
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!
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">
@ -66,10 +88,14 @@ To specify your sizes - for instance, Evennia's default site has three columns o
</div>
</div>
```
This layout would be 4 columns on large screens, 2 columns on medium screens, and 1 column on anything smaller.
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/)
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.
Bootstrap also provides a huge amount of utilities, as well as styling and content elements. To
learn more about them, please [read the Bootstrap docs](https://getbootstrap.com/docs/4.0/getting-
started/introduction/) or read one of our other web tutorials.

View file

@ -1,17 +1,25 @@
# Bootstrap Components and Utilities
Bootstrap provides many utilities and components you can use when customizing Evennia's web presence. We'll go over a few examples here that you might find useful.
> Please take a look at either [the basic web tutorial](Add-a-simple-new-web-page) or [the web character view tutorial](Web-Character-View-Tutorial)
Bootstrap provides many utilities and components you can use when customizing Evennia's web
presence. We'll go over a few examples here that you might find useful.
> Please take a look at either [the basic web tutorial](Add-a-simple-new-web-page) or [the web
character view tutorial](Web-Character-View-Tutorial)
> to get a feel for how to add pages to Evennia's website to test these examples.
## General Styling
Bootstrap provides base styles for your site. These can be customized through CSS, but the default styles are intended to provide a consistent, clean look for sites.
Bootstrap provides base styles for your site. These can be customized through CSS, but the default
styles are intended to provide a consistent, clean look for sites.
### Color
Most elements can be styled with default colors. [Take a look at the documentation](https://getbootstrap.com/docs/4.0/utilities/colors/) to learn more about these colors - suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color or background color.
Most elements can be styled with default colors. [Take a look at the
documentation](https://getbootstrap.com/docs/4.0/utilities/colors/) to learn more about these colors
- suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color
or background color.
### Borders
Simply adding a class of 'border' to an element adds a border to the element. For more in-depth info, please [read the documentation on borders.](https://getbootstrap.com/docs/4.0/utilities/borders/).
Simply adding a class of 'border' to an element adds a border to the element. For more in-depth
info, please [read the documentation on
borders.](https://getbootstrap.com/docs/4.0/utilities/borders/).
```
<span class="border border-dark"></span>
```
@ -21,13 +29,17 @@ You can also easily round corners just by adding a class.
```
### Spacing
Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might like to add margins or padding through CSS itself - however these classes are used in the default Evennia site. [Take a look at the docs](https://getbootstrap.com/docs/4.0/utilities/spacing/) to learn more.
Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might
like to add margins or padding through CSS itself - however these classes are used in the default
Evennia site. [Take a look at the docs](https://getbootstrap.com/docs/4.0/utilities/spacing/) to
learn more.
***
## Components
### Buttons
[Buttons](https://getbootstrap.com/docs/4.0/components/buttons/) in Bootstrap are very easy to use - button styling can be added to `<button>`, `<a>`, and `<input>` elements.
[Buttons](https://getbootstrap.com/docs/4.0/components/buttons/) in Bootstrap are very easy to use -
button styling can be added to `<button>`, `<a>`, and `<input>` elements.
```
<a class="btn btn-primary" href="#" role="button">I'm a Button</a>
<button class="btn btn-primary" type="submit">Me too!</button>
@ -36,7 +48,10 @@ Bootstrap provides classes to easily add responsive margin and padding. Most of
<input class="btn btn-primary" type="reset" value="Button as Well">
```
### Cards
[Cards](https://getbootstrap.com/docs/4.0/components/card/) provide a container for other elements that stands out from the rest of the page. The "Accounts", "Recently Connected", and "Database Stats" on the default webpage are all in cards. Cards provide quite a bit of formatting options - the following is a simple example, but read the documentation or look at the site's source for more.
[Cards](https://getbootstrap.com/docs/4.0/components/card/) provide a container for other elements
that stands out from the rest of the page. The "Accounts", "Recently Connected", and "Database
Stats" on the default webpage are all in cards. Cards provide quite a bit of formatting options -
the following is a simple example, but read the documentation or look at the site's source for more.
```
<div class="card">
<div class="card-body">
@ -49,7 +64,9 @@ Bootstrap provides classes to easily add responsive margin and padding. Most of
```
### Jumbotron
[Jumbotrons](https://getbootstrap.com/docs/4.0/components/jumbotron/) are useful for featuring an image or tagline for your game. They can flow with the rest of your content or take up the full width of the page - Evennia's base site uses the former.
[Jumbotrons](https://getbootstrap.com/docs/4.0/components/jumbotron/) are useful for featuring an
image or tagline for your game. They can flow with the rest of your content or take up the full
width of the page - Evennia's base site uses the former.
```
<div class="jumbotron jumbotron-fluid">
<div class="container">
@ -60,4 +77,6 @@ Bootstrap provides classes to easily add responsive margin and padding. Most of
```
### Forms
[Forms](https://getbootstrap.com/docs/4.0/components/forms/) are highly customizable with Bootstrap. For a more in-depth look at how to use forms and their styles in your own Evennia site, please read over [the web character gen tutorial.](Web-Character-Generation)
[Forms](https://getbootstrap.com/docs/4.0/components/forms/) are highly customizable with Bootstrap.
For a more in-depth look at how to use forms and their styles in your own Evennia site, please read
over [the web character gen tutorial.](Web-Character-Generation)

View file

@ -23,4 +23,4 @@ This section contains information useful to world builders.
### The Tutorial world
- [Introduction and setup](Tutorial-World-Introduction)
- [Introduction and setup](Tutorial-World-Introduction)

View file

@ -1,25 +1,47 @@
# Building Permissions
*OBS: This gives only a brief introduction to the access system. Locks and permissions are fully detailed* [here](Locks).
*OBS: This gives only a brief introduction to the access system. Locks and permissions are fully
detailed* [here](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.
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](Locks) is used. By default Evennia creates the following hierarchy (spelled exactly like this):
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](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. **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.
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.
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
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
@ -27,12 +49,24 @@ 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).
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:
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.
This will allow you to test out the game using the current Character's permission level. A developer
or builder can thus in principle maintain several test characters, all using different permission
levels. Note that you cannot escalate your permissions this way; If the Character happens to have a
*higher* permission level than the Account, the *Account's* (lower) permission will still be used.

View file

@ -14,33 +14,51 @@ The default commands have the following style (where `[...]` marks optional part
command[/switch/switch...] [arguments ...]
A _switch_ is a special, optional flag to the command to make it behave differently. It is always put directly after the command name, and begins with a forward slash (`/`). The _arguments_ are one or more inputs to the commands. It's common to use an equal sign (`=`) when assigning something to an object.
A _switch_ is a special, optional flag to the command to make it behave differently. It is always
put directly after the command name, and begins with a forward slash (`/`). The _arguments_ are one
or more inputs to the commands. It's common to use an equal sign (`=`) when assigning something to
an object.
Below are some examples of commands you can try when logged in to the game. Use `help <command>` for learning more about each command and their detailed options.
Below are some examples of commands you can try when logged in to the game. Use `help <command>` for
learning more about each command and their detailed options.
## Stepping Down From Godhood
If you just installed Evennia, your very first player account is called user #1, also known as the _superuser_ or _god user_. This user is very powerful, so powerful that it will override many game restrictions such as locks. This can be useful, but it also hides some functionality that you might want to test.
If you just installed Evennia, your very first player account is called user #1, also known as the
_superuser_ or _god user_. This user is very powerful, so powerful that it will override many game
restrictions such as locks. This can be useful, but it also hides some functionality that you might
want to test.
To temporarily step down from your superuser position you can use the `quell` command in-game:
quell
This will make you start using the permission of your current character's level instead of your superuser level. If you didn't change any settings your game Character should have an _Developer_ level permission - high as can be without bypassing locks like the superuser does. This will work fine for the examples on this page. Use `unquell` to get back to superuser status again afterwards.
This will make you start using the permission of your current character's level instead of your
superuser level. If you didn't change any settings your game Character should have an _Developer_
level permission - high as can be without bypassing locks like the superuser does. This will work
fine for the examples on this page. Use `unquell` to get back to superuser status again afterwards.
## Creating an Object
Basic objects can be anything -- swords, flowers and non-player characters. They are created using the `create` command:
Basic objects can be anything -- swords, flowers and non-player characters. They are created using
the `create` command:
create box
This created a new 'box' (of the default object type) in your inventory. Use the command `inventory` (or `i`) to see it. Now, 'box' is a rather short name, let's rename it and tack on a few aliases.
This created a new 'box' (of the default object type) in your inventory. Use the command `inventory`
(or `i`) to see it. Now, 'box' is a rather short name, let's rename it and tack on a few aliases.
name box = very large box;box;very;crate
We now renamed the box to _very large box_ (and this is what we will see when looking at it), but we will also recognize it by any of the other names we give - like _crate_ or simply _box_ as before. We could have given these aliases directly after the name in the `create` command, this is true for all creation commands - you can always tag on a list of `;`-separated aliases to the name of your new object. If you had wanted to not change the name itself, but to only add aliases, you could have used the `alias` command.
We now renamed the box to _very large box_ (and this is what we will see when looking at it), but we
will also recognize it by any of the other names we give - like _crate_ or simply _box_ as before.
We could have given these aliases directly after the name in the `create` command, this is true for
all creation commands - you can always tag on a list of `;`-separated aliases to the name of your
new object. If you had wanted to not change the name itself, but to only add aliases, you could have
used the `alias` command.
We are currently carrying the box. Let's drop it (there is also a short cut to create and drop in one go by using the `/drop` switch, for example `create/drop box`).
We are currently carrying the box. Let's drop it (there is also a short cut to create and drop in
one go by using the `/drop` switch, for example `create/drop box`).
drop box
@ -48,7 +66,8 @@ Hey presto - there it is on the ground, in all its normality.
examine box
This will show some technical details about the box object. For now we will ignore what this information means.
This will show some technical details about the box object. For now we will ignore what this
information means.
Try to `look` at the box to see the (default) description.
@ -59,74 +78,116 @@ The description you get is not very exciting. Let's add some flavor.
describe box = This is a large and very heavy box.
If you try the `get` command we will pick up the box. So far so good, but if we really want this to be a large and heavy box, people should _not_ be able to run off with it that easily. To prevent this we need to lock it down. This is done by assigning a _Lock_ to it. Make sure the box was dropped in the room, then try this:
If you try the `get` command we will pick up the box. So far so good, but if we really want this to
be a large and heavy box, people should _not_ be able to run off with it that easily. To prevent
this we need to lock it down. This is done by assigning a _Lock_ to it. Make sure the box was
dropped in the room, then try this:
lock box = get:false()
Locks represent a rather [big topic](Locks), but for now that will do what we want. This will lock the box so noone can lift it. The exception is superusers, they override all locks and will pick it up anyway. Make sure you are quelling your superuser powers and try to get the box now:
Locks represent a rather [big topic](Locks), but for now that will do what we want. This will lock
the box so noone can lift it. The exception is superusers, they override all locks and will pick it
up anyway. Make sure you are quelling your superuser powers and try to get the box now:
> get box
You can't get that.
Think thís default error message looks dull? The `get` command looks for an [Attribute](Attributes) named `get_err_msg` for returning a nicer error message (we just happen to know this, you would need to peek into the [code](https://github.com/evennia/evennia/blob/master/evennia/commands/default/general.py#L235) for the `get` command to find out.). You set attributes using the `set` command:
Think thís default error message looks dull? The `get` command looks for an [Attribute](Attributes)
named `get_err_msg` for returning a nicer error message (we just happen to know this, you would need
to peek into the
[code](https://github.com/evennia/evennia/blob/master/evennia/commands/default/general.py#L235) for
the `get` command to find out.). You set attributes using the `set` command:
set box/get_err_msg = It's way too heavy for you to lift.
Try to get it now and you should see a nicer error message echoed back to you. To see what this message string is in the future, you can use 'examine.'
Try to get it now and you should see a nicer error message echoed back to you. To see what this
message string is in the future, you can use 'examine.'
examine box/get_err_msg
Examine will return the value of attributes, including color codes. `examine here/desc` would return the raw description of your current room (including color codes), so that you can copy-and-paste to set its description to something else.
Examine will return the value of attributes, including color codes. `examine here/desc` would return
the raw description of your current room (including color codes), so that you can copy-and-paste to
set its description to something else.
You create new Commands (or modify existing ones) in Python outside the game. See the [Adding Commands tutorial](Adding-Command-Tutorial) for help with creating your first own Command.
You create new Commands (or modify existing ones) in Python outside the game. See the [Adding
Commands tutorial](Adding-Command-Tutorial) for help with creating your first own Command.
## Get a Personality
[Scripts](Scripts) are powerful out-of-character objects useful for many "under the hood" things. One of their optional abilities is to do things on a timer. To try out a first script, let's put one on ourselves. There is an example script in `evennia/contrib/tutorial_examples/bodyfunctions.py` that is called `BodyFunctions`. To add this to us we will use the `script` command:
[Scripts](Scripts) are powerful out-of-character objects useful for many "under the hood" things.
One of their optional abilities is to do things on a timer. To try out a first script, let's put one
on ourselves. There is an example script in `evennia/contrib/tutorial_examples/bodyfunctions.py`
that is called `BodyFunctions`. To add this to us we will use the `script` command:
script self = tutorial_examples.bodyfunctions.BodyFunctions
(note that you don't have to give the full path as long as you are pointing to a place inside the `contrib` directory, it's one of the places Evennia looks for Scripts). Wait a while and you will notice yourself starting making random observations.
(note that you don't have to give the full path as long as you are pointing to a place inside the
`contrib` directory, it's one of the places Evennia looks for Scripts). Wait a while and you will
notice yourself starting making random observations.
script self
This will show details about scripts on yourself (also `examine` works). You will see how long it is until it "fires" next. Don't be alarmed if nothing happens when the countdown reaches zero - this particular script has a randomizer to determine if it will say something or not. So you will not see output every time it fires.
This will show details about scripts on yourself (also `examine` works). You will see how long it is
until it "fires" next. Don't be alarmed if nothing happens when the countdown reaches zero - this
particular script has a randomizer to determine if it will say something or not. So you will not see
output every time it fires.
When you are tired of your character's "insights", kill the script with
script/stop self = tutorial_examples.bodyfunctions.BodyFunctions
You create your own scripts in Python, outside the game; the path you give to `script` is literally the Python path to your script file. The [Scripts](Scripts) page explains more details.
You create your own scripts in Python, outside the game; the path you give to `script` is literally
the Python path to your script file. The [Scripts](Scripts) page explains more details.
## Pushing Your Buttons
If we get back to the box we made, there is only so much fun you can do with it at this point. It's just a dumb generic object. If you renamed it to `stone` and changed its description noone would be the wiser. However, with the combined use of custom [Typeclasses](Typeclasses), [Scripts](Scripts) and object-based [Commands](Commands), you could expand it and other items to be as unique, complex and interactive as you want.
If we get back to the box we made, there is only so much fun you can do with it at this point. It's
just a dumb generic object. If you renamed it to `stone` and changed its description noone would be
the wiser. However, with the combined use of custom [Typeclasses](Typeclasses), [Scripts](Scripts)
and object-based [Commands](Commands), you could expand it and other items to be as unique, complex
and interactive as you want.
Let's take an example. So far we have only created objects that use the default object typeclass named simply `Object`. Let's create an object that is a little more interesting. Under `evennia/contrib/tutorial_examples` there is a module `red_button.py`. It contains the enigmatic `RedButton` typeclass.
Let's take an example. So far we have only created objects that use the default object typeclass
named simply `Object`. Let's create an object that is a little more interesting. Under
`evennia/contrib/tutorial_examples` there is a module `red_button.py`. It contains the enigmatic
`RedButton` typeclass.
Let's make us one of _those_!
create/drop button:tutorial_examples.red_button.RedButton
We import the RedButton python class the same way you would import it in Python except Evennia makes sure to look in`evennia/contrib/` so you don't have to write the full path every time. There you go - one red button.
We import the RedButton python class the same way you would import it in Python except Evennia makes
sure to look in`evennia/contrib/` so you don't have to write the full path every time. There you go
- one red button.
The RedButton is an example object intended to show off a few of Evennia's features. You will find that the [Typeclass](Typeclasses) and [Commands](Commands) controlling it are inside `evennia/contrib/tutorial_examples/`.
The RedButton is an example object intended to show off a few of Evennia's features. You will find
that the [Typeclass](Typeclasses) and [Commands](Commands) controlling it are inside
`evennia/contrib/tutorial_examples/`.
If you wait for a while (make sure you dropped it!) the button will blink invitingly. Why don't you try to push it ...? Surely a big red button is meant to be pushed. You know you want to.
If you wait for a while (make sure you dropped it!) the button will blink invitingly. Why don't you
try to push it ...? Surely a big red button is meant to be pushed. You know you want to.
## Making Yourself a House
The main command for shaping the game world is `dig`. For example, if you are standing in Limbo you can dig a route to your new house location like this:
The main command for shaping the game world is `dig`. For example, if you are standing in Limbo you
can dig a route to your new house location like this:
dig house = large red door;door;in,to the outside;out
This will create a new room named 'house'. Spaces at the start/end of names and aliases are ignored so you could put more air if you wanted. This call will directly create an exit from your current location named 'large red door' and a corresponding exit named 'to the outside' in the house room leading back to Limbo. We also define a few aliases to those exits, so people don't have to write the full thing all the time.
This will create a new room named 'house'. Spaces at the start/end of names and aliases are ignored
so you could put more air if you wanted. This call will directly create an exit from your current
location named 'large red door' and a corresponding exit named 'to the outside' in the house room
leading back to Limbo. We also define a few aliases to those exits, so people don't have to write
the full thing all the time.
If you wanted to use normal compass directions (north, west, southwest etc), you could do that with `dig` too. But Evennia also has a limited version of `dig` that helps for compass directions (and also up/down and in/out). It's called `tunnel`:
If you wanted to use normal compass directions (north, west, southwest etc), you could do that with
`dig` too. But Evennia also has a limited version of `dig` that helps for compass directions (and
also up/down and in/out). It's called `tunnel`:
tunnel sw = cliff
This will create a new room "cliff" with an exit "southwest" leading there and a path "northeast" leading back from the cliff to your current location.
This will create a new room "cliff" with an exit "southwest" leading there and a path "northeast"
leading back from the cliff to your current location.
You can create new exits from where you are using the `open` command:
@ -134,7 +195,10 @@ You can create new exits from where you are using the `open` command:
This opens an exit `north` (with an alias `n`) to the previously created room `house`.
If you have many rooms named `house` you will get a list of matches and have to select which one you want to link to. You can also give its database (#dbref) number, which is unique to every object. This can be found with the `examine` command or by looking at the latest constructions with `objects`.
If you have many rooms named `house` you will get a list of matches and have to select which one you
want to link to. You can also give its database (#dbref) number, which is unique to every object.
This can be found with the `examine` command or by looking at the latest constructions with
`objects`.
Follow the north exit to your 'house' or `teleport` to it:
@ -152,7 +216,8 @@ To manually open an exit back to Limbo (if you didn't do so with the `dig` comma
## Reshuffling the World
You can find things using the `find` command. Assuming you are back at `Limbo`, let's teleport the _large box to our house_.
You can find things using the `find` command. Assuming you are back at `Limbo`, let's teleport the
_large box to our house_.
> teleport box = house
very large box is leaving Limbo, heading for house.
@ -164,34 +229,46 @@ We can still find the box by using find:
One Match(#1-#8):
very large box(#8) - src.objects.objects.Object
Knowing the `#dbref` of the box (#8 in this example), you can grab the box and get it back here without actually yourself going to `house` first:
Knowing the `#dbref` of the box (#8 in this example), you can grab the box and get it back here
without actually yourself going to `house` first:
teleport #8 = here
(You can usually use `here` to refer to your current location. To refer to yourself you can use `self` or `me`). The box should now be back in Limbo with you.
(You can usually use `here` to refer to your current location. To refer to yourself you can use
`self` or `me`). The box should now be back in Limbo with you.
We are getting tired of the box. Let's destroy it.
destroy box
You can destroy many objects in one go by giving a comma-separated list of objects (or their #dbrefs, if they are not in the same location) to the command.
You can destroy many objects in one go by giving a comma-separated list of objects (or their
#dbrefs, if they are not in the same location) to the command.
## Adding a Help Entry
An important part of building is keeping the help files updated. You can add, delete and append to existing help entries using the `sethelp` command.
An important part of building is keeping the help files updated. You can add, delete and append to
existing help entries using the `sethelp` command.
sethelp/add MyTopic = This help topic is about ...
## Adding a World
After this brief introduction to building you may be ready to see a more fleshed-out example. Evennia comes with a tutorial world for you to explore.
After this brief introduction to building you may be ready to see a more fleshed-out example.
Evennia comes with a tutorial world for you to explore.
First you need to switch back to _superuser_ by using the `unquell` command. Next, place yourself in `Limbo` and run the following command:
First you need to switch back to _superuser_ by using the `unquell` command. Next, place yourself in
`Limbo` and run the following command:
batchcommand tutorial_world.build
This will take a while (be patient and don't re-run the command). You will see all the commands used to build the world scroll by as the world is built for you.
This will take a while (be patient and don't re-run the command). You will see all the commands used
to build the world scroll by as the world is built for you.
You will end up with a new exit from Limbo named _tutorial_. Apart from being a little solo-adventure in its own right, the tutorial world is a good source for learning Evennia building (and coding).
You will end up with a new exit from Limbo named _tutorial_. Apart from being a little solo-
adventure in its own right, the tutorial world is a good source for learning Evennia building (and
coding).
Read [the batch file](https://github.com/evennia/evennia/blob/master/evennia/contrib/tutorial_world/build.ev) to see exactly how it's built, step by step. See also more info about the tutorial world [here](Tutorial-World-Introduction).
Read [the batch
file](https://github.com/evennia/evennia/blob/master/evennia/contrib/tutorial_world/build.ev) to see
exactly how it's built, step by step. See also more info about the tutorial world [here](Tutorial-
World-Introduction).

View file

@ -1,14 +1,18 @@
# Building a mech tutorial
> This page was adapted from the article "Building a Giant Mech in Evennia" by Griatch, published in Imaginary Realities Volume 6, issue 1, 2014. The original article is no longer available online, this is a version adopted to be compatible with the latest Evennia.
> This page was adapted from the article "Building a Giant Mech in Evennia" by Griatch, published in
Imaginary Realities Volume 6, issue 1, 2014. The original article is no longer available online,
this is a version adopted to be compatible with the latest Evennia.
## Creating the Mech
Let us create a functioning giant mech using the Python MUD-creation system Evennia. Everyone likes a giant mech, right? Start in-game as a character with build privileges (or the superuser).
Let us create a functioning giant mech using the Python MUD-creation system Evennia. Everyone likes
a giant mech, right? Start in-game as a character with build privileges (or the superuser).
@create/drop Giant Mech ; mech
Boom. We created a Giant Mech Object and dropped it in the room. We also gave it an alias *mech*. Lets describe it.
Boom. We created a Giant Mech Object and dropped it in the room. We also gave it an alias *mech*.
Lets describe it.
@desc mech = This is a huge mech. It has missiles and stuff.
@ -16,36 +20,51 @@ Next we define who can “puppet” the mech object.
@lock mech = puppet:all()
This makes it so that everyone can control the mech. More mechs to the people! (Note that whereas Evennias default commands may look vaguely MUX-like, you can change the syntax to look like whatever interface style you prefer.)
This makes it so that everyone can control the mech. More mechs to the people! (Note that whereas
Evennias default commands may look vaguely MUX-like, you can change the syntax to look like
whatever interface style you prefer.)
Before we continue, lets make a brief detour. Evennia is very flexible about its objects and even more flexible about using and adding commands to those objects. Here are some ground rules well worth remembering for the remainder of this article:
Before we continue, lets make a brief detour. Evennia is very flexible about its objects and even
more flexible about using and adding commands to those objects. Here are some ground rules well
worth remembering for the remainder of this article:
- The [Account](Accounts) represents the real person logging in and has no game-world existence.
- Any [Object](Objects) can be puppeted by an Account (with proper permissions).
- [Characters](Objects#characters), [Rooms](Objects#rooms), and [Exits](Objects#exits) are just children of normal Objects.
- [Characters](Objects#characters), [Rooms](Objects#rooms), and [Exits](Objects#exits) are just
children of normal Objects.
- Any Object can be inside another (except if it creates a loop).
- Any Object can store custom sets of commands on it. Those commands can:
- be made available to the puppeteer (Account),
- be made available to anyone in the same location as the Object, and
- be made available to anyone “inside” the Object
- Also Accounts can store commands on themselves. Account commands are always available unless commands on a puppeted Object explicitly override them.
- Also Accounts can store commands on themselves. Account commands are always available unless
commands on a puppeted Object explicitly override them.
In Evennia, using the `@ic` command will allow you to puppet a given Object (assuming you have puppet-access to do so). As mentioned above, the bog-standard Character class is in fact like any Object: it is auto-puppeted when logging in and just has a command set on it containing the normal in-game commands, like look, inventory, get and so on.
In Evennia, using the `@ic` command will allow you to puppet a given Object (assuming you have
puppet-access to do so). As mentioned above, the bog-standard Character class is in fact like any
Object: it is auto-puppeted when logging in and just has a command set on it containing the normal
in-game commands, like look, inventory, get and so on.
@ic mech
You just jumped out of your Character and *are* now the mech! If people look at you in-game, they will look at a mech. The problem at this point is that the mech Object has no commands of its own. The usual things like look, inventory and get sat on the Character object, remember? So at the moment the mech is not quite as cool as it could be.
You just jumped out of your Character and *are* now the mech! If people look at you in-game, they
will look at a mech. The problem at this point is that the mech Object has no commands of its own.
The usual things like look, inventory and get sat on the Character object, remember? So at the
moment the mech is not quite as cool as it could be.
@ic <Your old Character>
You just jumped back to puppeting your normal, mundane Character again. All is well.
> (But, you ask, where did that `@ic` command come from, if the mech had no commands on it? The answer is that it came from the Account's command set. This is important. Without the Account being the one with the `@ic` command, we would not have been able to get back out of our mech again.)
> (But, you ask, where did that `@ic` command come from, if the mech had no commands on it? The
answer is that it came from the Account's command set. This is important. Without the Account being
the one with the `@ic` command, we would not have been able to get back out of our mech again.)
### Arming the Mech
Let us make the mech a little more interesting. In our favorite text editor, we will create some new mech-suitable commands. In Evennia, commands are defined as Python classes.
Let us make the mech a little more interesting. In our favorite text editor, we will create some new
mech-suitable commands. In Evennia, commands are defined as Python classes.
```python
# in a new file mygame/commands/mechcommands.py
@ -89,7 +108,11 @@ class CmdLaunch(Command):
```
This is saved as a normal Python module (lets call it `mechcommands.py`), in a place Evennia looks for such modules (`mygame/commands/`). This command will trigger when the player gives the command “shoot”, “fire,” or even “fire!” with an exclamation mark. The mech can shoot in the air or at a target if you give one. In a real game the gun would probably be given a chance to hit and give damage to the target, but this is enough for now.
This is saved as a normal Python module (lets call it `mechcommands.py`), in a place Evennia looks
for such modules (`mygame/commands/`). This command will trigger when the player gives the command
“shoot”, “fire,” or even “fire!” with an exclamation mark. The mech can shoot in the air or at a
target if you give one. In a real game the gun would probably be given a chance to hit and give
damage to the target, but this is enough for now.
We also make a second command for launching missiles (`CmdLaunch`). To save
space we wont describe it here; it looks the same except it returns a text
@ -97,7 +120,8 @@ about the missiles being fired and has different `key` and `aliases`. We leave
that up to you to create as an exercise. You could have it print "WOOSH! The
mech launches missiles against <target>!", for example.
Now we shove our commands into a command set. A [Command Set](Command-Sets) (CmdSet) is a container holding any number of commands. The command set is what we will store on the mech.
Now we shove our commands into a command set. A [Command Set](Command-Sets) (CmdSet) is a container
holding any number of commands. The command set is what we will store on the mech.
```python
# in the same file mygame/commands/mechcommands.py
@ -117,11 +141,14 @@ class MechCmdSet(CmdSet):
self.add(CmdLaunch())
```
This simply groups all the commands we want. We add our new shoot/launch commands. Lets head back into the game. For testing we will manually attach our new CmdSet to the mech.
This simply groups all the commands we want. We add our new shoot/launch commands. Lets head back
into the game. For testing we will manually attach our new CmdSet to the mech.
@py self.search("mech").cmdset.add("commands.mechcommands.MechCmdSet")
This is a little Python snippet (run from the command line as an admin) that searches for the mech in our current location and attaches our new MechCmdSet to it. What we add is actually the Python path to our cmdset class. Evennia will import and initialize it behind the scenes.
This is a little Python snippet (run from the command line as an admin) that searches for the mech
in our current location and attaches our new MechCmdSet to it. What we add is actually the Python
path to our cmdset class. Evennia will import and initialize it behind the scenes.
@ic mech
@ -130,15 +157,24 @@ We are back as the mech! Lets do some shooting!
fire!
BOOM! The mech fires its gun in the air!
There we go, one functioning mech. Try your own `launch` command and see that it works too. We can not only walk around as the mech — since the CharacterCmdSet is included in our MechCmdSet, the mech can also do everything a Character could do, like look around, pick up stuff, and have an inventory. We could now shoot the gun at a target or try the missile launch command. Once you have your own mech, what else do you need?
There we go, one functioning mech. Try your own `launch` command and see that it works too. We can
not only walk around as the mech — since the CharacterCmdSet is included in our MechCmdSet, the mech
can also do everything a Character could do, like look around, pick up stuff, and have an inventory.
We could now shoot the gun at a target or try the missile launch command. Once you have your own
mech, what else do you need?
> Note: You'll find that the mech's commands are available to you by just standing in the same location (not just by puppeting it). We'll solve this with a *lock* in the next section.
> Note: You'll find that the mech's commands are available to you by just standing in the same
location (not just by puppeting it). We'll solve this with a *lock* in the next section.
## Making a Mech production line
What weve done so far is just to make a normal Object, describe it and put some commands on it. This is great for testing. The way we added it, the MechCmdSet will even go away if we reload the server. Now we want to make the mech an actual object “type” so we can create mechs without those extra steps. For this we need to create a new Typeclass.
What weve done so far is just to make a normal Object, describe it and put some commands on it.
This is great for testing. The way we added it, the MechCmdSet will even go away if we reload the
server. Now we want to make the mech an actual object “type” so we can create mechs without those
extra steps. For this we need to create a new Typeclass.
A [Typeclass](Typeclasses) is a near-normal Python class that stores its existence to the database behind the scenes. A Typeclass is created in a normal Python source file:
A [Typeclass](Typeclasses) is a near-normal Python class that stores its existence to the database
behind the scenes. A Typeclass is created in a normal Python source file:
```python
# in the new file mygame/typeclasses/mech.py
@ -159,15 +195,24 @@ class Mech(Object):
self.db.desc = "This is a huge mech. It has missiles and stuff."
```
For convenience we include the full contents of the default `CharacterCmdSet` in there. This will make a Characters normal commands available to the mech. We also add the mech-commands from before, making sure they are stored persistently in the database. The locks specify that anyone can puppet the meck and no-one can "call" the mech's Commands from 'outside' it - you have to puppet it to be able to shoot.
For convenience we include the full contents of the default `CharacterCmdSet` in there. This will
make a Characters normal commands available to the mech. We also add the mech-commands from before,
making sure they are stored persistently in the database. The locks specify that anyone can puppet
the meck and no-one can "call" the mech's Commands from 'outside' it - you have to puppet it to be
able to shoot.
Thats it. When Objects of this type are created, they will always start out with the mechs command set and the correct lock. We set a default description, but you would probably change this with `@desc` to individualize your mechs as you build them.
Thats it. When Objects of this type are created, they will always start out with the mechs command
set and the correct lock. We set a default description, but you would probably change this with
`@desc` to individualize your mechs as you build them.
Back in the game, just exit the old mech (`@ic` back to your old character) then do
@create/drop The Bigger Mech ; bigmech : mech.Mech
We create a new, bigger mech with an alias bigmech. Note how we give the python-path to our Typeclass at the end — this tells Evennia to create the new object based on that class (we don't have to give the full path in our game dir `typeclasses.mech.Mech` because Evennia knows to look in the `typeclasses` folder already). A shining new mech will appear in the room! Just use
We create a new, bigger mech with an alias bigmech. Note how we give the python-path to our
Typeclass at the end — this tells Evennia to create the new object based on that class (we don't
have to give the full path in our game dir `typeclasses.mech.Mech` because Evennia knows to look in
the `typeclasses` folder already). A shining new mech will appear in the room! Just use
@ic bigmech
@ -175,10 +220,17 @@ to take it on a test drive.
## Future Mechs
To expand on this you could add more commands to the mech and remove others. Maybe the mech shouldnt work just like a Character after all. Maybe it makes loud noises every time it passes from room to room. Maybe it cannot pick up things without crushing them. Maybe it needs fuel, ammo and repairs. Maybe youll lock it down so it can only be puppeted by emo teenagers.
To expand on this you could add more commands to the mech and remove others. Maybe the mech
shouldnt work just like a Character after all. Maybe it makes loud noises every time it passes from
room to room. Maybe it cannot pick up things without crushing them. Maybe it needs fuel, ammo and
repairs. Maybe youll lock it down so it can only be puppeted by emo teenagers.
Having you puppet the mech-object directly is also just one way to implement a giant mech in Evennia.
Having you puppet the mech-object directly is also just one way to implement a giant mech in
Evennia.
For example, you could instead picture a mech as a “vehicle” that you “enter” as your normal Character (since any Object can move inside another). In that case the “insides” of the mech Object could be the “cockpit”. The cockpit would have the `MechCommandSet` stored on itself and all the shooting goodness would be made available to you only when you enter it.
For example, you could instead picture a mech as a “vehicle” that you “enter” as your normal
Character (since any Object can move inside another). In that case the “insides” of the mech Object
could be the “cockpit”. The cockpit would have the `MechCommandSet` stored on itself and all the
shooting goodness would be made available to you only when you enter it.
And of course you could put more guns on it. And make it fly.
And of course you could put more guns on it. And make it fly.

View file

@ -3,23 +3,36 @@
# The building_menu contrib
This contrib allows you to write custom and easy to use building menus. As the name implies, these menus are most useful for building things, that is, your builders might appreciate them, although you can use them for your players as well.
This contrib allows you to write custom and easy to use building menus. As the name implies, these
menus are most useful for building things, that is, your builders might appreciate them, although
you can use them for your players as well.
Building menus are somewhat similar to `EvMenu` although they don't use the same system at all and are intended to make building easier. They replicate what other engines refer to as "building editors", which allow to you to build in a menu instead of having to enter a lot of complex commands. Builders might appreciate this simplicity, and if the code that was used to create them is simple as well, coders could find this contrib useful.
Building menus are somewhat similar to `EvMenu` although they don't use the same system at all and
are intended to make building easier. They replicate what other engines refer to as "building
editors", which allow to you to build in a menu instead of having to enter a lot of complex
commands. Builders might appreciate this simplicity, and if the code that was used to create them
is simple as well, coders could find this contrib useful.
## A simple menu
Before diving in, there are some things to point out:
- Building menus work on an object. This object will be edited by manipulations in the menu. So you can create a menu to add/edit a room, an exit, a character and so on.
- Building menus are arranged in layers of choices. A choice gives access to an option or to a sub-menu. Choices are linked to commands (usually very short). For instance, in the example shown below, to edit the room key, after opening the building menu, you can type `k`. That will lead you to the key choice where you can enter a new key for the room. Then you can enter `@` to leave this choice and go back to the entire menu. (All of this can be changed).
- To open the menu, you will need something like a command. This contrib offers a basic command for demonstration, but we will override it in this example, using the same code with more flexibility.
- Building menus work on an object. This object will be edited by manipulations in the menu. So
you can create a menu to add/edit a room, an exit, a character and so on.
- Building menus are arranged in layers of choices. A choice gives access to an option or to a sub-
menu. Choices are linked to commands (usually very short). For instance, in the example shown
below, to edit the room key, after opening the building menu, you can type `k`. That will lead you
to the key choice where you can enter a new key for the room. Then you can enter `@` to leave this
choice and go back to the entire menu. (All of this can be changed).
- To open the menu, you will need something like a command. This contrib offers a basic command for
demonstration, but we will override it in this example, using the same code with more flexibility.
So let's add a very basic example to begin with.
### A generic editing command
Let's begin by adding a new command. You could add or edit the following file (there's no trick here, feel free to organize the code differently):
Let's begin by adding a new command. You could add or edit the following file (there's no trick
here, feel free to organize the code differently):
```python
# file: commands/building.py
@ -60,7 +73,8 @@ class EditCmd(Command):
if obj.typename == "Room":
Menu = RoomBuildingMenu
else:
self.msg("|rThe object {} cannot be edited.|n".format(obj.get_display_name(self.caller)))
self.msg("|rThe object {} cannot be
edited.|n".format(obj.get_display_name(self.caller)))
return
menu = Menu(self.caller, obj)
@ -70,19 +84,29 @@ class EditCmd(Command):
This command is rather simple in itself:
1. It has a key `@edit` and a lock to only allow builders to use it.
2. In its `func` method, it begins by checking the arguments, returning an error if no argument is specified.
3. It then searches for the given argument. We search globally. The `search` method used in this way will return the found object or `None`. It will also send the error message to the caller if necessary.
4. Assuming we have found an object, we check the object `typename`. This will be used later when we want to display several building menus. For the time being, we only handle `Room`. If the caller specified something else, we'll display an error.
5. Assuming this object is a `Room`, we have defined a `Menu` object containing the class of our building menu. We build this class (creating an instance), giving it the caller and the object to edit.
2. In its `func` method, it begins by checking the arguments, returning an error if no argument is
specified.
3. It then searches for the given argument. We search globally. The `search` method used in this
way will return the found object or `None`. It will also send the error message to the caller if
necessary.
4. Assuming we have found an object, we check the object `typename`. This will be used later when
we want to display several building menus. For the time being, we only handle `Room`. If the
caller specified something else, we'll display an error.
5. Assuming this object is a `Room`, we have defined a `Menu` object containing the class of our
building menu. We build this class (creating an instance), giving it the caller and the object to
edit.
6. We then open the building menu, using the `open` method.
The end might sound a bit surprising at first glance. But the process is still very simple: we create an instance of our building menu and call its `open` method. Nothing more.
The end might sound a bit surprising at first glance. But the process is still very simple: we
create an instance of our building menu and call its `open` method. Nothing more.
> Where is our building menu?
If you go ahead and add this command and test it, you'll get an error. We haven't defined `RoomBuildingMenu` yet.
If you go ahead and add this command and test it, you'll get an error. We haven't defined
`RoomBuildingMenu` yet.
To add this command, edit `commands/default_cmdsets.py`. Import our command, adding an import line at the top of the file:
To add this command, edit `commands/default_cmdsets.py`. Import our command, adding an import line
at the top of the file:
```python
"""
@ -119,7 +143,8 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
### Our first menu
So far, we can't use our building menu. Our `@edit` command will throw an error. We have to define the `RoomBuildingMenu` class. Open the `commands/building.py` file and add to the end of the file:
So far, we can't use our building menu. Our `@edit` command will throw an error. We have to define
the `RoomBuildingMenu` class. Open the `commands/building.py` file and add to the end of the file:
```python
# ... at the end of commands/building.py
@ -138,7 +163,9 @@ class RoomBuildingMenu(BuildingMenu):
self.add_choice("key", "k", attr="key")
```
Save these changes, reload your game. You can now use the `@edit` command. Here's what we get (notice that the commands we enter into the game are prefixed with `> `, though this prefix will probably not appear in your MUD client):
Save these changes, reload your game. You can now use the `@edit` command. Here's what we get
(notice that the commands we enter into the game are prefixed with `> `, though this prefix will
probably not appear in your MUD client):
```
> look
@ -204,10 +231,12 @@ Before diving into the code, let's examine what we have:
- When we use the `@edit here` command, a building menu for this room appears.
- This menu has two choices:
- Enter `k` to edit the room key. You will go into a choice where you can simply type the key room key (the way we have done here). You can use `@` to go back to the menu.
- Enter `k` to edit the room key. You will go into a choice where you can simply type the key
room key (the way we have done here). You can use `@` to go back to the menu.
- You can use `q` to quit the menu.
We then check, with the `look` command, that the menu has modified this room key. So by adding a class, with a method and a single line of code within, we've added a menu with two choices.
We then check, with the `look` command, that the menu has modified this room key. So by adding a
class, with a method and a single line of code within, we've added a menu with two choices.
### Code explanation
@ -227,34 +256,53 @@ class RoomBuildingMenu(BuildingMenu):
self.add_choice("key", "k", attr="key")
```
- We first create a class inheriting from `BuildingMenu`. This is usually the case when we want to create a building menu with this contrib.
- We first create a class inheriting from `BuildingMenu`. This is usually the case when we want to
create a building menu with this contrib.
- In this class, we override the `init` method, which is called when the menu opens.
- In this `init` method, we call `add_choice`. This takes several arguments, but we've defined only three here:
- The choice name. This is mandatory and will be used by the building menu to know how to display this choice.
- The command key to access this choice. We've given a simple `"k"`. Menu commands usually are pretty short (that's part of the reason building menus are appreciated by builders). You can also specify additional aliases, but we'll see that later.
- We've added a keyword argument, `attr`. This tells the building menu that when we are in this choice, the text we enter goes into this attribute name. It's called `attr`, but it could be a room attribute or a typeclass persistent or non-persistent attribute (we'll see other examples as well).
- In this `init` method, we call `add_choice`. This takes several arguments, but we've defined only
three here:
- The choice name. This is mandatory and will be used by the building menu to know how to
display this choice.
- The command key to access this choice. We've given a simple `"k"`. Menu commands usually are
pretty short (that's part of the reason building menus are appreciated by builders). You can also
specify additional aliases, but we'll see that later.
- We've added a keyword argument, `attr`. This tells the building menu that when we are in this
choice, the text we enter goes into this attribute name. It's called `attr`, but it could be a room
attribute or a typeclass persistent or non-persistent attribute (we'll see other examples as well).
> We've added the menu choice for `key` here, why is another menu choice defined for `quit`?
Our building menu creates a choice at the end of our choice list if it's a top-level menu (sub-menus don't have this feature). You can, however, override it to provide a different "quit" message or to perform some actions.
Our building menu creates a choice at the end of our choice list if it's a top-level menu (sub-menus
don't have this feature). You can, however, override it to provide a different "quit" message or to
perform some actions.
I encourage you to play with this code. As simple as it is, it offers some functionalities already.
## Customizing building menus
This somewhat long section explains how to customize building menus. There are different ways depending on what you would like to achieve. We'll go from specific to more advanced here.
This somewhat long section explains how to customize building menus. There are different ways
depending on what you would like to achieve. We'll go from specific to more advanced here.
### Generic choices
In the previous example, we've used `add_choice`. This is one of three methods you can use to add choices. The other two are to handle more generic actions:
In the previous example, we've used `add_choice`. This is one of three methods you can use to add
choices. The other two are to handle more generic actions:
- `add_choice_edit`: this is called to add a choice which points to the `EvEditor`. It is used to edit a description in most cases, although you could edit other things. We'll see an example shortly. `add_choice_edit` uses most of the `add_choice` keyword arguments we'll see, but usually we specify only two (sometimes three):
- `add_choice_edit`: this is called to add a choice which points to the `EvEditor`. It is used to
edit a description in most cases, although you could edit other things. We'll see an example
shortly. `add_choice_edit` uses most of the `add_choice` keyword arguments we'll see, but usually
we specify only two (sometimes three):
- The choice title as usual.
- The choice key (command key) as usual.
- Optionally, the attribute of the object to edit, with the `attr` keyword argument. By default, `attr` contains `db.desc`. It means that this persistent data attribute will be edited by the `EvEditor`. You can change that to whatever you want though.
- `add_choice_quit`: this allows to add a choice to quit the editor. Most advisable! If you don't do it, the building menu will do it automatically, except if you really tell it not to. Again, you can specify the title and key of this menu. You can also call a function when this menu closes.
- Optionally, the attribute of the object to edit, with the `attr` keyword argument. By
default, `attr` contains `db.desc`. It means that this persistent data attribute will be edited by
the `EvEditor`. You can change that to whatever you want though.
- `add_choice_quit`: this allows to add a choice to quit the editor. Most advisable! If you don't
do it, the building menu will do it automatically, except if you really tell it not to. Again, you
can specify the title and key of this menu. You can also call a function when this menu closes.
So here's a more complete example (you can replace your `RoomBuildingMenu` class in `commands/building.py` to see it):
So here's a more complete example (you can replace your `RoomBuildingMenu` class in
`commands/building.py` to see it):
```python
class RoomBuildingMenu(BuildingMenu):
@ -269,7 +317,9 @@ class RoomBuildingMenu(BuildingMenu):
self.add_choice_quit("quit this editor", "q")
```
So far, our building menu class is still thin... and yet we already have some interesting feature. See for yourself the following MUD client output (again, the commands are prefixed with `> ` to distinguish them):
So far, our building menu class is still thin... and yet we already have some interesting feature.
See for yourself the following MUD client output (again, the commands are prefixed with `> ` to
distinguish them):
```
> @reload
@ -316,27 +366,52 @@ A beautiful meadow(#2)
This is a beautiful meadow. But so beautiful I can't describe it.
```
So by using the `d` shortcut in our building menu, an `EvEditor` opens. You can use the `EvEditor` commands (like we did here, `:DD` to remove all, `:wq` to save and quit). When you quit the editor, the description is saved (here, in `room.db.desc`) and you go back to the building menu.
So by using the `d` shortcut in our building menu, an `EvEditor` opens. You can use the `EvEditor`
commands (like we did here, `:DD` to remove all, `:wq` to save and quit). When you quit the editor,
the description is saved (here, in `room.db.desc`) and you go back to the building menu.
Notice that the choice to quit has changed too, which is due to our adding `add_choice_quit`. In most cases, you will probably not use this method, since the quit menu is added automatically.
Notice that the choice to quit has changed too, which is due to our adding `add_choice_quit`. In
most cases, you will probably not use this method, since the quit menu is added automatically.
### `add_choice` options
`add_choice` and the two methods `add_choice_edit` and `add_choice_quit` take a lot of optional arguments to make customization easier. Some of these options might not apply to `add_choice_edit` or `add_choice_quit` however.
`add_choice` and the two methods `add_choice_edit` and `add_choice_quit` take a lot of optional
arguments to make customization easier. Some of these options might not apply to `add_choice_edit`
or `add_choice_quit` however.
Below are the options of `add_choice`, specify them as arguments:
- The first positional, mandatory argument is the choice title, as we have seen. This will influence how the choice appears in the menu.
- The second positional, mandatory argument is the command key to access to this menu. It is best to use keyword arguments for the other arguments.
- The `aliases` keyword argument can contain a list of aliases that can be used to access to this menu. For instance: `add_choice(..., aliases=['t'])`
- The `attr` keyword argument contains the attribute to edit when this choice is selected. It's a string, it has to be the name, from the object (specified in the menu constructor) to reach this attribute. For instance, a `attr` of `"key"` will try to find `obj.key` to read and write the attribute. You can specify more complex attribute names, for instance, `attr="db.desc"` to set the `desc` persistent attribute, or `attr="ndb.something"` so use a non-persistent data attribute on the object.
- The `text` keyword argument is used to change the text that will be displayed when the menu choice is selected. Menu choices provide a default text that you can change. Since this is a long text, it's useful to use multi-line strings (see an example below).
- The `glance` keyword argument is used to specify how to display the current information while in the menu, when the choice hasn't been opened. If you examine the previous examples, you will see that the current (`key` or `db.desc`) was shown in the menu, next to the command key. This is useful for seeing at a glance the current value (hence the name). Again, menu choices will provide a default glance if you don't specify one.
- The `on_enter` keyword argument allows to add a callback to use when the menu choice is opened. This is more advanced, but sometimes useful.
- The `on_nomatch` keyword argument is called when, once in the menu, the caller enters some text that doesn't match any command (including the `@` command). By default, this will edit the specified `attr`.
- The `on_leave` keyword argument allows to specify a callback used when the caller leaves the menu choice. This can be useful for cleanup as well.
- The first positional, mandatory argument is the choice title, as we have seen. This will
influence how the choice appears in the menu.
- The second positional, mandatory argument is the command key to access to this menu. It is best
to use keyword arguments for the other arguments.
- The `aliases` keyword argument can contain a list of aliases that can be used to access to this
menu. For instance: `add_choice(..., aliases=['t'])`
- The `attr` keyword argument contains the attribute to edit when this choice is selected. It's a
string, it has to be the name, from the object (specified in the menu constructor) to reach this
attribute. For instance, a `attr` of `"key"` will try to find `obj.key` to read and write the
attribute. You can specify more complex attribute names, for instance, `attr="db.desc"` to set the
`desc` persistent attribute, or `attr="ndb.something"` so use a non-persistent data attribute on the
object.
- The `text` keyword argument is used to change the text that will be displayed when the menu choice
is selected. Menu choices provide a default text that you can change. Since this is a long text,
it's useful to use multi-line strings (see an example below).
- The `glance` keyword argument is used to specify how to display the current information while in
the menu, when the choice hasn't been opened. If you examine the previous examples, you will see
that the current (`key` or `db.desc`) was shown in the menu, next to the command key. This is
useful for seeing at a glance the current value (hence the name). Again, menu choices will provide
a default glance if you don't specify one.
- The `on_enter` keyword argument allows to add a callback to use when the menu choice is opened.
This is more advanced, but sometimes useful.
- The `on_nomatch` keyword argument is called when, once in the menu, the caller enters some text
that doesn't match any command (including the `@` command). By default, this will edit the
specified `attr`.
- The `on_leave` keyword argument allows to specify a callback used when the caller leaves the menu
choice. This can be useful for cleanup as well.
These are a lot of possibilities, and most of the time you won't need them all. Here is a short example using some of these arguments (again, replace the `RoomBuildingMenu` class in `commands/building.py` with the following code to see it working):
These are a lot of possibilities, and most of the time you won't need them all. Here is a short
example using some of these arguments (again, replace the `RoomBuildingMenu` class in
`commands/building.py` with the following code to see it working):
```python
class RoomBuildingMenu(BuildingMenu):
@ -395,18 +470,27 @@ Building menu: A beautiful meadow
Closing the building menu.
```
The most surprising part is no doubt the text. We use the multi-line syntax (with `"""`). Excessive spaces will be removed from the left for each line automatically. We specify some information between braces... sometimes using double braces. What might be a bit odd:
The most surprising part is no doubt the text. We use the multi-line syntax (with `"""`).
Excessive spaces will be removed from the left for each line automatically. We specify some
information between braces... sometimes using double braces. What might be a bit odd:
- `{back}` is a direct format argument we'll use (see the `.format` specifiers).
- `{{obj...}} refers to the object being edited. We use two braces, because `.format` will remove them.
- `{{obj...}} refers to the object being edited. We use two braces, because `.format` will remove
them.
In `glance`, we also use `{obj.key}` to indicate we want to show the room's key.
### Everything can be a function
The keyword arguments of `add_choice` are often strings (type `str`). But each of these arguments can also be a function. This allows for a lot of customization, since we define the callbacks that will be executed to achieve such and such an operation.
The keyword arguments of `add_choice` are often strings (type `str`). But each of these arguments
can also be a function. This allows for a lot of customization, since we define the callbacks that
will be executed to achieve such and such an operation.
To demonstrate, we will try to add a new feature. Our building menu for rooms isn't that bad, but it would be great to be able to edit exits too. So we can add a new menu choice below description... but how to actually edit exits? Exits are not just an attribute to set: exits are objects (of type `Exit` by default) which stands between two rooms (object of type `Room`). So how can we show that?
To demonstrate, we will try to add a new feature. Our building menu for rooms isn't that bad, but
it would be great to be able to edit exits too. So we can add a new menu choice below
description... but how to actually edit exits? Exits are not just an attribute to set: exits are
objects (of type `Exit` by default) which stands between two rooms (object of type `Room`). So how
can we show that?
First let's add a couple of exits in limbo, so we have something to work with:
@ -431,9 +515,11 @@ We can access room exits with the `exits` property:
[<Exit: north>, <Exit: south>]
```
So what we need is to display this list in our building menu... and to allow to edit it would be great. Perhaps even add new exits?
So what we need is to display this list in our building menu... and to allow to edit it would be
great. Perhaps even add new exits?
First of all, let's write a function to display the `glance` on existing exits. Here's the code, it's explained below:
First of all, let's write a function to display the `glance` on existing exits. Here's the code,
it's explained below:
```python
class RoomBuildingMenu(BuildingMenu):
@ -470,7 +556,10 @@ def glance_exits(room):
return "\n |gNo exit yet|n"
```
When the building menu opens, it displays each choice to the caller. A choice is displayed with its title (rendered a bit nicely to show the key as well) and the glance. In the case of the `exits` choice, the glance is a function, so the building menu calls this function giving it the object being edited (the room here). The function should return the text to see.
When the building menu opens, it displays each choice to the caller. A choice is displayed with its
title (rendered a bit nicely to show the key as well) and the glance. In the case of the `exits`
choice, the glance is a function, so the building menu calls this function giving it the object
being edited (the room here). The function should return the text to see.
```
> @edit here
@ -490,13 +579,24 @@ Closing the editor.
> How do I know the parameters of the function to give?
The function you give can accept a lot of different parameters. This allows for a flexible approach but might seem complicated at first. Basically, your function can accept any parameter, and the building menu will send only the parameter based on their names. If your function defines an argument named `caller` for instance (like `def func(caller):` ), then the building menu knows that the first argument should contain the caller of the building menu. Here are the arguments, you don't have to specify them (if you do, they need to have the same name):
The function you give can accept a lot of different parameters. This allows for a flexible approach
but might seem complicated at first. Basically, your function can accept any parameter, and the
building menu will send only the parameter based on their names. If your function defines an
argument named `caller` for instance (like `def func(caller):` ), then the building menu knows that
the first argument should contain the caller of the building menu. Here are the arguments, you
don't have to specify them (if you do, they need to have the same name):
- `menu`: if your function defines an argument named `menu`, it will contain the building menu itself.
- `choice`: if your function defines an argument named `choice`, it will contain the `Choice` object representing this menu choice.
- `string`: if your function defines an argument named `string`, it will contain the user input to reach this menu choice. This is not very useful, except on `nomatch` callbacks which we'll see later.
- `obj`: if your function defines an argument named `obj`, it will contain the building menu edited object.
- `caller`: if your function defines an argument named `caller`, it will contain the caller of the building menu.
- `menu`: if your function defines an argument named `menu`, it will contain the building menu
itself.
- `choice`: if your function defines an argument named `choice`, it will contain the `Choice` object
representing this menu choice.
- `string`: if your function defines an argument named `string`, it will contain the user input to
reach this menu choice. This is not very useful, except on `nomatch` callbacks which we'll see
later.
- `obj`: if your function defines an argument named `obj`, it will contain the building menu edited
object.
- `caller`: if your function defines an argument named `caller`, it will contain the caller of the
building menu.
- Anything else: any other argument will contain the object being edited by the building menu.
So in our case:
@ -505,13 +605,18 @@ So in our case:
def glance_exits(room):
```
The only argument we need is `room`. It's not present in the list of possible arguments, so the editing object of the building menu (the room, here) is given.
The only argument we need is `room`. It's not present in the list of possible arguments, so the
editing object of the building menu (the room, here) is given.
> Why is it useful to get the menu or choice object?
Most of the time, you will not need these arguments. In very rare cases, you will use them to get specific data (like the default attribute that was set). This tutorial will not elaborate on these possibilities. Just know that they exist.
Most of the time, you will not need these arguments. In very rare cases, you will use them to get
specific data (like the default attribute that was set). This tutorial will not elaborate on these
possibilities. Just know that they exist.
We should also define a text callback, so that we can enter our menu to see the room exits. We'll see how to edit them in the next section but this is a good opportunity to show a more complete callback. To see it in action, as usual, replace the class and functions in `commands/building.py`:
We should also define a text callback, so that we can enter our menu to see the room exits. We'll
see how to edit them in the next section but this is a good opportunity to show a more complete
callback. To see it in action, as usual, replace the class and functions in `commands/building.py`:
```python
# Our building menu
@ -569,7 +674,9 @@ def text_exits(caller, room):
return text
```
Look at the second callback in particular. It takes an additional argument, the caller (remember, the argument names are important, their order is not relevant). This is useful for displaying destination of exits accurately. Here is a demonstration of this menu:
Look at the second callback in particular. It takes an additional argument, the caller (remember,
the argument names are important, their order is not relevant). This is useful for displaying
destination of exits accurately. Here is a demonstration of this menu:
```
> @edit here
@ -612,21 +719,36 @@ Using callbacks allows a great flexibility. We'll now see how to handle sub-men
### Sub-menus for complex menus
A menu is relatively flat: it has a root (where you see all the menu choices) and individual choices you can go to using the menu choice keys. Once in a choice you can type some input or go back to the root menu by entering the return command (usually `@`).
A menu is relatively flat: it has a root (where you see all the menu choices) and individual choices
you can go to using the menu choice keys. Once in a choice you can type some input or go back to
the root menu by entering the return command (usually `@`).
Why shouldn't individual exits have their own menu though? Say, you edit an exit and can change its key, description or aliases... perhaps even destination? Why ever not? It would make building much easier!
Why shouldn't individual exits have their own menu though? Say, you edit an exit and can change its
key, description or aliases... perhaps even destination? Why ever not? It would make building much
easier!
The building menu system offers two ways to do that. The first is nested keys: nested keys allow to go beyond just one menu/choice, to have menus with more layers. Using them is quick but might feel a bit counter-intuitive at first. Another option is to create a different menu class and redirect from the first to the second. This option might require more lines but is more explicit and can be re-used for multiple menus. Adopt one of them depending of your taste.
The building menu system offers two ways to do that. The first is nested keys: nested keys allow to
go beyond just one menu/choice, to have menus with more layers. Using them is quick but might feel
a bit counter-intuitive at first. Another option is to create a different menu class and redirect
from the first to the second. This option might require more lines but is more explicit and can be
re-used for multiple menus. Adopt one of them depending of your taste.
#### Nested menu keys
So far, we've only used menu keys with one letter. We can add more, of course, but menu keys in their simple shape are just command keys. Press "e" to go to the "exits" choice.
So far, we've only used menu keys with one letter. We can add more, of course, but menu keys in
their simple shape are just command keys. Press "e" to go to the "exits" choice.
But menu keys can be nested. Nested keys allow to add choices with sub-menus. For instance, type "e" to go to the "exits" choice, and then you can type "c" to open a menu to create a new exit, or "d" to open a menu to delete an exit. The first menu would have the "e.c" key (first e, then c), the second menu would have key as "e.d".
But menu keys can be nested. Nested keys allow to add choices with sub-menus. For instance, type
"e" to go to the "exits" choice, and then you can type "c" to open a menu to create a new exit, or
"d" to open a menu to delete an exit. The first menu would have the "e.c" key (first e, then c),
the second menu would have key as "e.d".
That's more advanced and, if the following code doesn't sound very friendly to you, try the next section which provides a different approach of the same problem.
That's more advanced and, if the following code doesn't sound very friendly to you, try the next
section which provides a different approach of the same problem.
So we would like to edit exits. That is, you can type "e" to go into the choice of exits, then enter `@e` followed by the exit name to edit it... which will open another menu. In this sub-menu you could change the exit key or description.
So we would like to edit exits. That is, you can type "e" to go into the choice of exits, then
enter `@e` followed by the exit name to edit it... which will open another menu. In this sub-menu
you could change the exit key or description.
So we have a menu hierarchy similar to that:
@ -707,7 +829,8 @@ Building menu: A beautiful meadow
Closing the building menu.
```
This needs a bit of code and a bit of explanation. So here we go... the code first, the explanations next!
This needs a bit of code and a bit of explanation. So here we go... the code first, the
explanations next!
```python
# ... from commands/building.py
@ -733,7 +856,8 @@ class RoomBuildingMenu(BuildingMenu):
Current title: |c{{obj.key}}|n
""".format(back="|n or |y".join(self.keys_go_back)))
self.add_choice_edit("description", "d")
self.add_choice("exits", "e", glance=glance_exits, text=text_exits, on_nomatch=nomatch_exits)
self.add_choice("exits", "e", glance=glance_exits, text=text_exits,
on_nomatch=nomatch_exits)
# Exit sub-menu
self.add_choice("exit", "e.*", text=text_single_exit, on_nomatch=nomatch_single_exit)
@ -815,16 +939,37 @@ def nomatch_single_exit(menu, caller, room, string):
> That's a lot of code! And we only handle editing the exit key!
That's why at some point you might want to write a real sub-menu, instead of using simple nested keys. But you might need both to build pretty menus too!
That's why at some point you might want to write a real sub-menu, instead of using simple nested
keys. But you might need both to build pretty menus too!
1. The first thing new is in our menu class. After creating a `on_nomatch` callback for the exits menu (that shouldn't be a surprised), we need to add a nested key. We give this menu a key of `"e.*"`. That's a bit odd! "e" is our key to the exits menu, . is the separator to indicate a nested menu, and * means anything. So basically, we create a nested menu that is contains within the exits menu and anything. We'll see what this "anything" is in practice.
1. The first thing new is in our menu class. After creating a `on_nomatch` callback for the exits
menu (that shouldn't be a surprised), we need to add a nested key. We give this menu a key of
`"e.*"`. That's a bit odd! "e" is our key to the exits menu, . is the separator to indicate a
nested menu, and * means anything. So basically, we create a nested menu that is contains within
the exits menu and anything. We'll see what this "anything" is in practice.
2. The `glance_exits` and `text_exits` are basically the same.
3. The `nomatch_exits` is short but interesting. It's called when we enter some text in the "exits" menu (that is, in the list of exits). We have said that the user should enter `@e` followed by the exit name to edit it. So in the `nomatch_exits` callbac, we check for that input. If the entered text begins by `@e`, we try to find the exit in the room. If we do...
4. We call the `menu.move` method. That's where things get a bit complicated with nested menus: we need to use `menu.move` to change from layer to layer. Here, we are in the choice of exits (the exits menu, of key "e"). We need to go down one layer to edit an exit. So we call `menu.move` and give it an exit object. The menu system remembers what position the user is based on the keys she has entered: when the user opens the menu, there is no key. If she selects the exits choice, the menu key being "e", the position of the user is `["e"]` (a list with the menu keys). If we call `menu.move`, whatever we give to this method will be appended to the list of keys, so that the user position becomes `["e", <Exit object>]`.
5. In the menu class, we have defined the menu "e.*", meaning "the menu contained in the exits choice plus anything". The "anything" here is an exit: we have called `menu.move(exit)`, so the `"e.*"` menu choice is chosen.
6. In this menu, the text is set to a callback. There is also a `on_nomatch` callback that is called whenever the user enters some text. If so, we change the exit name.
3. The `nomatch_exits` is short but interesting. It's called when we enter some text in the "exits"
menu (that is, in the list of exits). We have said that the user should enter `@e` followed by the
exit name to edit it. So in the `nomatch_exits` callbac, we check for that input. If the entered
text begins by `@e`, we try to find the exit in the room. If we do...
4. We call the `menu.move` method. That's where things get a bit complicated with nested menus: we
need to use `menu.move` to change from layer to layer. Here, we are in the choice of exits (the
exits menu, of key "e"). We need to go down one layer to edit an exit. So we call `menu.move` and
give it an exit object. The menu system remembers what position the user is based on the keys she
has entered: when the user opens the menu, there is no key. If she selects the exits choice, the
menu key being "e", the position of the user is `["e"]` (a list with the menu keys). If we call
`menu.move`, whatever we give to this method will be appended to the list of keys, so that the user
position becomes `["e", <Exit object>]`.
5. In the menu class, we have defined the menu "e.*", meaning "the menu contained in the exits
choice plus anything". The "anything" here is an exit: we have called `menu.move(exit)`, so the
`"e.*"` menu choice is chosen.
6. In this menu, the text is set to a callback. There is also a `on_nomatch` callback that is
called whenever the user enters some text. If so, we change the exit name.
Using `menu.move` like this is a bit confusing at first. Sometimes it's useful. In this case, if we want a more complex menu for exits, it makes sense to use a real sub-menu, not nested keys like this. But sometimes, you will find yourself in a situation where you don't need a full menu to handle a choice.
Using `menu.move` like this is a bit confusing at first. Sometimes it's useful. In this case, if
we want a more complex menu for exits, it makes sense to use a real sub-menu, not nested keys like
this. But sometimes, you will find yourself in a situation where you don't need a full menu to
handle a choice.
#### Full sub-menu as separate classes
@ -833,7 +978,8 @@ The best way to handle individual exits is to create two separate classes:
- One for the room menu.
- One for the individual exit menu.
The first one will have to redirect on the second. This might be more intuitive and flexible, depending on what you want to achieve. So let's build two menus:
The first one will have to redirect on the second. This might be more intuitive and flexible,
depending on what you want to achieve. So let's build two menus:
```python
# Still in commands/building.py, replace the menu class and functions by...
@ -856,7 +1002,8 @@ class RoomBuildingMenu(BuildingMenu):
Current title: |c{{obj.key}}|n
""".format(back="|n or |y".join(self.keys_go_back)))
self.add_choice_edit("description", "d")
self.add_choice("exits", "e", glance=glance_exits, text=text_exits, on_nomatch=nomatch_exits)
self.add_choice("exits", "e", glance=glance_exits, text=text_exits,
on_nomatch=nomatch_exits)
# Menu functions
@ -916,7 +1063,8 @@ class ExitBuildingMenu(BuildingMenu):
self.add_choice_edit("description", "d")
```
The code might be much easier to read. But before detailing it, let's see how it behaves in the game:
The code might be much easier to read. But before detailing it, let's see how it behaves in the
game:
```
> @edit here
@ -1029,28 +1177,43 @@ north
This is the northern exit. Cool huh?
```
Very simply, we created two menus and bridged them together. This needs much less callbacks. There is only one line in the `nomatch_exits` to add:
Very simply, we created two menus and bridged them together. This needs much less callbacks. There
is only one line in the `nomatch_exits` to add:
```python
menu.open_submenu("commands.building.ExitBuildingMenu", exit, parent_keys=["e"])
```
We have to call `open_submenu` on the menu object (which opens, as its name implies, a sub menu) with three arguments:
We have to call `open_submenu` on the menu object (which opens, as its name implies, a sub menu)
with three arguments:
- The path of the menu class to create. It's the Python class leading to the menu (notice the dots).
- The path of the menu class to create. It's the Python class leading to the menu (notice the
dots).
- The object that will be edited by the menu. Here, it's our exit, so we give it to the sub-menu.
- The keys of the parent to open when the sub-menu closes. Basically, when we're in the root of the sub-menu and press `@`, we'll open the parent menu, with the parent keys. So we specify `["e"]`, since the parent menus is the "exits" choice.
- The keys of the parent to open when the sub-menu closes. Basically, when we're in the root of the
sub-menu and press `@`, we'll open the parent menu, with the parent keys. So we specify `["e"]`,
since the parent menus is the "exits" choice.
And that's it. The new class will be automatically created. As you can see, we have to create a `on_nomatch` callback to open the sub-menu, but once opened, it automatically close whenever needed.
And that's it. The new class will be automatically created. As you can see, we have to create a
`on_nomatch` callback to open the sub-menu, but once opened, it automatically close whenever needed.
### Generic menu options
There are some options that can be set on any menu class. These options allow for greater customization. They are class attributes (see the example below), so just set them in the class body:
There are some options that can be set on any menu class. These options allow for greater
customization. They are class attributes (see the example below), so just set them in the class
body:
- `keys_go_back` (default to `["@"]`): the keys to use to go back in the menu hierarchy, from choice to root menu, from sub-menu to parent-menu. By default, only a `@` is used. You can change this key for one menu or all of them. You can define multiple return commands if you want.
- `sep_keys` (default `"."`): this is the separator for nested keys. There is no real need to redefine it except if you really need the dot as a key, and need nested keys in your menu.
- `joker_key` (default to `"*"`): used for nested keys to indicate "any key". Again, you shouldn't need to change it unless you want to be able to use the @*@ in a command key, and also need nested keys in your menu.
- `min_shortcut` (default to `1`): although we didn't see it here, one can create a menu choice without giving it a key. If so, the menu system will try to "guess" the key. This option allows to change the minimum length of any key for security reasons.
- `keys_go_back` (default to `["@"]`): the keys to use to go back in the menu hierarchy, from choice
to root menu, from sub-menu to parent-menu. By default, only a `@` is used. You can change this
key for one menu or all of them. You can define multiple return commands if you want.
- `sep_keys` (default `"."`): this is the separator for nested keys. There is no real need to
redefine it except if you really need the dot as a key, and need nested keys in your menu.
- `joker_key` (default to `"*"`): used for nested keys to indicate "any key". Again, you shouldn't
need to change it unless you want to be able to use the @*@ in a command key, and also need nested
keys in your menu.
- `min_shortcut` (default to `1`): although we didn't see it here, one can create a menu choice
without giving it a key. If so, the menu system will try to "guess" the key. This option allows to
change the minimum length of any key for security reasons.
To set one of them just do so in your menu class(es):
@ -1062,4 +1225,9 @@ class RoomBuildingMenu(BuildingMenu):
## Conclusion
Building menus mean to save you time and create a rich yet simple interface. But they can be complicated to learn and require reading the source code to find out how to do such and such a thing. This documentation, however long, is an attempt at describing this system, but chances are you'll still have questions about it after reading it, especially if you try to push this system to a great extent. Do not hesitate to read the documentation of this contrib, it's meant to be exhaustive but user-friendly.
Building menus mean to save you time and create a rich yet simple interface. But they can be
complicated to learn and require reading the source code to find out how to do such and such a
thing. This documentation, however long, is an attempt at describing this system, but chances are
you'll still have questions about it after reading it, especially if you try to push this system to
a great extent. Do not hesitate to read the documentation of this contrib, it's meant to be
exhaustive but user-friendly.

View file

@ -7,24 +7,49 @@ This page gives an overview of the supported SQL databases as well as instructio
- PostgreSQL
- MySQL / MariaDB
Since Evennia uses [Django](http://djangoproject.com), most of our notes are based off of what we know from the community and their documentation. While the information below may be useful, you can always find the most up-to-date and "correct" information at Django's [Notes about supported Databases](http://docs.djangoproject.com/en/dev/ref/databases/#ref-databases) page.
Since Evennia uses [Django](http://djangoproject.com), most of our notes are based off of what we
know from the community and their documentation. While the information below may be useful, you can
always find the most up-to-date and "correct" information at Django's [Notes about supported
Databases](http://docs.djangoproject.com/en/dev/ref/databases/#ref-databases) page.
## SQLite3
[SQLite3](https://sqlite.org/) is a light weight single-file database. It is our default database and Evennia will set this up for you automatically if you give no other options. SQLite stores the database in a single file (`mygame/server/evennia.db3`). This means it's very easy to reset this database - just delete (or move) that `evennia.db3` file and run `evennia migrate` again! No server process is needed and the administrative overhead and resource consumption is tiny. It is also very fast since it's run in-memory. For the vast majority of Evennia installs it will probably be all that's ever needed.
[SQLite3](https://sqlite.org/) is a light weight single-file database. It is our default database
and Evennia will set this up for you automatically if you give no other options. SQLite stores the
database in a single file (`mygame/server/evennia.db3`). This means it's very easy to reset this
database - just delete (or move) that `evennia.db3` file and run `evennia migrate` again! No server
process is needed and the administrative overhead and resource consumption is tiny. It is also very
fast since it's run in-memory. For the vast majority of Evennia installs it will probably be all
that's ever needed.
SQLite will generally be much faster than MySQL/PostgreSQL but its performance comes with two drawbacks:
SQLite will generally be much faster than MySQL/PostgreSQL but its performance comes with two
drawbacks:
* SQLite [ignores length constraints by design](https://www.sqlite.org/faq.html#q9); it is possible to store very large strings and numbers in fields that technically should not accept them. This is not something you will notice; your game will read and write them and function normally, but this *can* create some data migration problems requiring careful thought if you do need to change databases later.
* SQLite can scale well to storage of millions of objects, but if you end up with a thundering herd of users trying to access your MUD and web site at the same time, or you find yourself writing long-running functions to update large numbers of objects on a live game, either will yield errors and interference. SQLite does not work reliably with multiple concurrent threads or processes accessing its records. This has to do with file-locking clashes of the database file. So for a production server making heavy use of process- or thread pools (or when using a third-party webserver like Apache), a proper database is a more appropriate choice.
* SQLite [ignores length constraints by design](https://www.sqlite.org/faq.html#q9); it is possible
to store very large strings and numbers in fields that technically should not accept them. This is
not something you will notice; your game will read and write them and function normally, but this
*can* create some data migration problems requiring careful thought if you do need to change
databases later.
* SQLite can scale well to storage of millions of objects, but if you end up with a thundering herd
of users trying to access your MUD and web site at the same time, or you find yourself writing long-
running functions to update large numbers of objects on a live game, either will yield errors and
interference. SQLite does not work reliably with multiple concurrent threads or processes accessing
its records. This has to do with file-locking clashes of the database file. So for a production
server making heavy use of process- or thread pools (or when using a third-party webserver like
Apache), a proper database is a more appropriate choice.
### Install of SQlite3
This is installed and configured as part of Evennia. The database file is created as `mygame/server/evennia.db3` when you run
This is installed and configured as part of Evennia. The database file is created as
`mygame/server/evennia.db3` when you run
evennia migrate
without changing any database options. An optional requirement is the `sqlite3` client program - this is required if you want to inspect the database data manually. A shortcut for using it with the evennia database is `evennia dbshell`. Linux users should look for the `sqlite3` package for their distro while Mac/Windows should get the [sqlite-tools package from this page](https://sqlite.org/download.html).
without changing any database options. An optional requirement is the `sqlite3` client program -
this is required if you want to inspect the database data manually. A shortcut for using it with the
evennia database is `evennia dbshell`. Linux users should look for the `sqlite3` package for their
distro while Mac/Windows should get the [sqlite-tools package from this
page](https://sqlite.org/download.html).
To inspect the default Evennia database (once it's been created), go to your game dir and do
@ -34,25 +59,34 @@ To inspect the default Evennia database (once it's been created), go to your gam
evennia dbshell
```
This will bring you into the sqlite command line. Use `.help` for instructions and `.quit` to exit. See [here](https://gist.github.com/vincent178/10889334) for a cheat-sheet of commands.
This will bring you into the sqlite command line. Use `.help` for instructions and `.quit` to exit.
See [here](https://gist.github.com/vincent178/10889334) for a cheat-sheet of commands.
## PostgreSQL
[PostgreSQL](https://www.postgresql.org/) is an open-source database engine, recommended by Django. While not as fast as SQLite for normal usage, it will scale better than SQLite, especially if your game has an very large database and/or extensive web presence through a separate server process.
[PostgreSQL](https://www.postgresql.org/) is an open-source database engine, recommended by Django.
While not as fast as SQLite for normal usage, it will scale better than SQLite, especially if your
game has an very large database and/or extensive web presence through a separate server process.
### Install and initial setup of PostgreSQL
First, install the posgresql server. Version `9.6` is tested with Evennia. Packages are readily available for all distributions. You need to also get the `psql` client (this is called `postgresql-client` on debian-derived systems). Windows/Mac users can [find what they need on the postgresql download page](https://www.postgresql.org/download/). You should be setting up a password for your database-superuser (always called `postgres`) when you install.
First, install the posgresql server. Version `9.6` is tested with Evennia. Packages are readily
available for all distributions. You need to also get the `psql` client (this is called `postgresql-
client` on debian-derived systems). Windows/Mac users can [find what they need on the postgresql
download page](https://www.postgresql.org/download/). You should be setting up a password for your
database-superuser (always called `postgres`) when you install.
For interaction with Evennia you need to also install `psycopg2` to your Evennia install (`pip install psycopg2-binary` in your virtualenv). This acts as the python bridge to the database server.
For interaction with Evennia you need to also install `psycopg2` to your Evennia install (`pip
install psycopg2-binary` in your virtualenv). This acts as the python bridge to the database server.
Next, start the postgres client:
```bash
psql -U postgres --password
```
> :warning: **Warning:** With the `--password` argument, Postgres should prompt you for a password.
If it won't, replace that with `-p yourpassword` instead. Do not use the `-p` argument unless you have to since the resulting command, and your password, will be logged in the shell history.
> :warning: **Warning:** With the `--password` argument, Postgres should prompt you for a password.
If it won't, replace that with `-p yourpassword` instead. Do not use the `-p` argument unless you
have to since the resulting command, and your password, will be logged in the shell history.
This will open a console to the postgres service using the psql client.
@ -76,8 +110,12 @@ GRANT ALL PRIVILEGES ON DATABASE evennia TO evennia;
```
[Here](https://gist.github.com/Kartones/dd3ff5ec5ea238d4c546) is a cheat-sheet for psql commands.
We create a database user 'evennia' and a new database named `evennia` (you can call them whatever you want though). We then grant the 'evennia' user full privileges to the new database so it can read/write etc to it.
If you in the future wanted to completely wipe the database, an easy way to do is to log in as the `postgres` superuser again, then do `DROP DATABASE evennia;`, then `CREATE` and `GRANT` steps above again to recreate the database and grant privileges.
We create a database user 'evennia' and a new database named `evennia` (you can call them whatever
you want though). We then grant the 'evennia' user full privileges to the new database so it can
read/write etc to it.
If you in the future wanted to completely wipe the database, an easy way to do is to log in as the
`postgres` superuser again, then do `DROP DATABASE evennia;`, then `CREATE` and `GRANT` steps above
again to recreate the database and grant privileges.
### Evennia PostgreSQL configuration
@ -102,23 +140,34 @@ If you used some other name for the database and user, enter those instead. Run
evennia migrate
to populate your database. Should you ever want to inspect the database directly you can from now on also use
to populate your database. Should you ever want to inspect the database directly you can from now on
also use
evennia dbshell
as a shortcut to get into the postgres command line for the right database and user.
With the database setup you should now be able to start start Evennia normally with your new database.
With the database setup you should now be able to start start Evennia normally with your new
database.
## MySQL / MariaDB
[MySQL](https://www.mysql.com/) is a commonly used proprietary database system, on par with PostgreSQL. There is an open-source alternative called [MariaDB](https://mariadb.org/) that mimics all functionality and command syntax of the former. So this section covers both.
[MySQL](https://www.mysql.com/) is a commonly used proprietary database system, on par with
PostgreSQL. There is an open-source alternative called [MariaDB](https://mariadb.org/) that mimics
all functionality and command syntax of the former. So this section covers both.
### Installing and initial setup of MySQL/MariaDB
First, install and setup MariaDB or MySQL for your specific server. Linux users should look for the `mysql-server` or `mariadb-server` packages for their respective distributions. Windows/Mac users will find what they need from the [MySQL downloads](https://www.mysql.com/downloads/) or [MariaDB downloads](https://mariadb.org/download/) pages. You also need the respective database clients (`mysql`, `mariadb-client`), so you can setup the database itself. When you install the server you should usually be asked to set up the database root user and password.
First, install and setup MariaDB or MySQL for your specific server. Linux users should look for the
`mysql-server` or `mariadb-server` packages for their respective distributions. Windows/Mac users
will find what they need from the [MySQL downloads](https://www.mysql.com/downloads/) or [MariaDB
downloads](https://mariadb.org/download/) pages. You also need the respective database clients
(`mysql`, `mariadb-client`), so you can setup the database itself. When you install the server you
should usually be asked to set up the database root user and password.
You will finally also need a Python interface to allow Evennia to talk to the database. Django recommends the `mysqlclient` one. Install this into the evennia virtualenv with `pip install mysqlclient`.
You will finally also need a Python interface to allow Evennia to talk to the database. Django
recommends the `mysqlclient` one. Install this into the evennia virtualenv with `pip install
mysqlclient`.
Start the database client (this is named the same for both mysql and mariadb):
@ -126,29 +175,43 @@ Start the database client (this is named the same for both mysql and mariadb):
mysql -u root -p
```
You should get to enter your database root password (set this up when you installed the database server).
You should get to enter your database root password (set this up when you installed the database
server).
Inside the database client interface:
```sql
CREATE USER 'evennia'@'localhost' IDENTIFIED BY 'somepassword';
CREATE DATABASE evennia;
ALTER DATABASE `evennia` CHARACTER SET utf8; -- note that it's `evennia` with back-ticks, not quotes!
ALTER DATABASE `evennia` CHARACTER SET utf8; -- note that it's `evennia` with back-ticks, not
quotes!
GRANT ALL PRIVILEGES ON evennia.* TO 'evennia'@'localhost';
FLUSH PRIVILEGES;
-- use 'exit' to quit client
```
[Here](https://gist.github.com/hofmannsven/9164408) is a mysql command cheat sheet.
Above we created a new local user and database (we called both 'evennia' here, you can name them what you prefer). We set the character set to `utf8` to avoid an issue with prefix character length that can pop up on some installs otherwise. Next we grant the 'evennia' user all privileges on the `evennia` database and make sure the privileges are applied. Exiting the client brings us back to the normal terminal/console.
Above we created a new local user and database (we called both 'evennia' here, you can name them
what you prefer). We set the character set to `utf8` to avoid an issue with prefix character length
that can pop up on some installs otherwise. Next we grant the 'evennia' user all privileges on the
`evennia` database and make sure the privileges are applied. Exiting the client brings us back to
the normal terminal/console.
> Note: If you are not using MySQL for anything else you might consider granting the 'evennia' user full privileges with `GRANT ALL PRIVILEGES ON *.* TO 'evennia'@'localhost';`. If you do, it means you can use `evennia dbshell` later to connect to mysql, drop your database and re-create it as a way of easy reset. Without this extra privilege you will be able to drop the database but not re-create it without first switching to the database-root user.
> Note: If you are not using MySQL for anything else you might consider granting the 'evennia' user
full privileges with `GRANT ALL PRIVILEGES ON *.* TO 'evennia'@'localhost';`. If you do, it means
you can use `evennia dbshell` later to connect to mysql, drop your database and re-create it as a
way of easy reset. Without this extra privilege you will be able to drop the database but not re-
create it without first switching to the database-root user.
## Add MySQL configuration to Evennia
To tell Evennia to use your new database you need to edit `mygame/server/conf/settings.py` (or `secret_settings.py` if you don't want your db info passed around on git repositories).
To tell Evennia to use your new database you need to edit `mygame/server/conf/settings.py` (or
`secret_settings.py` if you don't want your db info passed around on git repositories).
> Note: The Django documentation suggests using an external `db.cnf` or other external conf-formatted file. Evennia users have however found that this leads to problems (see e.g. [issue #1184](https://git.io/vQdiN)). To avoid trouble we recommend you simply put the configuration in your settings as below.
> Note: The Django documentation suggests using an external `db.cnf` or other external conf-
formatted file. Evennia users have however found that this leads to problems (see e.g. [issue
#1184](https://git.io/vQdiN)). To avoid trouble we recommend you simply put the configuration in
your settings as below.
```python
#
@ -169,14 +232,18 @@ Change this to fit your database setup. Next, run:
evennia migrate
to populate your database. Should you ever want to inspect the database directly you can from now on also use
to populate your database. Should you ever want to inspect the database directly you can from now on
also use
evennia dbshell
as a shortcut to get into the postgres command line for the right database and user.
With the database setup you should now be able to start start Evennia normally with your new database.
With the database setup you should now be able to start start Evennia normally with your new
database.
## Others
No testing has been performed with Oracle, but it is also supported through Django. There are community maintained drivers for [MS SQL](http://code.google.com/p/django-mssql/) and possibly a few others. If you try other databases out, consider expanding this page with instructions.
No testing has been performed with Oracle, but it is also supported through Django. There are
community maintained drivers for [MS SQL](http://code.google.com/p/django-mssql/) and possibly a few
others. If you try other databases out, consider expanding this page with instructions.

View file

@ -1,12 +1,15 @@
# Client Support Grid
This grid tries to gather Evennia-specific knowledge about the various clients and protocols used. Everyone's welcome to report their findings.
This grid tries to gather Evennia-specific knowledge about the various clients and protocols used.
Everyone's welcome to report their findings.
##### Legend:
- **Name**: The name of the client. If it's only available for a specific OS, it should be noted here too.
- **Name**: The name of the client. If it's only available for a specific OS, it should be noted
here too.
- **Version**: Which version or range of client versions were tested.
- **Comments**: Any comments or quirks on using this client with Evennia should be added here. Also note if some other protocol than Telnet is used (like Websockets, SSH etc).
- **Comments**: Any comments or quirks on using this client with Evennia should be added here. Also
note if some other protocol than Telnet is used (like Websockets, SSH etc).
## Client Grid
@ -18,20 +21,27 @@ Name | Version | Comments
[MUSHclient][5] (Win) | 4.94 | NAWS reports full text area
[Zmud][6] (Win) | 7.21 | *UNTESTED*
[Cmud][7] (Win) | v3 | *UNTESTED*
[Potato][8] | 2.0.0b16 | No MXP, MCCP support. Win 32bit does not understand "localhost", must use `127.0.0.1`. [Newline issue](https://github.com/evennia/evennia/issues/1131). *Won't send a single blank line on Enter press.
[Potato][8] | 2.0.0b16 | No MXP, MCCP support. Win 32bit does not understand
"localhost", must use `127.0.0.1`. [Newline issue](https://github.com/evennia/evennia/issues/1131).
*Won't send a single blank line on Enter press.
[Mudlet][9] | 3.4+ | No known issues. Some older versions showed <> as html under MXP.
[SimpleMU][10] (Win) | full | *UNTESTED*. Discontinued. NAWS reports pixel size.
[Atlantis][11] (Mac) | 0.9.9.4 | No known issues.
[GMUD][12] | 0.0.1 | Can't handle any telnet handshakes. Not recommended.
[BeipMU][13] (Win) | 3.0.255 | No MXP support. Best to enable "MUD prompt handling", disable "Handle HTML tags".
[BeipMU][13] (Win) | 3.0.255 | No MXP support. Best to enable "MUD prompt handling", disable
"Handle HTML tags".
[MudRammer][14] (IOS) | 1.8.7 | Bad Telnet Protocol compliance: displays spurious characters.
[MUDMaster][15] (IOS) | 1.3.1 | *UNTESTED*
[BlowTorch][16] (Andr) | 1.1.3 | *Telnet NOP displays as spurious character.
[Mukluk][17] (Andr) | 2015.11.20| *Telnet NOP displays as spurious character. Has UTF-8/Emoji support.
[Gnome-MUD][18] (Unix) | 0.11.2 | Telnet handshake errors. First (only) attempt at logging in fails.
[Mukluk][17] (Andr) | 2015.11.20| *Telnet NOP displays as spurious character. Has UTF-8/Emoji
support.
[Gnome-MUD][18] (Unix) | 0.11.2 | Telnet handshake errors. First (only) attempt at logging in
fails.
[Spyrit][19] | 0.4 | No MXP, OOB support.
[JamochaMUD][20] | 5.2 | Does not support ANSI within MXP text.
[DuckClient][21] (Chrome)| 4.2 | No MXP support. Displays Telnet Go-Ahead and WILL SUPPRESS-GO-AHEAD as ù character. Also seems to run the `version` command on connection, which will not work in `MULTISESSION_MODES` above 1.
[DuckClient][21] (Chrome)| 4.2 | No MXP support. Displays Telnet Go-Ahead and WILL SUPPRESS-GO-AHEAD
as ù character. Also seems to run the `version` command on connection, which will not work in
`MULTISESSION_MODES` above 1.
[KildClient][22] | 2.11.1 | No known issues.
[1]: https://github.com/evennia/evennia/wiki/Web%20features#web-client
@ -68,7 +78,8 @@ Known clients:
Workaround:
* Set the command in game to `@option NOPKEEPALIVE=off` for the session, or use the `/save` parameter to disable it for that Evennian account permanently.
* Set the command in game to `@option NOPKEEPALIVE=off` for the session, or use the `/save`
parameter to disable it for that Evennian account permanently.
* Client-side: Set a gag-type trigger on the NOP character to make it invisible to the client.
@ -80,4 +91,4 @@ Known clients:
Workaround:
* Press Control Enter, then Enter key again to send blank line.
* Press Control Enter, then Enter key again to send blank line.

View file

@ -1,31 +1,43 @@
# Coding FAQ
*This FAQ page is for users to share their solutions to coding problems. Keep it brief and link to the docs if you can rather than too lengthy explanations. Don't forget to check if an answer already exists before answering - maybe you can clarify that answer rather than to make a new Q&A section.*
*This FAQ page is for users to share their solutions to coding problems. Keep it brief and link to
the docs if you can rather than too lengthy explanations. Don't forget to check if an answer already
exists before answering - maybe you can clarify that answer rather than to make a new Q&A section.*
## Table of Contents
- [Removing default commands](Coding-FAQ#removing-default-commands)
- [Preventing character from moving based on a condition](Coding-FAQ#preventing-character-from-moving-based-on-a-condition)
- [Reference initiating object in an EvMenu command](Coding-FAQ#reference-initiating-object-in-an-evmenu-command)
- [Preventing character from moving based on a condition](Coding-FAQ#preventing-character-from-
moving-based-on-a-condition)
- [Reference initiating object in an EvMenu command](Coding-FAQ#reference-initiating-object-in-an-
evmenu-command)
- [Adding color to default Evennia Channels](Coding-FAQ#adding-color-to-default-evennia-channels)
- [Selectively turn off commands in a room](Coding-FAQ#selectively-turn-off-commands-in-a-room)
- [Select Command based on a condition](Coding-FAQ#select-command-based-on-a-condition)
- [Automatically updating code when reloading](Coding-FAQ#automatically-updating-code-when-reloading)
- [Automatically updating code when reloading](Coding-FAQ#automatically-updating-code-when-
reloading)
- [Changing all exit messages](Coding-FAQ#changing-all-exit-messages)
- [Add parsing with the "to" delimiter](Coding-FAQ#add-parsing-with-the-to-delimiter)
- [Store last used session IP address](Coding-FAQ#store-last-used-session-ip-address)
- [Use wide characters with EvTable](Coding-FAQ#non-latin-characters-in-evtable)
## Removing default commands
**Q:** How does one *remove* (not replace) e.g. the default `get` [Command](Commands) from the Character [Command Set](Command-Sets)?
**Q:** How does one *remove* (not replace) e.g. the default `get` [Command](Commands) from the
Character [Command Set](Command-Sets)?
**A:** Go to `mygame/commands/default_cmdsets.py`. Find the `CharacterCmdSet` class. It has one method named `at_cmdset_creation`. At the end of that method, add the following line: `self.remove(default_cmds.CmdGet())`. See the [Adding Commands Tutorial](Adding-Command-Tutorial) for more info.
**A:** Go to `mygame/commands/default_cmdsets.py`. Find the `CharacterCmdSet` class. It has one
method named `at_cmdset_creation`. At the end of that method, add the following line:
`self.remove(default_cmds.CmdGet())`. See the [Adding Commands Tutorial](Adding-Command-Tutorial)
for more info.
## Preventing character from moving based on a condition
**Q:** How does one keep a character from using any exit, if they meet a certain condition? (I.E. in combat, immobilized, etc.)
**Q:** How does one keep a character from using any exit, if they meet a certain condition? (I.E. in
combat, immobilized, etc.)
**A:** The `at_before_move` hook is called by Evennia just before performing any move. If it returns `False`, the move is aborted. Let's say we want to check for an [Attribute](Attributes) `cantmove`. Add the following code to the `Character` class:
**A:** The `at_before_move` hook is called by Evennia just before performing any move. If it returns
`False`, the move is aborted. Let's say we want to check for an [Attribute](Attributes) `cantmove`.
Add the following code to the `Character` class:
```python
def at_before_move(self, destination):
@ -37,9 +49,13 @@ def at_before_move(self, destination):
```
## Reference initiating object in an EvMenu command.
**Q:** An object has a Command on it starts up an EvMenu instance. How do I capture a reference to that object for use in the menu?
**Q:** An object has a Command on it starts up an EvMenu instance. How do I capture a reference to
that object for use in the menu?
**A:** When an [EvMenu](EvMenu) is started, the menu object is stored as `caller.ndb._menutree`. This is a good place to store menu-specific things since it will clean itself up when the menu closes. When initiating the menu, any additional keywords you give will be available for you as properties on this menu object:
**A:** When an [EvMenu](EvMenu) is started, the menu object is stored as `caller.ndb._menutree`.
This is a good place to store menu-specific things since it will clean itself up when the menu
closes. When initiating the menu, any additional keywords you give will be available for you as
properties on this menu object:
```python
class MyObjectCommand(Command):
@ -57,7 +73,8 @@ Inside the menu you can now access the object through `caller.ndb._menutree.stor
## Adding color to default Evennia Channels
**Q:** How do I add colors to the names of Evennia channels?
**A:** The Channel typeclass' `channel_prefix` method decides what is shown at the beginning of a channel send. Edit `mygame/typeclasses/channels.py` (and then `@reload`):
**A:** The Channel typeclass' `channel_prefix` method decides what is shown at the beginning of a
channel send. Edit `mygame/typeclasses/channels.py` (and then `@reload`):
```python
# define our custom color names
@ -75,13 +92,18 @@ CHANNEL_COLORS = {'public': '|015Public|n',
prefix_string = "[%s] " % self.key.capitalize()
return prefix_string
```
Additional hint: To make colors easier to change from one place you could instead put the `CHANNEL_COLORS` dict in your settings file and import it as `from django.conf.settings import CHANNEL_COLORS`.
Additional hint: To make colors easier to change from one place you could instead put the
`CHANNEL_COLORS` dict in your settings file and import it as `from django.conf.settings import
CHANNEL_COLORS`.
## Selectively turn off commands in a room
**Q:** I want certain commands to turn off in a given room. They should still work normally for staff.
**Q:** I want certain commands to turn off in a given room. They should still work normally for
staff.
**A:** This is done using a custom cmdset on a room [locked with the 'call' lock type](Locks). Only if this lock is passed will the commands on the room be made available to an object inside it. Here is an example of a room where certain commands are disabled for non-staff:
**A:** This is done using a custom cmdset on a room [locked with the 'call' lock type](Locks). Only
if this lock is passed will the commands on the room be made available to an object inside it. Here
is an example of a room where certain commands are disabled for non-staff:
```python
# in mygame/typeclasses/rooms.py
@ -109,12 +131,19 @@ class BlockingRoom(Room):
# are NOT Builders or higher
self.locks.add("call:not perm(Builders)")
```
After `@reload`, make some `BlockingRooms` (or switch a room to it with `@typeclass`). Entering one will now replace the given commands for anyone that does not have the `Builders` or higher permission. Note that the 'call' lock is special in that even the superuser will be affected by it (otherwise superusers would always see other player's cmdsets and a game would be unplayable for superusers).
After `@reload`, make some `BlockingRooms` (or switch a room to it with `@typeclass`). Entering one
will now replace the given commands for anyone that does not have the `Builders` or higher
permission. Note that the 'call' lock is special in that even the superuser will be affected by it
(otherwise superusers would always see other player's cmdsets and a game would be unplayable for
superusers).
## Select Command based on a condition
**Q:** I want a command to be available only based on a condition. For example I want the "werewolf" command to only be available on a full moon, from midnight to three in-game time.
**Q:** I want a command to be available only based on a condition. For example I want the "werewolf"
command to only be available on a full moon, from midnight to three in-game time.
**A:** This is easiest accomplished by putting the "werewolf" command on the Character as normal, but to [lock](Locks) it with the "cmd" type lock. Only if the "cmd" lock type is passed will the command be available.
**A:** This is easiest accomplished by putting the "werewolf" command on the Character as normal,
but to [lock](Locks) it with the "cmd" type lock. Only if the "cmd" lock type is passed will the
command be available.
```python
# in mygame/commands/command.py
@ -128,7 +157,8 @@ class CmdWerewolf(Command):
def func(self):
# ...
```
Add this to the [default cmdset as usual](Adding-Command-Tutorial). The `is_full_moon` [lock function](Locks#lock-functions) does not yet exist. We must create that:
Add this to the [default cmdset as usual](Adding-Command-Tutorial). The `is_full_moon` [lock
function](Locks#lock-functions) does not yet exist. We must create that:
```python
# in mygame/server/conf/lockfuncs.py
@ -140,12 +170,17 @@ def is_full_moon(accessing_obj, accessed_obj,
# return True or False
```
After a `@reload`, the `werewolf` command will be available only at the right time, that is when the `is_full_moon` lock function returns True.
After a `@reload`, the `werewolf` command will be available only at the right time, that is when the
`is_full_moon` lock function returns True.
## Automatically updating code when reloading
**Q:** I have a development server running Evennia. Can I have the server update its code-base when I reload?
**Q:** I have a development server running Evennia. Can I have the server update its code-base when
I reload?
**A:** Having a development server that pulls updated code whenever you reload it can be really useful if you have limited shell access to your server, or want to have it done automatically. If you have your project in a configured Git environment, it's a matter of automatically calling `git pull` when you reload. And that's pretty straightforward:
**A:** Having a development server that pulls updated code whenever you reload it can be really
useful if you have limited shell access to your server, or want to have it done automatically. If
you have your project in a configured Git environment, it's a matter of automatically calling `git
pull` when you reload. And that's pretty straightforward:
In `/server/conf/at_server_startstop.py`:
@ -162,28 +197,45 @@ def at_server_reload_stop():
process = subprocess.call(["git", "pull"], shell=False)
```
That's all. We call `subprocess` to execute a shell command (that code works on Windows and Linux, assuming the current directory is your game directory, which is probably the case when you run Evennia). `call` waits for the process to complete, because otherwise, Evennia would reload on partially-modified code, which would be problematic.
That's all. We call `subprocess` to execute a shell command (that code works on Windows and Linux,
assuming the current directory is your game directory, which is probably the case when you run
Evennia). `call` waits for the process to complete, because otherwise, Evennia would reload on
partially-modified code, which would be problematic.
Now, when you enter `@reload` on your development server, the game repository is updated from the configured remote repository (Github, for instance). Your development cycle could resemble something like:
Now, when you enter `@reload` on your development server, the game repository is updated from the
configured remote repository (Github, for instance). Your development cycle could resemble
something like:
1. Coding on the local machine.
2. Testing modifications.
3. Committing once, twice or more (being sure the code is still working, unittests are pretty useful here).
3. Committing once, twice or more (being sure the code is still working, unittests are pretty useful
here).
4. When the time comes, login to the development server and run `@reload`.
The reloading might take one or two additional seconds, since Evennia will pull from your remote Git repository. But it will reload on it and you will have your modifications ready, without needing connecting to your server using SSH or something similar.
The reloading might take one or two additional seconds, since Evennia will pull from your remote Git
repository. But it will reload on it and you will have your modifications ready, without needing
connecting to your server using SSH or something similar.
## Changing all exit messages
**Q:** How can I change the default exit messages to something like "XXX leaves east" or "XXX arrives from the west"?
**Q:** How can I change the default exit messages to something like "XXX leaves east" or "XXX
arrives from the west"?
**A:** the default exit messages are stored in two hooks, namely `announce_move_from` and `announce_move_to`, on the `Character` typeclass (if what you want to change is the message other characters will see when a character exits).
**A:** the default exit messages are stored in two hooks, namely `announce_move_from` and
`announce_move_to`, on the `Character` typeclass (if what you want to change is the message other
characters will see when a character exits).
These two hooks provide some useful features to easily update the message to be displayed. They take both the default message and mapping as argument. You can easily call the parent hook with these information:
These two hooks provide some useful features to easily update the message to be displayed. They
take both the default message and mapping as argument. You can easily call the parent hook with
these information:
* The message represents the string of characters sent to characters in the room when a character leaves.
* The mapping is a dictionary containing additional mappings (you will probably not need it for simple customization).
* The message represents the string of characters sent to characters in the room when a character
leaves.
* The mapping is a dictionary containing additional mappings (you will probably not need it for
simple customization).
It is advisable to look in the [code of both hooks](https://github.com/evennia/evennia/tree/master/evennia/objects/objects.py), and read the hooks' documentation. The explanations on how to quickly update the message are shown below:
It is advisable to look in the [code of both
hooks](https://github.com/evennia/evennia/tree/master/evennia/objects/objects.py), and read the
hooks' documentation. The explanations on how to quickly update the message are shown below:
```python
# In typeclasses/characters.py
@ -244,13 +296,18 @@ class Character(DefaultCharacter):
super().announce_move_to(source_location, msg="{object} arrives from the {exit}.")
```
We override both hooks, but call the parent hook to display a different message. If you read the provided docstrings, you will better understand why and how we use mappings (information between braces). You can provide additional mappings as well, if you want to set a verb to move, for instance, or other, extra information.
We override both hooks, but call the parent hook to display a different message. If you read the
provided docstrings, you will better understand why and how we use mappings (information between
braces). You can provide additional mappings as well, if you want to set a verb to move, for
instance, or other, extra information.
## Add parsing with the "to" delimiter
**Q:** How do I change commands to undestand say `give obj to target` as well as the default `give obj = target`?
**Q:** How do I change commands to undestand say `give obj to target` as well as the default `give
obj = target`?
**A:** You can make change the default `MuxCommand` parent with your own class making a small change in its `parse` method:
**A:** You can make change the default `MuxCommand` parent with your own class making a small change
in its `parse` method:
```python
# in mygame/commands/command.py
@ -273,7 +330,8 @@ MuxCommand class is also found commented-out in the `mygame/commands/command.py`
## Store last used session IP address
**Q:** If a user has already logged out of an Evennia account, their IP is no longer visible to staff that wants to ban-by-ip (instead of the user) with `@ban/ip`?
**Q:** If a user has already logged out of an Evennia account, their IP is no longer visible to
staff that wants to ban-by-ip (instead of the user) with `@ban/ip`?
**A:** One approach is to write the IP from the last session onto the "account" account object.
@ -283,7 +341,10 @@ MuxCommand class is also found commented-out in the `mygame/commands/command.py`
super().at_post_login(session=session, **kwargs)
self.db.lastsite = self.sessions.all()[-1].address
```
Adding timestamp for login time and appending to a list to keep the last N login IP addresses and timestamps is possible, also. Additionally, if you don't want the list to grow beyond a `do_not_exceed` length, conditionally pop a value after you've added it, if the length has grown too long.
Adding timestamp for login time and appending to a list to keep the last N login IP addresses and
timestamps is possible, also. Additionally, if you don't want the list to grow beyond a
`do_not_exceed` length, conditionally pop a value after you've added it, if the length has grown too
long.
**NOTE:** You'll need to add `import time` to generate the login timestamp.
```python
@ -297,7 +358,8 @@ Adding timestamp for login time and appending to a list to keep the last N login
if len(self.db.lastsite) > do_not_exceed:
self.db.lastsite.pop()
```
This only stores the data. You may want to interface the `@ban` command or make a menu-driven viewer for staff to browse the list and display how long ago the login occurred.
This only stores the data. You may want to interface the `@ban` command or make a menu-driven viewer
for staff to browse the list and display how long ago the login occurred.
## Non-latin characters in EvTable
@ -309,4 +371,8 @@ This only stores the data. You may want to interface the `@ban` command or make
| | |
+~~~~~~+~~~~~~+
```
**A:** The reason for this is because certain non-latin characters are *visually* much wider than their len() suggests. There is little Evennia can (reliably) do about this. If you are using such characters, you need to make sure to use a suitable mono-spaced font where are width are equal. You can set this in your web client and need to recommend it for telnet-client users. See [this discussion](https://github.com/evennia/evennia/issues/1522) where some suitable fonts are suggested.
**A:** The reason for this is because certain non-latin characters are *visually* much wider than
their len() suggests. There is little Evennia can (reliably) do about this. If you are using such
characters, you need to make sure to use a suitable mono-spaced font where are width are equal. You
can set this in your web client and need to recommend it for telnet-client users. See [this
discussion](https://github.com/evennia/evennia/issues/1522) where some suitable fonts are suggested.

View file

@ -1,17 +1,23 @@
# Coding Introduction
Evennia allows for a lot of freedom when designing your game - but to code efficiently you still need to adopt some best practices as well as find a good place to start to learn.
Evennia allows for a lot of freedom when designing your game - but to code efficiently you still
need to adopt some best practices as well as find a good place to start to learn.
Here are some pointers to get you going.
### Python
Evennia is developed using Python. Even if you are more of a designer than a coder, it is wise to learn how to read and understand basic Python code. If you are new to Python, or need a refresher, take a look at our two-part [Python introduction](Python-basic-introduction).
Evennia is developed using Python. Even if you are more of a designer than a coder, it is wise to
learn how to read and understand basic Python code. If you are new to Python, or need a refresher,
take a look at our two-part [Python introduction](Python-basic-introduction).
### Explore Evennia interactively
When new to Evennia it can be hard to find things or figure out what is available. Evennia offers a special interactive python shell that allows you to experiment and try out things. It's recommended to use [ipython](http://ipython.org/) for this since the vanilla python prompt is very limited. Here are some simple commands to get started:
When new to Evennia it can be hard to find things or figure out what is available. Evennia offers a
special interactive python shell that allows you to experiment and try out things. It's recommended
to use [ipython](http://ipython.org/) for this since the vanilla python prompt is very limited. Here
are some simple commands to get started:
# [open a new console/terminal]
# [activate your evennia virtualenv in this console/terminal]
@ -24,39 +30,75 @@ This will open an Evennia-aware python shell (using ipython). From within this s
import evennia
evennia.<TAB>
That is, enter `evennia.` and press the `<TAB>` key. This will show you all the resources made available at the top level of Evennia's "flat API". See the [flat API](Evennia-API) page for more info on how to explore it efficiently.
That is, enter `evennia.` and press the `<TAB>` key. This will show you all the resources made
available at the top level of Evennia's "flat API". See the [flat API](Evennia-API) page for more
info on how to explore it efficiently.
You can complement your exploration by peeking at the sections of the much more detailed [Developer Central](Developer-Central). The [Tutorials](Tutorials) section also contains a growing collection of system- or implementation-specific help.
You can complement your exploration by peeking at the sections of the much more detailed [Developer
Central](Developer-Central). The [Tutorials](Tutorials) section also contains a growing collection
of system- or implementation-specific help.
### Use a python syntax checker
Evennia works by importing your own modules and running them as part of the server. Whereas Evennia should just gracefully tell you what errors it finds, it can nevertheless be a good idea for you to check your code for simple syntax errors *before* you load it into the running server. There are many python syntax checkers out there. A fast and easy one is [pyflakes](https://pypi.python.org/pypi/pyflakes), a more verbose one is [pylint](http://www.pylint.org/). You can also check so that your code looks up to snuff using [pep8](https://pypi.python.org/pypi/pep8). Even with a syntax checker you will not be able to catch every possible problem - some bugs or problems will only appear when you actually run the code. But using such a checker can be a good start to weed out the simple problems.
Evennia works by importing your own modules and running them as part of the server. Whereas Evennia
should just gracefully tell you what errors it finds, it can nevertheless be a good idea for you to
check your code for simple syntax errors *before* you load it into the running server. There are
many python syntax checkers out there. A fast and easy one is
[pyflakes](https://pypi.python.org/pypi/pyflakes), a more verbose one is
[pylint](http://www.pylint.org/). You can also check so that your code looks up to snuff using
[pep8](https://pypi.python.org/pypi/pep8). Even with a syntax checker you will not be able to catch
every possible problem - some bugs or problems will only appear when you actually run the code. But
using such a checker can be a good start to weed out the simple problems.
### Plan before you code
Before you start coding away at your dream game, take a look at our [Game Planning](Game-Planning) page. It might hopefully help you avoid some common pitfalls and time sinks.
Before you start coding away at your dream game, take a look at our [Game Planning](Game-Planning)
page. It might hopefully help you avoid some common pitfalls and time sinks.
### Code in your game folder, not in the evennia/ repository
As part of the Evennia setup you will create a game folder to host your game code. This is your home. You should *never* need to modify anything in the `evennia` library (anything you download from us, really). You import useful functionality from here and if you see code you like, copy&paste it out into your game folder and edit it there.
As part of the Evennia setup you will create a game folder to host your game code. This is your
home. You should *never* need to modify anything in the `evennia` library (anything you download
from us, really). You import useful functionality from here and if you see code you like, copy&paste
it out into your game folder and edit it there.
If you find that Evennia doesn't support some functionality you need, make a [Feature Request](feature-request) about it. Same goes for [bugs][bug]. If you add features or fix bugs yourself, please consider [Contributing](Contributing) your changes upstream!
If you find that Evennia doesn't support some functionality you need, make a [Feature
Request](feature-request) about it. Same goes for [bugs][bug]. If you add features or fix bugs
yourself, please consider [Contributing](Contributing) your changes upstream!
### Learn to read tracebacks
Python is very good at reporting when and where things go wrong. A *traceback* shows everything you need to know about crashing code. The text can be pretty long, but you usually are only interested in the last bit, where it says what the error is and at which module and line number it happened - armed with this info you can resolve most problems.
Python is very good at reporting when and where things go wrong. A *traceback* shows everything you
need to know about crashing code. The text can be pretty long, but you usually are only interested
in the last bit, where it says what the error is and at which module and line number it happened -
armed with this info you can resolve most problems.
Evennia will usually not show the full traceback in-game though. Instead the server outputs errors to the terminal/console from which you started Evennia in the first place. If you want more to show in-game you can add `IN_GAME_ERRORS = True` to your settings file. This will echo most (but not all) tracebacks both in-game as well as to the terminal/console. This is a potential security problem though, so don't keep this active when your game goes into production.
Evennia will usually not show the full traceback in-game though. Instead the server outputs errors
to the terminal/console from which you started Evennia in the first place. If you want more to show
in-game you can add `IN_GAME_ERRORS = True` to your settings file. This will echo most (but not all)
tracebacks both in-game as well as to the terminal/console. This is a potential security problem
though, so don't keep this active when your game goes into production.
> A common confusing error is finding that objects in-game are suddenly of the type `DefaultObject` rather than your custom typeclass. This happens when you introduce a critical Syntax error to the module holding your custom class. Since such a module is not valid Python, Evennia can't load it at all. Instead of crashing, Evennia will then print the full traceback to the terminal/console and temporarily fall back to the safe `DefaultObject` until you fix the problem and reload.
> A common confusing error is finding that objects in-game are suddenly of the type `DefaultObject`
rather than your custom typeclass. This happens when you introduce a critical Syntax error to the
module holding your custom class. Since such a module is not valid Python, Evennia can't load it at
all. Instead of crashing, Evennia will then print the full traceback to the terminal/console and
temporarily fall back to the safe `DefaultObject` until you fix the problem and reload.
### Docs are here to help you
Some people find reading documentation extremely dull and shun it out of principle. That's your call, but reading docs really *does* help you, promise! Evennia's documentation is pretty thorough and knowing what is possible can often give you a lot of new cool game ideas. That said, if you can't find the answer in the docs, don't be shy to ask questions! The [discussion group](https://sites.google.com/site/evenniaserver/discussions) and the [irc chat](http://webchat.freenode.net/?channels=evennia) are also there for you.
Some people find reading documentation extremely dull and shun it out of principle. That's your
call, but reading docs really *does* help you, promise! Evennia's documentation is pretty thorough
and knowing what is possible can often give you a lot of new cool game ideas. That said, if you
can't find the answer in the docs, don't be shy to ask questions! The [discussion
group](https://sites.google.com/site/evenniaserver/discussions) and the [irc
chat](http://webchat.freenode.net/?channels=evennia) are also there for you.
### The most important point
And finally, of course, have fun!
[feature-request]: (https://github.com/evennia/evennia/issues/new?title=Feature+Request%3a+%3Cdescriptive+title+here%3E&body=%23%23%23%23+Description+of+the+suggested+feature+and+how+it+is+supposed+to+work+for+the+admin%2fend+user%3a%0D%0A%0D%0A%0D%0A%23%23%23%23+A+list+of+arguments+for+why+you+think+this+new+feature+should+be+included+in+Evennia%3a%0D%0A%0D%0A1.%0D%0A2.%0D%0A%0D%0A%23%23%23%23+Extra+information%2c+such+as+requirements+or+ideas+on+implementation%3a%0D%0A%0D%0A
[bug]: https://github.com/evennia/evennia/issues/new?title=Bug%3a+%3Cdescriptive+title+here%3E&body=%23%23%23%23+Steps+to+reproduce+the+issue%3a%0D%0A%0D%0A1.+%0D%0A2.+%0D%0A3.+%0D%0A%0D%0A%23%23%23%23+What+I+expect+to+see+and+what+I+actually+see+%28tracebacks%2c+error+messages+etc%29%3a%0D%0A%0D%0A%0D%0A%0D%0A%23%23%23%23+Extra+information%2c+such+as+Evennia+revision%2frepo%2fbranch%2c+operating+system+and+ideas+for+how+to+solve%3a%0D%0A%0D%0A
[feature-request]:
(https://github.com/evennia/evennia/issues/new?title=Feature+Request%3a+%3Cdescriptive+title+here%3E&body=%23%23%23%23+Description+of+the+suggested+feature+and+how+it+is+supposed+to+work+for+the+admin%2fend+user%3a%0D%0A%0D%0A%0D%0A%23%23%23%23+A+list+of+arguments+for+why+you+think+this+new+feature+should+be+included+in+Evennia%3a%0D%0A%0D%0A1.%0D%0A2.%0D%0A%0D%0A%23%23%23%23+Extra+information%2c+such+as+requirements+or+ideas+on+implementation%3a%0D%0A%0D%0A
[bug]:
https://github.com/evennia/evennia/issues/new?title=Bug%3a+%3Cdescriptive+title+here%3E&body=%23%23%23%23+Steps+to+reproduce+the+issue%3a%0D%0A%0D%0A1.+%0D%0A2.+%0D%0A3.+%0D%0A%0D%0A%23%23%23%23+What+I+expect+to+see+and+what+I+actually+see+%28tracebacks%2c+error+messages+etc%29%3a%0D%0A%0D%0A%0D%0A%0D%0A%23%23%23%23+Extra+information%2c+such+as+Evennia+revision%2frepo%2fbranch%2c+operating+system+and+ideas+for+how+to+solve%3a%0D%0A%0D%0A

View file

@ -1,21 +1,28 @@
# Coding Utils
Evennia comes with many utilities to help with common coding tasks. Most are accessible directly from the flat API, otherwise you can find them in the `evennia/utils/` folder.
Evennia comes with many utilities to help with common coding tasks. Most are accessible directly
from the flat API, otherwise you can find them in the `evennia/utils/` folder.
## Searching
A common thing to do is to search for objects. There it's easiest to use the `search` method defined on all objects. This will search for objects in the same location and inside the self object:
A common thing to do is to search for objects. There it's easiest to use the `search` method defined
on all objects. This will search for objects in the same location and inside the self object:
```python
obj = self.search(objname)
```
The most common time one needs to do this is inside a command body. `obj = self.caller.search(objname)` will search inside the caller's (typically, the character that typed the command) `.contents` (their "inventory") and `.location` (their "room").
The most common time one needs to do this is inside a command body. `obj =
self.caller.search(objname)` will search inside the caller's (typically, the character that typed
the command) `.contents` (their "inventory") and `.location` (their "room").
Give the keyword `global_search=True` to extend search to encompass entire database. Aliases will also be matched by this search. You will find multiple examples of this functionality in the default command set.
Give the keyword `global_search=True` to extend search to encompass entire database. Aliases will
also be matched by this search. You will find multiple examples of this functionality in the default
command set.
If you need to search for objects in a code module you can use the functions in `evennia.utils.search`. You can access these as shortcuts `evennia.search_*`.
If you need to search for objects in a code module you can use the functions in
`evennia.utils.search`. You can access these as shortcuts `evennia.search_*`.
```python
from evennia import search_object
@ -30,11 +37,13 @@ If you need to search for objects in a code module you can use the functions in
- [evennia.search_message](../wiki/evennia.comms.managers#msgmanagersearch_message)
- [evennia.search_help](../wiki/evennia.help.manager#helpentrymanagersearch_help)
Note that these latter methods will always return a `list` of results, even if the list has one or zero entries.
Note that these latter methods will always return a `list` of results, even if the list has one or
zero entries.
## Create
Apart from the in-game build commands (`@create` etc), you can also build all of Evennia's game entities directly in code (for example when defining new create commands).
Apart from the in-game build commands (`@create` etc), you can also build all of Evennia's game
entities directly in code (for example when defining new create commands).
```python
import evennia
@ -48,11 +57,14 @@ Apart from the in-game build commands (`@create` etc), you can also build all of
- [evennia.create_help_entry](../wiki/evennia.utils.create#create_help_entry)
- [evennia.create_message](../wiki/evennia.utils.create#create_message)
Each of these create-functions have a host of arguments to further customize the created entity. See `evennia/utils/create.py` for more information.
Each of these create-functions have a host of arguments to further customize the created entity. See
`evennia/utils/create.py` for more information.
## Logging
Normally you can use Python `print` statements to see output to the terminal/log. The `print` statement should only be used for debugging though. For producion output, use the `logger` which will create proper logs either to terminal or to file.
Normally you can use Python `print` statements to see output to the terminal/log. The `print`
statement should only be used for debugging though. For producion output, use the `logger` which
will create proper logs either to terminal or to file.
```python
from evennia import logger
@ -63,7 +75,9 @@ Normally you can use Python `print` statements to see output to the terminal/log
logger.log_dep("This feature is deprecated")
```
There is a special log-message type, `log_trace()` that is intended to be called from inside a traceback - this can be very useful for relaying the traceback message back to log without having it kill the server.
There is a special log-message type, `log_trace()` that is intended to be called from inside a
traceback - this can be very useful for relaying the traceback message back to log without having it
kill the server.
```python
try:
@ -72,18 +86,25 @@ There is a special log-message type, `log_trace()` that is intended to be called
logger.log_trace("This text will show beneath the traceback itself.")
```
The `log_file` logger, finally, is a very useful logger for outputting arbitrary log messages. This is a heavily optimized asynchronous log mechanism using [threads](https://en.wikipedia.org/wiki/Thread_%28computing%29) to avoid overhead. You should be able to use it for very heavy custom logging without fearing disk-write delays.
The `log_file` logger, finally, is a very useful logger for outputting arbitrary log messages. This
is a heavily optimized asynchronous log mechanism using
[threads](https://en.wikipedia.org/wiki/Thread_%28computing%29) to avoid overhead. You should be
able to use it for very heavy custom logging without fearing disk-write delays.
```python
logger.log_file(message, filename="mylog.log")
```
If not an absolute path is given, the log file will appear in the `mygame/server/logs/` directory. If the file already exists, it will be appended to. Timestamps on the same format as the normal Evennia logs will be automatically added to each entry. If a filename is not specified, output will be written to a file `game/logs/game.log`.
If not an absolute path is given, the log file will appear in the `mygame/server/logs/` directory.
If the file already exists, it will be appended to. Timestamps on the same format as the normal
Evennia logs will be automatically added to each entry. If a filename is not specified, output will
be written to a file `game/logs/game.log`.
## Time Utilities
### Game time
Evennia tracks the current server time. You can access this time via the `evennia.gametime` shortcut:
Evennia tracks the current server time. You can access this time via the `evennia.gametime`
shortcut:
```python
from evennia import gametime
@ -110,9 +131,13 @@ gametime.reset_gametime()
```
The setting `TIME_FACTOR` determines how fast/slow in-game time runs compared to the real world. The setting `TIME_GAME_EPOCH` sets the starting game epoch (in seconds). The functions from the `gametime` module all return their times in seconds. You can convert this to whatever units of time you desire for your game. You can use the `@time` command to view the server time info.
The setting `TIME_FACTOR` determines how fast/slow in-game time runs compared to the real world. The
setting `TIME_GAME_EPOCH` sets the starting game epoch (in seconds). The functions from the
`gametime` module all return their times in seconds. You can convert this to whatever units of time
you desire for your game. You can use the `@time` command to view the server time info.
You can also *schedule* things to happen at specific in-game times using the [gametime.schedule](github:evennia.utils.gametime#schedule) function:
You can also *schedule* things to happen at specific in-game times using the
[gametime.schedule](github:evennia.utils.gametime#schedule) function:
```python
import evennia
@ -126,7 +151,9 @@ gametime.schedule(church_clock, hour=2)
### utils.time_format()
This function takes a number of seconds as input (e.g. from the `gametime` module above) and converts it to a nice text output in days, hours etc. It's useful when you want to show how old something is. It converts to four different styles of output using the *style* keyword:
This function takes a number of seconds as input (e.g. from the `gametime` module above) and
converts it to a nice text output in days, hours etc. It's useful when you want to show how old
something is. It converts to four different styles of output using the *style* keyword:
- style 0 - `5d:45m:12s` (standard colon output)
- style 1 - `5d` (shows only the longest time unit)
@ -148,20 +175,38 @@ deferred = utils.delay(10, _callback, obj, "Echo!", persistent=False)
```
This creates an asynchronous delayed call. It will fire the given callback function after the given number of seconds. This is a very light wrapper over a Twisted [Deferred](https://twistedmatrix.com/documents/current/core/howto/defer.html). Normally this is run non-persistently, which means that if the server is `@reload`ed before the delay is over, the callback will never run (the server forgets it). If setting `persistent` to True, the delay will be stored in the database and survive a `@reload` - but for this to work it is susceptible to the same limitations incurred when saving to an [Attribute](Attributes).
This creates an asynchronous delayed call. It will fire the given callback function after the given
number of seconds. This is a very light wrapper over a Twisted
[Deferred](https://twistedmatrix.com/documents/current/core/howto/defer.html). Normally this is run
non-persistently, which means that if the server is `@reload`ed before the delay is over, the
callback will never run (the server forgets it). If setting `persistent` to True, the delay will be
stored in the database and survive a `@reload` - but for this to work it is susceptible to the same
limitations incurred when saving to an [Attribute](Attributes).
The `deferred` return object can usually be ignored, but calling its `.cancel()` method will abort the delay prematurely.
The `deferred` return object can usually be ignored, but calling its `.cancel()` method will abort
the delay prematurely.
`utils.delay` is the lightest form of delayed call in Evennia. For other way to create time-bound tasks, see the [TickerHandler](TickerHandler) and [Scripts](Scripts).
`utils.delay` is the lightest form of delayed call in Evennia. For other way to create time-bound
tasks, see the [TickerHandler](TickerHandler) and [Scripts](Scripts).
> Note that many delayed effects can be achieved without any need for an active timer. For example if you have a trait that should recover a point every 5 seconds you might just need its value when it's needed, but checking the current time and calculating on the fly what value it should have.
> Note that many delayed effects can be achieved without any need for an active timer. For example
if you have a trait that should recover a point every 5 seconds you might just need its value when
it's needed, but checking the current time and calculating on the fly what value it should have.
## Object Classes
### utils.inherits_from()
This useful function takes two arguments - an object to check and a parent. It returns `True` if object inherits from parent *at any distance* (as opposed to Python's in-built `is_instance()` that will only catch immediate dependence). This function also accepts as input any combination of classes, instances or python-paths-to-classes.
This useful function takes two arguments - an object to check and a parent. It returns `True` if
object inherits from parent *at any distance* (as opposed to Python's in-built `is_instance()` that
will only catch immediate dependence). This function also accepts as input any combination of
classes, instances or python-paths-to-classes.
Note that Python code should usually work with [duck typing](http://en.wikipedia.org/wiki/Duck_typing). But in Evennia's case it can sometimes be useful to check if an object inherits from a given [Typeclass](Typeclasses) as a way of identification. Say for example that we have a typeclass *Animal*. This has a subclass *Felines* which in turn has a subclass *HouseCat*. Maybe there are a bunch of other animal types too, like horses and dogs. Using `inherits_from` will allow you to check for all animals in one go:
Note that Python code should usually work with [duck
typing](http://en.wikipedia.org/wiki/Duck_typing). But in Evennia's case it can sometimes be useful
to check if an object inherits from a given [Typeclass](Typeclasses) as a way of identification. Say
for example that we have a typeclass *Animal*. This has a subclass *Felines* which in turn has a
subclass *HouseCat*. Maybe there are a bunch of other animal types too, like horses and dogs. Using
`inherits_from` will allow you to check for all animals in one go:
```python
from evennia import utils
@ -173,11 +218,14 @@ Note that Python code should usually work with [duck typing](http://en.wikipedia
## Text utilities
In a text game, you are naturally doing a lot of work shuffling text back and forth. Here is a *non-complete* selection of text utilities found in `evennia/utils/utils.py` (shortcut `evennia.utils`). If nothing else it can be good to look here before starting to develop a solution of your own.
In a text game, you are naturally doing a lot of work shuffling text back and forth. Here is a *non-
complete* selection of text utilities found in `evennia/utils/utils.py` (shortcut `evennia.utils`).
If nothing else it can be good to look here before starting to develop a solution of your own.
### utils.fill()
This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also indents as needed.
This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also
indents as needed.
```python
outtxt = fill(intxt, width=78, indent=4)
@ -185,7 +233,8 @@ This flood-fills a text to a given width (shuffles the words to make each line e
### utils.crop()
This function will crop a very long line, adding a suffix to show the line actually continues. This can be useful in listings when showing multiple lines would mess up things.
This function will crop a very long line, adding a suffix to show the line actually continues. This
can be useful in listings when showing multiple lines would mess up things.
```python
intxt = "This is a long text that we want to crop."
@ -195,7 +244,11 @@ This function will crop a very long line, adding a suffix to show the line actua
### utils.dedent()
This solves what may at first glance appear to be a trivial problem with text - removing indentations. It is used to shift entire paragraphs to the left, without disturbing any further formatting they may have. A common case for this is when using Python triple-quoted strings in code - they will retain whichever indentation they have in the code, and to make easily-readable source code one usually don't want to shift the string to the left edge.
This solves what may at first glance appear to be a trivial problem with text - removing
indentations. It is used to shift entire paragraphs to the left, without disturbing any further
formatting they may have. A common case for this is when using Python triple-quoted strings in code
- they will retain whichever indentation they have in the code, and to make easily-readable source
code one usually don't want to shift the string to the left edge.
```python
#python code is entered at a given indentation
@ -209,7 +262,8 @@ This solves what may at first glance appear to be a trivial problem with text -
# but be shifted all the way to the left.
```
Normally you do the dedent in the display code (this is for example how the help system homogenizes help entries).
Normally you do the dedent in the display code (this is for example how the help system homogenizes
help entries).
### to_str() and to_bytes()
@ -228,9 +282,16 @@ never raise a traceback but instead echo errors through logging. See
## Display utilities
### Making ascii tables
The [EvTable](github:evennia.utils.evtable#evtable) class (`evennia/utils/evtable.py`) can be used to create correctly formatted text tables. There is also [EvForm](github:evennia.utils.evform#evform) (`evennia/utils/evform.py`). This reads a fixed-format text template from a file in order to create any level of sophisticated ascii layout. Both evtable and evform have lots of options and inputs so see the header of each module for help.
The [EvTable](github:evennia.utils.evtable#evtable) class (`evennia/utils/evtable.py`) can be used
to create correctly formatted text tables. There is also
[EvForm](github:evennia.utils.evform#evform) (`evennia/utils/evform.py`). This reads a fixed-format
text template from a file in order to create any level of sophisticated ascii layout. Both evtable
and evform have lots of options and inputs so see the header of each module for help.
The third-party [PrettyTable](https://code.google.com/p/prettytable/) module is also included in Evennia. PrettyTable is considered deprecated in favor of EvTable since PrettyTable cannot handle ANSI colour. PrettyTable can be found in `evennia/utils/prettytable/`. See its homepage above for instructions.
The third-party [PrettyTable](https://code.google.com/p/prettytable/) module is also included in
Evennia. PrettyTable is considered deprecated in favor of EvTable since PrettyTable cannot handle
ANSI colour. PrettyTable can be found in `evennia/utils/prettytable/`. See its homepage above for
instructions.
### Menus
- [evennia.EvMenu](github:evennia.utils.evmenu#evmenu)
- [evennia.EvMenu](github:evennia.utils.evmenu#evmenu)

View file

@ -95,4 +95,4 @@ using cooldowns also has the advantage of working *between* commands - you can
for example let all fire-related spells check the same cooldown to make sure
the casting of *Firestorm* blocks all fire-related spells for a while. Or, in
the case of taking that big swing with the sword, this could now block all
other types of attacks for a while before the warrior can recover.
other types of attacks for a while before the warrior can recover.

View file

@ -58,7 +58,8 @@ This syntax will not "freeze" all commands. While the command is "pausing",
## The more advanced way with utils.delay
The `yield` syntax is easy to read, easy to understand, easy to use. But it's not that flexible if you want more advanced options. Learning to use alternatives might be much worth it in the end.
The `yield` syntax is easy to read, easy to understand, easy to use. But it's not that flexible if
you want more advanced options. Learning to use alternatives might be much worth it in the end.
Below is a simple command example for adding a duration for a command to finish.
@ -93,20 +94,40 @@ class CmdEcho(default_cmds.MuxCommand):
self.caller.msg(string)
```
Import this new echo command into the default command set and reload the server. You will find that it will take 10 seconds before you see your shout coming back. You will also find that this is a *non-blocking* effect; you can issue other commands in the interim and the game will go on as usual. The echo will come back to you in its own time.
Import this new echo command into the default command set and reload the server. You will find that
it will take 10 seconds before you see your shout coming back. You will also find that this is a
*non-blocking* effect; you can issue other commands in the interim and the game will go on as usual.
The echo will come back to you in its own time.
### About utils.delay()
`utils.delay(timedelay, callback, persistent=False, *args, **kwargs)` is a useful function. It will wait `timedelay` seconds, then call the `callback` function, optionally passing to it the arguments provided to utils.delay by way of *args and/or **kwargs`.
`utils.delay(timedelay, callback, persistent=False, *args, **kwargs)` is a useful function. It will
wait `timedelay` seconds, then call the `callback` function, optionally passing to it the arguments
provided to utils.delay by way of *args and/or **kwargs`.
> Note: The callback argument should be provided with a python path to the desired function, for instance `my_object.my_function` instead of `my_object.my_function()`. Otherwise my_function would get called and run immediately upon attempting to pass it to the delay function.
If you want to provide arguments for utils.delay to use, when calling your callback function, you have to do it separatly, for instance using the utils.delay *args and/or **kwargs, as mentioned above.
> Note: The callback argument should be provided with a python path to the desired function, for
instance `my_object.my_function` instead of `my_object.my_function()`. Otherwise my_function would
get called and run immediately upon attempting to pass it to the delay function.
If you want to provide arguments for utils.delay to use, when calling your callback function, you
have to do it separatly, for instance using the utils.delay *args and/or **kwargs, as mentioned
above.
> If you are not familiar with the syntax `*args` and `**kwargs`, [see the Python documentation here](https://docs.python.org/2/tutorial/controlflow.html#arbitrary-argument-lists).
> If you are not familiar with the syntax `*args` and `**kwargs`, [see the Python documentation
here](https://docs.python.org/2/tutorial/controlflow.html#arbitrary-argument-lists).
Looking at it you might think that `utils.delay(10, callback)` in the code above is just an alternative to some more familiar thing like `time.sleep(10)`. This is *not* the case. If you do `time.sleep(10)` you will in fact freeze the *entire server* for ten seconds! The `utils.delay()`is a thin wrapper around a Twisted [Deferred](http://twistedmatrix.com/documents/11.0.0/core/howto/defer.html) that will delay execution until 10 seconds have passed, but will do so asynchronously, without bothering anyone else (not even you - you can continue to do stuff normally while it waits to continue).
Looking at it you might think that `utils.delay(10, callback)` in the code above is just an
alternative to some more familiar thing like `time.sleep(10)`. This is *not* the case. If you do
`time.sleep(10)` you will in fact freeze the *entire server* for ten seconds! The `utils.delay()`is
a thin wrapper around a Twisted
[Deferred](http://twistedmatrix.com/documents/11.0.0/core/howto/defer.html) that will delay
execution until 10 seconds have passed, but will do so asynchronously, without bothering anyone else
(not even you - you can continue to do stuff normally while it waits to continue).
The point to remember here is that the `delay()` call will not "pause" at that point when it is called (the way `yield` does in the previous section). The lines after the `delay()` call will actually execute *right away*. What you must do is to tell it which function to call *after the time has passed* (its "callback"). This may sound strange at first, but it is normal practice in asynchronous systems. You can also link such calls together as seen below:
The point to remember here is that the `delay()` call will not "pause" at that point when it is
called (the way `yield` does in the previous section). The lines after the `delay()` call will
actually execute *right away*. What you must do is to tell it which function to call *after the time
has passed* (its "callback"). This may sound strange at first, but it is normal practice in
asynchronous systems. You can also link such calls together as seen below:
```python
from evennia import default_cmds, utils
@ -148,7 +169,8 @@ class CmdEcho(default_cmds.MuxCommand):
self.caller.msg("... %s ..." % self.args.lower())
```
The above version will have the echoes arrive one after another, each separated by a two second delay.
The above version will have the echoes arrive one after another, each separated by a two second
delay.
> echo Hello!
... HELLO!
@ -157,9 +179,19 @@ The above version will have the echoes arrive one after another, each separated
## Blocking commands
As mentioned, a great thing about the delay introduced by `yield` or `utils.delay()` is that it does not block. It just goes on in the background and you are free to play normally in the interim. In some cases this is not what you want however. Some commands should simply "block" other commands while they are running. If you are in the process of crafting a helmet you shouldn't be able to also start crafting a shield at the same time, or if you just did a huge power-swing with your weapon you should not be able to do it again immediately.
As mentioned, a great thing about the delay introduced by `yield` or `utils.delay()` is that it does
not block. It just goes on in the background and you are free to play normally in the interim. In
some cases this is not what you want however. Some commands should simply "block" other commands
while they are running. If you are in the process of crafting a helmet you shouldn't be able to also
start crafting a shield at the same time, or if you just did a huge power-swing with your weapon you
should not be able to do it again immediately.
The simplest way of implementing blocking is to use the technique covered in the [Command Cooldown](Command-Cooldown) tutorial. In that tutorial we implemented cooldowns by having the Command store the current time. Next time the Command was called, we compared the current time to the stored time to determine if enough time had passed for a renewed use. This is a *very* efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player when enough time has passed unless they keep trying.
The simplest way of implementing blocking is to use the technique covered in the [Command
Cooldown](Command-Cooldown) tutorial. In that tutorial we implemented cooldowns by having the
Command store the current time. Next time the Command was called, we compared the current time to
the stored time to determine if enough time had passed for a renewed use. This is a *very*
efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player
when enough time has passed unless they keep trying.
Here is an example where we will use `utils.delay` to tell the player when the cooldown has passed:
@ -203,15 +235,22 @@ class CmdBigSwing(default_cmds.MuxCommand):
self.caller.msg("You regain your balance.")
```
Note how, after the cooldown, the user will get a message telling them they are now ready for another swing.
Note how, after the cooldown, the user will get a message telling them they are now ready for
another swing.
By storing the `off_balance` flag on the character (rather than on, say, the Command instance itself) it can be accessed by other Commands too. Other attacks may also not work when you are off balance. You could also have an enemy Command check your `off_balance` status to gain bonuses, to take another example.
By storing the `off_balance` flag on the character (rather than on, say, the Command instance
itself) it can be accessed by other Commands too. Other attacks may also not work when you are off
balance. You could also have an enemy Command check your `off_balance` status to gain bonuses, to
take another example.
## Abortable commands
One can imagine that you will want to abort a long-running command before it has a time to finish. If you are in the middle of crafting your armor you will probably want to stop doing that when a monster enters your smithy.
One can imagine that you will want to abort a long-running command before it has a time to finish.
If you are in the middle of crafting your armor you will probably want to stop doing that when a
monster enters your smithy.
You can implement this in the same way as you do the "blocking" command above, just in reverse. Below is an example of a crafting command that can be aborted by starting a fight:
You can implement this in the same way as you do the "blocking" command above, just in reverse.
Below is an example of a crafting command that can be aborted by starting a fight:
```python
from evennia import utils, default_cmds
@ -304,11 +343,18 @@ class CmdAttack(default_cmds.MuxCommand):
# [...]
```
The above code creates a delayed crafting command that will gradually create the armour. If the `attack` command is issued during this process it will set a flag that causes the crafting to be quietly canceled next time it tries to update.
The above code creates a delayed crafting command that will gradually create the armour. If the
`attack` command is issued during this process it will set a flag that causes the crafting to be
quietly canceled next time it tries to update.
## Persistent delays
In the latter examples above we used `.ndb` storage. This is fast and easy but it will reset all cooldowns/blocks/crafting etc if you reload the server. If you don't want that you can replace `.ndb` with `.db`. But even this won't help because the `yield` keyword is not persisent and nor is the use of `delay` shown above. To resolve this you can use `delay` with the `persistent=True` keyword. But wait! Making something persistent will add some extra complications, because now you must make sure Evennia can properly store things to the database.
In the latter examples above we used `.ndb` storage. This is fast and easy but it will reset all
cooldowns/blocks/crafting etc if you reload the server. If you don't want that you can replace
`.ndb` with `.db`. But even this won't help because the `yield` keyword is not persisent and nor is
the use of `delay` shown above. To resolve this you can use `delay` with the `persistent=True`
keyword. But wait! Making something persistent will add some extra complications, because now you
must make sure Evennia can properly store things to the database.
Here is the original echo-command reworked to function with persistence:
```python
@ -345,7 +391,13 @@ class CmdEcho(default_cmds.MuxCommand):
```
Above you notice two changes:
- The callback (`echo`) was moved out of the class and became its own stand-alone function in the outermost scope of the module. It also now takes `caller` and `args` as arguments (it doesn't have access to them directly since this is now a stand-alone function).
- `utils.delay` specifies the `echo` function (not `self.echo` - it's no longer a method!) and sends `self.caller` and `self.args` as arguments for it to use. We also set `persistent=True`.
- The callback (`echo`) was moved out of the class and became its own stand-alone function in the
outermost scope of the module. It also now takes `caller` and `args` as arguments (it doesn't have
access to them directly since this is now a stand-alone function).
- `utils.delay` specifies the `echo` function (not `self.echo` - it's no longer a method!) and sends
`self.caller` and `self.args` as arguments for it to use. We also set `persistent=True`.
The reason for this change is because Evennia needs to `pickle` the callback into storage and it cannot do this correctly when the method sits on the command class. Now this behave the same as the first version except if you reload (or even shut down) the server mid-delay it will still fire the callback when the server comes back up (it will resume the countdown and ignore the downtime).
The reason for this change is because Evennia needs to `pickle` the callback into storage and it
cannot do this correctly when the method sits on the command class. Now this behave the same as the
first version except if you reload (or even shut down) the server mid-delay it will still fire the
callback when the server comes back up (it will resume the countdown and ignore the downtime).

View file

@ -1,11 +1,16 @@
# Command Prompt
A *prompt* is quite common in MUDs. The prompt display useful details about your character that you are likely to want to keep tabs on at all times, such as health, magical power etc. It might also show things like in-game time, weather and so on. Many modern MUD clients (including Evennia's own webclient) allows for identifying the prompt and have it appear in a correct location (usually just above the input line). Usually it will remain like that until it is explicitly updated.
A *prompt* is quite common in MUDs. The prompt display useful details about your character that you
are likely to want to keep tabs on at all times, such as health, magical power etc. It might also
show things like in-game time, weather and so on. Many modern MUD clients (including Evennia's own
webclient) allows for identifying the prompt and have it appear in a correct location (usually just
above the input line). Usually it will remain like that until it is explicitly updated.
## Sending a prompt
A prompt is sent using the `prompt` keyword to the `msg()` method on objects. The prompt will be sent without any line breaks.
A prompt is sent using the `prompt` keyword to the `msg()` method on objects. The prompt will be
sent without any line breaks.
```python
self.msg(prompt="HP: 5, MP: 2, SP: 8")
@ -16,7 +21,9 @@ You can combine the sending of normal text with the sending (updating of the pro
self.msg("This is a text", prompt="This is a prompt")
```
You can update the prompt on demand, this is normally done using [OOB](OOB)-tracking of the relevant Attributes (like the character's health). You could also make sure that attacking commands update the prompt when they cause a change in health, for example.
You can update the prompt on demand, this is normally done using [OOB](OOB)-tracking of the relevant
Attributes (like the character's health). You could also make sure that attacking commands update
the prompt when they cause a change in health, for example.
Here is a simple example of the prompt sent/updated from a command class:
@ -60,9 +67,16 @@ Here is a simple example of the prompt sent/updated from a command class:
```
## A prompt sent with every command
The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a special flag). Most MUD telnet clients will understand and allow users to catch this and keep the prompt in place until it updates. So *in principle* you'd not need to update the prompt every command.
The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a
special flag). Most MUD telnet clients will understand and allow users to catch this and keep the
prompt in place until it updates. So *in principle* you'd not need to update the prompt every
command.
However, with a varying user base it can be unclear which clients are used and which skill level the users have. So sending a prompt with every command is a safe catch-all. You don't need to manually go in and edit every command you have though. Instead you edit the base command class for your custom commands (like `MuxCommand` in your `mygame/commands/command.py` folder) and overload the `at_post_cmd()` hook. This hook is always called *after* the main `func()` method of the Command.
However, with a varying user base it can be unclear which clients are used and which skill level the
users have. So sending a prompt with every command is a safe catch-all. You don't need to manually
go in and edit every command you have though. Instead you edit the base command class for your
custom commands (like `MuxCommand` in your `mygame/commands/command.py` folder) and overload the
`at_post_cmd()` hook. This hook is always called *after* the main `func()` method of the Command.
```python
from evennia import default_cmds
@ -81,7 +95,8 @@ class MuxCommand(default_cmds.MuxCommand):
### Modifying default commands
If you want to add something small like this to Evennia's default commands without modifying them directly the easiest way is to just wrap those with a multiple inheritance to your own base class:
If you want to add something small like this to Evennia's default commands without modifying them
directly the easiest way is to just wrap those with a multiple inheritance to your own base class:
```python
# in (for example) mygame/commands/mycommands.py
@ -95,7 +110,8 @@ class CmdLook(default_cmds.CmdLook, MuxCommand):
pass
```
The result of this is that the hooks from your custom `MuxCommand` will be mixed into the default `CmdLook` through multiple inheritance. Next you just add this to your default command set:
The result of this is that the hooks from your custom `MuxCommand` will be mixed into the default
`CmdLook` through multiple inheritance. Next you just add this to your default command set:
```python
# in mygame/commands/default_cmdsets.py

View file

@ -1,21 +1,41 @@
# Command Sets
Command Sets are intimately linked with [Commands](Commands) and you should be familiar with Commands before reading this page. The two pages were split for ease of reading.
Command Sets are intimately linked with [Commands](Commands) and you should be familiar with
Commands before reading this page. The two pages were split for ease of reading.
A *Command Set* (often referred to as a CmdSet or cmdset) is the basic unit for storing one or more *Commands*. A given Command can go into any number of different command sets. Storing Command classes in a command set is the way to make commands available to use in your game.
A *Command Set* (often referred to as a CmdSet or cmdset) is the basic unit for storing one or more
*Commands*. A given Command can go into any number of different command sets. Storing Command
classes in a command set is the way to make commands available to use in your game.
When storing a CmdSet on an object, you will make the commands in that command set available to the object. An example is the default command set stored on new Characters. This command set contains all the useful commands, from `look` and `inventory` to `@dig` and `@reload` ([permissions](Locks#Permissions) then limit which players may use them, but that's a separate topic).
When storing a CmdSet on an object, you will make the commands in that command set available to the
object. An example is the default command set stored on new Characters. This command set contains
all the useful commands, from `look` and `inventory` to `@dig` and `@reload`
([permissions](Locks#Permissions) then limit which players may use them, but that's a separate
topic).
When an account enters a command, cmdsets from the Account, Character, its location, and elsewhere are pulled together into a *merge stack*. This stack is merged together in a specific order to create a single "merged" cmdset, representing the pool of commands available at that very moment.
When an account enters a command, cmdsets from the Account, Character, its location, and elsewhere
are pulled together into a *merge stack*. This stack is merged together in a specific order to
create a single "merged" cmdset, representing the pool of commands available at that very moment.
An example would be a `Window` object that has a cmdset with two commands in it: `look through window` and `open window`. The command set would be visible to players in the room with the window, allowing them to use those commands only there. You could imagine all sorts of clever uses of this, like a `Television` object which had multiple commands for looking at it, switching channels and so on. The tutorial world included with Evennia showcases a dark room that replaces certain critical commands with its own versions because the Character cannot see.
An example would be a `Window` object that has a cmdset with two commands in it: `look through
window` and `open window`. The command set would be visible to players in the room with the window,
allowing them to use those commands only there. You could imagine all sorts of clever uses of this,
like a `Television` object which had multiple commands for looking at it, switching channels and so
on. The tutorial world included with Evennia showcases a dark room that replaces certain critical
commands with its own versions because the Character cannot see.
If you want a quick start into defining your first commands and using them with command sets, you can head over to the [Adding Command Tutorial](Adding-Command-Tutorial) which steps through things without the explanations.
If you want a quick start into defining your first commands and using them with command sets, you
can head over to the [Adding Command Tutorial](Adding-Command-Tutorial) which steps through things
without the explanations.
## Defining Command Sets
A CmdSet is, as most things in Evennia, defined as a Python class inheriting from the correct parent (`evennia.CmdSet`, which is a shortcut to `evennia.commands.cmdset.CmdSet`). The CmdSet class only needs to define one method, called `at_cmdset_creation()`. All other class parameters are optional, but are used for more advanced set manipulation and coding (see the [merge rules](Command-Sets#merge-rules) section).
A CmdSet is, as most things in Evennia, defined as a Python class inheriting from the correct parent
(`evennia.CmdSet`, which is a shortcut to `evennia.commands.cmdset.CmdSet`). The CmdSet class only
needs to define one method, called `at_cmdset_creation()`. All other class parameters are optional,
but are used for more advanced set manipulation and coding (see the [merge rules](Command-
Sets#merge-rules) section).
```python
# file mygame/commands/mycmdset.py
@ -37,7 +57,8 @@ class MyCmdSet(CmdSet):
self.add(mycommands.MyCommand3())
```
The CmdSet's `add()` method can also take another CmdSet as input. In this case all the commands from that CmdSet will be appended to this one as if you added them line by line:
The CmdSet's `add()` method can also take another CmdSet as input. In this case all the commands
from that CmdSet will be appended to this one as if you added them line by line:
```python
def at_cmdset_creation():
@ -46,7 +67,8 @@ The CmdSet's `add()` method can also take another CmdSet as input. In this case
...
```
If you added your command to an existing cmdset (like to the default cmdset), that set is already loaded into memory. You need to make the server aware of the code changes:
If you added your command to an existing cmdset (like to the default cmdset), that set is already
loaded into memory. You need to make the server aware of the code changes:
```
@reload
@ -54,7 +76,9 @@ If you added your command to an existing cmdset (like to the default cmdset), th
You should now be able to use the command.
If you created a new, fresh cmdset, this must be added to an object in order to make the commands within available. A simple way to temporarily test a cmdset on yourself is use the `@py` command to execute a python snippet:
If you created a new, fresh cmdset, this must be added to an object in order to make the commands
within available. A simple way to temporarily test a cmdset on yourself is use the `@py` command to
execute a python snippet:
```python
@py self.cmdset.add('commands.mycmdset.MyCmdSet')
@ -66,7 +90,8 @@ This will stay with you until you `@reset` or `@shutdown` the server, or you run
@py self.cmdset.delete('commands.mycmdset.MyCmdSet')
```
In the example above, a specific Cmdset class is removed. Calling `delete` without arguments will remove the latest added cmdset.
In the example above, a specific Cmdset class is removed. Calling `delete` without arguments will
remove the latest added cmdset.
> Note: Command sets added using `cmdset.add` are, by default, *not* persistent in the database.
@ -82,106 +107,235 @@ Or you could add the cmdset as the *default* cmdset:
@py self.cmdset.add_default(commands.mycmdset.MyCmdSet)
```
An object can only have one "default" cmdset (but can also have none). This is meant as a safe fall-back even if all other cmdsets fail or are removed. It is always persistent and will not be affected by `cmdset.delete()`. To remove a default cmdset you must explicitly call `cmdset.remove_default()`.
An object can only have one "default" cmdset (but can also have none). This is meant as a safe fall-
back even if all other cmdsets fail or are removed. It is always persistent and will not be affected
by `cmdset.delete()`. To remove a default cmdset you must explicitly call `cmdset.remove_default()`.
Command sets are often added to an object in its `at_object_creation` method. For more examples of adding commands, read the [Step by step tutorial](Adding-Command-Tutorial). Generally you can customize which command sets are added to your objects by using `self.cmdset.add()` or `self.cmdset.add_default()`.
Command sets are often added to an object in its `at_object_creation` method. For more examples of
adding commands, read the [Step by step tutorial](Adding-Command-Tutorial). Generally you can
customize which command sets are added to your objects by using `self.cmdset.add()` or
`self.cmdset.add_default()`.
> Important: Commands are identified uniquely by key *or* alias (see [Commands](Commands)). If any overlap exists, two commands are considered identical. Adding a Command to a command set that already has an identical command will *replace* the previous command. This is very important. You must take this behavior into account when attempting to overload any default Evennia commands with your own. Otherwise, you may accidentally "hide" your own command in your command set when adding a new one that has a matching alias.
> Important: Commands are identified uniquely by key *or* alias (see [Commands](Commands)). If any
overlap exists, two commands are considered identical. Adding a Command to a command set that
already has an identical command will *replace* the previous command. This is very important. You
must take this behavior into account when attempting to overload any default Evennia commands with
your own. Otherwise, you may accidentally "hide" your own command in your command set when adding a
new one that has a matching alias.
### Properties on Command Sets
There are several extra flags that you can set on CmdSets in order to modify how they work. All are optional and will be set to defaults otherwise. Since many of these relate to *merging* cmdsets, you might want to read the [Adding and Merging Command Sets](Command-Sets#adding-and-merging-command-sets) section for some of these to make sense.
There are several extra flags that you can set on CmdSets in order to modify how they work. All are
optional and will be set to defaults otherwise. Since many of these relate to *merging* cmdsets,
you might want to read the [Adding and Merging Command Sets](Command-Sets#adding-and-merging-
command-sets) section for some of these to make sense.
- `key` (string) - an identifier for the cmdset. This is optional, but should be unique. It is used for display in lists, but also to identify special merging behaviours using the `key_mergetype` dictionary below.
- `mergetype` (string) - allows for one of the following string values: "*Union*", "*Intersect*", "*Replace*", or "*Remove*".
- `priority` (int) - This defines the merge order of the merge stack - cmdsets will merge in rising order of priority with the highest priority set merging last. During a merger, the commands from the set with the higher priority will have precedence (just what happens depends on the [merge type](Command-Sets#adding-and-merging-command-sets)). If priority is identical, the order in the merge stack determines preference. The priority value must be greater or equal to `-100`. Most in-game sets should usually have priorities between `0` and `100`. Evennia default sets have priorities as follows (these can be changed if you want a different distribution):
- `key` (string) - an identifier for the cmdset. This is optional, but should be unique. It is used
for display in lists, but also to identify special merging behaviours using the `key_mergetype`
dictionary below.
- `mergetype` (string) - allows for one of the following string values: "*Union*", "*Intersect*",
"*Replace*", or "*Remove*".
- `priority` (int) - This defines the merge order of the merge stack - cmdsets will merge in rising
order of priority with the highest priority set merging last. During a merger, the commands from the
set with the higher priority will have precedence (just what happens depends on the [merge
type](Command-Sets#adding-and-merging-command-sets)). If priority is identical, the order in the
merge stack determines preference. The priority value must be greater or equal to `-100`. Most in-
game sets should usually have priorities between `0` and `100`. Evennia default sets have priorities
as follows (these can be changed if you want a different distribution):
- EmptySet: `-101` (should be lower than all other sets)
- SessionCmdSet: `-20`
- AccountCmdSet: `-10`
- CharacterCmdSet: `0`
- ExitCmdSet: ` 101` (generally should always be available)
- ChannelCmdSet: `101` (should usually always be available) - since exits never accept arguments, there is no collision between exits named the same as a channel even though the commands "collide".
- `key_mergetype` (dict) - a dict of `key:mergetype` pairs. This allows this cmdset to merge differently with certain named cmdsets. If the cmdset to merge with has a `key` matching an entry in `key_mergetype`, it will not be merged according to the setting in `mergetype` but according to the mode in this dict. Please note that this is more complex than it may seem due to the [merge order](Command-Sets#adding-and-merging-command-sets) of command sets. Please review that section before using `key_mergetype`.
- `duplicates` (bool/None default `None`) - this determines what happens when merging same-priority cmdsets containing same-key commands together. The`dupicate` option will *only* apply when merging the cmdset with this option onto one other cmdset with the same priority. The resulting cmdset will *not* retain this `duplicate` setting.
- `None` (default): No duplicates are allowed and the cmdset being merged "onto" the old one will take precedence. The result will be unique commands. *However*, the system will assume this value to be `True` for cmdsets on Objects, to avoid dangerous clashes. This is usually the safe bet.
- `False`: Like `None` except the system will not auto-assume any value for cmdsets defined on Objects.
- `True`: Same-named, same-prio commands will merge into the same cmdset. This will lead to a multimatch error (the user will get a list of possibilities in order to specify which command they meant). This is is useful e.g. for on-object cmdsets (example: There is a `red button` and a `green button` in the room. Both have a `press button` command, in cmdsets with the same priority. This flag makes sure that just writing `press button` will force the Player to define just which object's command was intended).
- `no_objs` this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from objects around the account (nor from rooms or inventory) when building the merged set. Exit commands will still be included. This option can have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If never set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_objs` are merged, priority determines what is used.
- `no_exits` - this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from exits. This flag can have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If never set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_exits` are merged, priority determines what is used.
- `no_channels` (bool) - this is a flag for the cmdhandler that builds the set of commands available at every moment. It tells the handler not to include cmdsets from available in-game channels. This flag can have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If never set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_channels` are merged, priority determines what is used.
- ChannelCmdSet: `101` (should usually always be available) - since exits never accept
arguments, there is no collision between exits named the same as a channel even though the commands
"collide".
- `key_mergetype` (dict) - a dict of `key:mergetype` pairs. This allows this cmdset to merge
differently with certain named cmdsets. If the cmdset to merge with has a `key` matching an entry in
`key_mergetype`, it will not be merged according to the setting in `mergetype` but according to the
mode in this dict. Please note that this is more complex than it may seem due to the [merge
order](Command-Sets#adding-and-merging-command-sets) of command sets. Please review that section
before using `key_mergetype`.
- `duplicates` (bool/None default `None`) - this determines what happens when merging same-priority
cmdsets containing same-key commands together. The`dupicate` option will *only* apply when merging
the cmdset with this option onto one other cmdset with the same priority. The resulting cmdset will
*not* retain this `duplicate` setting.
- `None` (default): No duplicates are allowed and the cmdset being merged "onto" the old one
will take precedence. The result will be unique commands. *However*, the system will assume this
value to be `True` for cmdsets on Objects, to avoid dangerous clashes. This is usually the safe bet.
- `False`: Like `None` except the system will not auto-assume any value for cmdsets defined on
Objects.
- `True`: Same-named, same-prio commands will merge into the same cmdset. This will lead to a
multimatch error (the user will get a list of possibilities in order to specify which command they
meant). This is is useful e.g. for on-object cmdsets (example: There is a `red button` and a `green
button` in the room. Both have a `press button` command, in cmdsets with the same priority. This
flag makes sure that just writing `press button` will force the Player to define just which object's
command was intended).
- `no_objs` this is a flag for the cmdhandler that builds the set of commands available at every
moment. It tells the handler not to include cmdsets from objects around the account (nor from rooms
or inventory) when building the merged set. Exit commands will still be included. This option can
have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If never
set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_objs` are merged,
priority determines what is used.
- `no_exits` - this is a flag for the cmdhandler that builds the set of commands available at every
moment. It tells the handler not to include cmdsets from exits. This flag can have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If
never set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_exits` are merged,
priority determines what is used.
- `no_channels` (bool) - this is a flag for the cmdhandler that builds the set of commands available
at every moment. It tells the handler not to include cmdsets from available in-game channels. This
flag can have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If
never set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_channels` are merged,
priority determines what is used.
## Command Sets Searched
When a user issues a command, it is matched against the [merged](Command-Sets#adding-and-merging-command-sets) command sets available to the player at the moment. Which those are may change at any time (such as when the player walks into the room with the `Window` object described earlier).
When a user issues a command, it is matched against the [merged](Command-Sets#adding-and-merging-
command-sets) command sets available to the player at the moment. Which those are may change at any
time (such as when the player walks into the room with the `Window` object described earlier).
The currently valid command sets are collected from the following sources:
- The cmdsets stored on the currently active [Session](Sessions). Default is the empty `SessionCmdSet` with merge priority `-20`.
- The cmdsets defined on the [Account](Accounts). Default is the AccountCmdSet with merge priority `-10`.
- All cmdsets on the Character/Object (assuming the Account is currently puppeting such a Character/Object). Merge priority `0`.
- The cmdsets of all objects carried by the puppeted Character (checks the `call` lock). Will not be included if `no_objs` option is active in the merge stack.
- The cmdsets of the Character's current location (checks the `call` lock). Will not be included if `no_objs` option is active in the merge stack.
- The cmdsets of objects in the current location (checks the `call` lock). Will not be included if `no_objs` option is active in the merge stack.
- The cmdsets of Exits in the location. Merge priority `+101`. Will not be included if `no_exits` *or* `no_objs` option is active in the merge stack.
- The [channel](Communications) cmdset containing commands for posting to all channels the account or character is currently connected to. Merge priority `+101`. Will not be included if `no_channels` option is active in the merge stack.
- The cmdsets stored on the currently active [Session](Sessions). Default is the empty
`SessionCmdSet` with merge priority `-20`.
- The cmdsets defined on the [Account](Accounts). Default is the AccountCmdSet with merge priority
`-10`.
- All cmdsets on the Character/Object (assuming the Account is currently puppeting such a
Character/Object). Merge priority `0`.
- The cmdsets of all objects carried by the puppeted Character (checks the `call` lock). Will not be
included if `no_objs` option is active in the merge stack.
- The cmdsets of the Character's current location (checks the `call` lock). Will not be included if
`no_objs` option is active in the merge stack.
- The cmdsets of objects in the current location (checks the `call` lock). Will not be included if
`no_objs` option is active in the merge stack.
- The cmdsets of Exits in the location. Merge priority `+101`. Will not be included if `no_exits`
*or* `no_objs` option is active in the merge stack.
- The [channel](Communications) cmdset containing commands for posting to all channels the account
or character is currently connected to. Merge priority `+101`. Will not be included if `no_channels`
option is active in the merge stack.
Note that an object does not *have* to share its commands with its surroundings. A Character's cmdsets should not be shared for example, or all other Characters would get multi-match errors just by being in the same room. The ability of an object to share its cmdsets is managed by its `call` [lock](Locks). For example, [Character objects](Objects) defaults to `call:false()` so that any cmdsets on them can only be accessed by themselves, not by other objects around them. Another example might be to lock an object with `call:inside()` to only make their commands available to objects inside them, or `cmd:holds()` to make their commands available only if they are held.
Note that an object does not *have* to share its commands with its surroundings. A Character's
cmdsets should not be shared for example, or all other Characters would get multi-match errors just
by being in the same room. The ability of an object to share its cmdsets is managed by its `call`
[lock](Locks). For example, [Character objects](Objects) defaults to `call:false()` so that any
cmdsets on them can only be accessed by themselves, not by other objects around them. Another
example might be to lock an object with `call:inside()` to only make their commands available to
objects inside them, or `cmd:holds()` to make their commands available only if they are held.
## Adding and Merging Command Sets
*Note: This is an advanced topic. It's very useful to know about, but you might want to skip it if this is your first time learning about commands.*
*Note: This is an advanced topic. It's very useful to know about, but you might want to skip it if
this is your first time learning about commands.*
CmdSets have the special ability that they can be *merged* together into new sets. Which of the ingoing commands end up in the merged set is defined by the *merge rule* and the relative *priorities* of the two sets. Removing the latest added set will restore things back to the way it was before the addition.
CmdSets have the special ability that they can be *merged* together into new sets. Which of the
ingoing commands end up in the merged set is defined by the *merge rule* and the relative
*priorities* of the two sets. Removing the latest added set will restore things back to the way it
was before the addition.
CmdSets are non-destructively stored in a stack inside the cmdset handler on the object. This stack is parsed to create the "combined" cmdset active at the moment. CmdSets from other sources are also included in the merger such as those on objects in the same room (like buttons to press) or those introduced by state changes (such as when entering a menu). The cmdsets are all ordered after priority and then merged together in *reverse order*. That is, the higher priority will be merged "onto" lower-prio ones. By defining a cmdset with a merge-priority between that of two other sets, you will make sure it will be merged in between them.
The very first cmdset in this stack is called the *Default cmdset* and is protected from accidental deletion. Running `obj.cmdset.delete()` will never delete the default set. Instead one should add new cmdsets on top of the default to "hide" it, as described below. Use the special `obj.cmdset.delete_default()` only if you really know what you are doing.
CmdSets are non-destructively stored in a stack inside the cmdset handler on the object. This stack
is parsed to create the "combined" cmdset active at the moment. CmdSets from other sources are also
included in the merger such as those on objects in the same room (like buttons to press) or those
introduced by state changes (such as when entering a menu). The cmdsets are all ordered after
priority and then merged together in *reverse order*. That is, the higher priority will be merged
"onto" lower-prio ones. By defining a cmdset with a merge-priority between that of two other sets,
you will make sure it will be merged in between them.
The very first cmdset in this stack is called the *Default cmdset* and is protected from accidental
deletion. Running `obj.cmdset.delete()` will never delete the default set. Instead one should add
new cmdsets on top of the default to "hide" it, as described below. Use the special
`obj.cmdset.delete_default()` only if you really know what you are doing.
CmdSet merging is an advanced feature useful for implementing powerful game effects. Imagine for example a player entering a dark room. You don't want the player to be able to find everything in the room at a glance - maybe you even want them to have a hard time to find stuff in their backpack! You can then define a different CmdSet with commands that override the normal ones. While they are in the dark room, maybe the `look` and `inv` commands now just tell the player they cannot see anything! Another example would be to offer special combat commands only when the player is in combat. Or when being on a boat. Or when having taken the super power-up. All this can be done on the fly by merging command sets.
CmdSet merging is an advanced feature useful for implementing powerful game effects. Imagine for
example a player entering a dark room. You don't want the player to be able to find everything in
the room at a glance - maybe you even want them to have a hard time to find stuff in their backpack!
You can then define a different CmdSet with commands that override the normal ones. While they are
in the dark room, maybe the `look` and `inv` commands now just tell the player they cannot see
anything! Another example would be to offer special combat commands only when the player is in
combat. Or when being on a boat. Or when having taken the super power-up. All this can be done on
the fly by merging command sets.
### Merge Rules
Basic rule is that command sets are merged in *reverse priority order*. That is, lower-prio sets are merged first and higher prio sets are merged "on top" of them. Think of it like a layered cake with the highest priority on top.
Basic rule is that command sets are merged in *reverse priority order*. That is, lower-prio sets are
merged first and higher prio sets are merged "on top" of them. Think of it like a layered cake with
the highest priority on top.
To further understand how sets merge, we need to define some examples. Let's call the first command set **A** and the second **B**. We assume **B** is the command set already active on our object and we will merge **A** onto **B**. In code terms this would be done by `object.cdmset.add(A)`. Remember, B is already active on `object` from before.
To further understand how sets merge, we need to define some examples. Let's call the first command
set **A** and the second **B**. We assume **B** is the command set already active on our object and
we will merge **A** onto **B**. In code terms this would be done by `object.cdmset.add(A)`.
Remember, B is already active on `object` from before.
We let the **A** set have higher priority than **B**. A priority is simply an integer number. As seen in the list above, Evennia's default cmdsets have priorities in the range `-101` to `120`. You are usually safe to use a priority of `0` or `1` for most game effects.
We let the **A** set have higher priority than **B**. A priority is simply an integer number. As
seen in the list above, Evennia's default cmdsets have priorities in the range `-101` to `120`. You
are usually safe to use a priority of `0` or `1` for most game effects.
In our examples, both sets contain a number of commands which we'll identify by numbers, like `A1, A2` for set **A** and `B1, B2, B3, B4` for **B**. So for that example both sets contain commands with the same keys (or aliases) "1" and "2" (this could for example be "look" and "get" in the real game), whereas commands 3 and 4 are unique to **B**. To describe a merge between these sets, we would write `A1,A2 + B1,B2,B3,B4 = ?` where `?` is a list of commands that depend on which merge type **A** has, and which relative priorities the two sets have. By convention, we read this statement as "New command set **A** is merged onto the old command set **B** to form **?**".
In our examples, both sets contain a number of commands which we'll identify by numbers, like `A1,
A2` for set **A** and `B1, B2, B3, B4` for **B**. So for that example both sets contain commands
with the same keys (or aliases) "1" and "2" (this could for example be "look" and "get" in the real
game), whereas commands 3 and 4 are unique to **B**. To describe a merge between these sets, we
would write `A1,A2 + B1,B2,B3,B4 = ?` where `?` is a list of commands that depend on which merge
type **A** has, and which relative priorities the two sets have. By convention, we read this
statement as "New command set **A** is merged onto the old command set **B** to form **?**".
Below are the available merge types and how they work. Names are partly borrowed from [Set theory](http://en.wikipedia.org/wiki/Set_theory).
Below are the available merge types and how they work. Names are partly borrowed from [Set
theory](http://en.wikipedia.org/wiki/Set_theory).
- **Union** (default) - The two cmdsets are merged so that as many commands as possible from each cmdset ends up in the merged cmdset. Same-key commands are merged by priority.
- **Union** (default) - The two cmdsets are merged so that as many commands as possible from each
cmdset ends up in the merged cmdset. Same-key commands are merged by priority.
# Union
A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4
- **Intersect** - Only commands found in *both* cmdsets (i.e. which have the same keys) end up in the merged cmdset, with the higher-priority cmdset replacing the lower one's commands.
- **Intersect** - Only commands found in *both* cmdsets (i.e. which have the same keys) end up in
the merged cmdset, with the higher-priority cmdset replacing the lower one's commands.
# Intersect
A1,A3,A5 + B1,B2,B4,B5 = A1,A5
- **Replace** - The commands of the higher-prio cmdset completely replaces the lower-priority cmdset's commands, regardless of if same-key commands exist or not.
- **Replace** - The commands of the higher-prio cmdset completely replaces the lower-priority
cmdset's commands, regardless of if same-key commands exist or not.
# Replace
A1,A3 + B1,B2,B4,B5 = A1,A3
- **Remove** - The high-priority command sets removes same-key commands from the lower-priority cmdset. They are not replaced with anything, so this is a sort of filter that prunes the low-prio set using the high-prio one as a template.
- **Remove** - The high-priority command sets removes same-key commands from the lower-priority
cmdset. They are not replaced with anything, so this is a sort of filter that prunes the low-prio
set using the high-prio one as a template.
# Remove
A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5
Besides `priority` and `mergetype`, a command-set also takes a few other variables to control how they merge:
Besides `priority` and `mergetype`, a command-set also takes a few other variables to control how
they merge:
- `duplicates` (bool) - determines what happens when two sets of equal priority merge. Default is that the new set in the merger (i.e. **A** above) automatically takes precedence. But if *duplicates* is true, the result will be a merger with more than one of each name match. This will usually lead to the player receiving a multiple-match error higher up the road, but can be good for things like cmdsets on non-player objects in a room, to allow the system to warn that more than one 'ball' in the room has the same 'kick' command defined on it and offer a chance to select which ball to kick ... Allowing duplicates only makes sense for *Union* and *Intersect*, the setting is ignored for the other mergetypes.
- `key_mergetypes` (dict) - allows the cmdset to define a unique mergetype for particular cmdsets, identified by their cmdset `key`. Format is `{CmdSetkey:mergetype}`. Example: `{'Myevilcmdset','Replace'}` which would make sure for this set to always use 'Replace' on the cmdset with the key `Myevilcmdset` only, no matter what the main `mergetype` is set to.
- `duplicates` (bool) - determines what happens when two sets of equal priority merge. Default is
that the new set in the merger (i.e. **A** above) automatically takes precedence. But if
*duplicates* is true, the result will be a merger with more than one of each name match. This will
usually lead to the player receiving a multiple-match error higher up the road, but can be good for
things like cmdsets on non-player objects in a room, to allow the system to warn that more than one
'ball' in the room has the same 'kick' command defined on it and offer a chance to select which
ball to kick ... Allowing duplicates only makes sense for *Union* and *Intersect*, the setting is
ignored for the other mergetypes.
- `key_mergetypes` (dict) - allows the cmdset to define a unique mergetype for particular cmdsets,
identified by their cmdset `key`. Format is `{CmdSetkey:mergetype}`. Example:
`{'Myevilcmdset','Replace'}` which would make sure for this set to always use 'Replace' on the
cmdset with the key `Myevilcmdset` only, no matter what the main `mergetype` is set to.
> Warning: The `key_mergetypes` dictionary *can only work on the cmdset we merge onto*. When using `key_mergetypes` it is thus important to consider the merge priorities - you must make sure that you pick a priority *between* the cmdset you want to detect and the next higher one, if any. That is, if we define a cmdset with a high priority and set it to affect a cmdset that is far down in the merge stack, we would not "see" that set when it's time for us to merge. Example: Merge stack is `A(prio=-10), B(prio=-5), C(prio=0), D(prio=5)`. We now merge a cmdset `E(prio=10)` onto this stack, with a `key_mergetype={"B":"Replace"}`. But priorities dictate that we won't be merged onto B, we will be merged onto E (which is a merger of the lower-prio sets at this point). Since we are merging onto E and not B, our `key_mergetype` directive won't trigger. To make sure it works we must make sure we merge onto B. Setting E's priority to, say, -4 will make sure to merge it onto B and affect it appropriately.
> Warning: The `key_mergetypes` dictionary *can only work on the cmdset we merge onto*. When using
`key_mergetypes` it is thus important to consider the merge priorities - you must make sure that you
pick a priority *between* the cmdset you want to detect and the next higher one, if any. That is, if
we define a cmdset with a high priority and set it to affect a cmdset that is far down in the merge
stack, we would not "see" that set when it's time for us to merge. Example: Merge stack is
`A(prio=-10), B(prio=-5), C(prio=0), D(prio=5)`. We now merge a cmdset `E(prio=10)` onto this stack,
with a `key_mergetype={"B":"Replace"}`. But priorities dictate that we won't be merged onto B, we
will be merged onto E (which is a merger of the lower-prio sets at this point). Since we are merging
onto E and not B, our `key_mergetype` directive won't trigger. To make sure it works we must make
sure we merge onto B. Setting E's priority to, say, -4 will make sure to merge it onto B and affect
it appropriately.
More advanced cmdset example:
@ -207,11 +361,16 @@ class MyCmdSet(CmdSet):
### Assorted Notes
It is very important to remember that two commands are compared *both* by their `key` properties *and* by their `aliases` properties. If either keys or one of their aliases match, the two commands are considered the *same*. So consider these two Commands:
It is very important to remember that two commands are compared *both* by their `key` properties
*and* by their `aliases` properties. If either keys or one of their aliases match, the two commands
are considered the *same*. So consider these two Commands:
- A Command with key "kick" and alias "fight"
- A Command with key "punch" also with an alias "fight"
During the cmdset merging (which happens all the time since also things like channel commands and exits are merged in), these two commands will be considered *identical* since they share alias. It means only one of them will remain after the merger. Each will also be compared with all other commands having any combination of the keys and/or aliases "kick", "punch" or "fight".
During the cmdset merging (which happens all the time since also things like channel commands and
exits are merged in), these two commands will be considered *identical* since they share alias. It
means only one of them will remain after the merger. Each will also be compared with all other
commands having any combination of the keys and/or aliases "kick", "punch" or "fight".
... So avoid duplicate aliases, it will only cause confusion.

View file

@ -1,22 +1,43 @@
# Commands
Commands are intimately linked to [Command Sets](Command-Sets) and you need to read that page too to be familiar with how the command system works. The two pages were split for easy reading.
Commands are intimately linked to [Command Sets](Command-Sets) and you need to read that page too to
be familiar with how the command system works. The two pages were split for easy reading.
The basic way for users to communicate with the game is through *Commands*. These can be commands directly related to the game world such as *look*, *get*, *drop* and so on, or administrative commands such as *examine* or *@dig*.
The basic way for users to communicate with the game is through *Commands*. These can be commands
directly related to the game world such as *look*, *get*, *drop* and so on, or administrative
commands such as *examine* or *@dig*.
The [default commands](Default-Command-Help) coming with Evennia are 'MUX-like' in that they use @ for admin commands, support things like switches, syntax with the '=' symbol etc, but there is nothing that prevents you from implementing a completely different command scheme for your game. You can find the default commands in `evennia/commands/default`. You should not edit these directly - they will be updated by the Evennia team as new features are added. Rather you should look to them for inspiration and inherit your own designs from them.
The [default commands](Default-Command-Help) coming with Evennia are 'MUX-like' in that they use @
for admin commands, support things like switches, syntax with the '=' symbol etc, but there is
nothing that prevents you from implementing a completely different command scheme for your game. You
can find the default commands in `evennia/commands/default`. You should not edit these directly -
they will be updated by the Evennia team as new features are added. Rather you should look to them
for inspiration and inherit your own designs from them.
There are two components to having a command running - the *Command* class and the [Command Set](Command-Sets) (command sets were split into a separate wiki page for ease of reading).
There are two components to having a command running - the *Command* class and the [Command
Set](Command-Sets) (command sets were split into a separate wiki page for ease of reading).
1. A *Command* is a python class containing all the functioning code for what a command does - for example, a *get* command would contain code for picking up objects.
1. A *Command Set* (often referred to as a CmdSet or cmdset) is like a container for one or more Commands. A given Command can go into any number of different command sets. Only by putting the command set on a character object you will make all the commands therein available to use by that character. You can also store command sets on normal objects if you want users to be able to use the object in various ways. Consider a "Tree" object with a cmdset defining the commands *climb* and *chop down*. Or a "Clock" with a cmdset containing the single command *check time*.
1. A *Command* is a python class containing all the functioning code for what a command does - for
example, a *get* command would contain code for picking up objects.
1. A *Command Set* (often referred to as a CmdSet or cmdset) is like a container for one or more
Commands. A given Command can go into any number of different command sets. Only by putting the
command set on a character object you will make all the commands therein available to use by that
character. You can also store command sets on normal objects if you want users to be able to use the
object in various ways. Consider a "Tree" object with a cmdset defining the commands *climb* and
*chop down*. Or a "Clock" with a cmdset containing the single command *check time*.
This page goes into full detail about how to use Commands. To fully use them you must also read the page detailing [Command Sets](Command-Sets). There is also a step-by-step [Adding Command Tutorial](Adding-Command-Tutorial) that will get you started quickly without the extra explanations.
This page goes into full detail about how to use Commands. To fully use them you must also read the
page detailing [Command Sets](Command-Sets). There is also a step-by-step [Adding Command
Tutorial](Adding-Command-Tutorial) that will get you started quickly without the extra explanations.
## Defining Commands
All commands are implemented as normal Python classes inheriting from the base class `Command` (`evennia.Command`). You will find that this base class is very "bare". The default commands of Evennia actually inherit from a child of `Command` called `MuxCommand` - this is the class that knows all the mux-like syntax like `/switches`, splitting by "=" etc. Below we'll avoid mux-specifics and use the base `Command` class directly.
All commands are implemented as normal Python classes inheriting from the base class `Command`
(`evennia.Command`). You will find that this base class is very "bare". The default commands of
Evennia actually inherit from a child of `Command` called `MuxCommand` - this is the class that
knows all the mux-like syntax like `/switches`, splitting by "=" etc. Below we'll avoid mux-
specifics and use the base `Command` class directly.
```python
# basic Command definition
@ -47,78 +68,158 @@ Here is a minimalistic command with no custom parsing:
```
You define a new command by assigning a few class-global properties on your inherited class and overloading one or two hook functions. The full gritty mechanic behind how commands work are found towards the end of this page; for now you only need to know that the command handler creates an instance of this class and uses that instance whenever you use this command - it also dynamically assigns the new command instance a few useful properties that you can assume to always be available.
You define a new command by assigning a few class-global properties on your inherited class and
overloading one or two hook functions. The full gritty mechanic behind how commands work are found
towards the end of this page; for now you only need to know that the command handler creates an
instance of this class and uses that instance whenever you use this command - it also dynamically
assigns the new command instance a few useful properties that you can assume to always be available.
### Who is calling the command?
In Evennia there are three types of objects that may call the command. It is important to be aware of this since this will also assign appropriate `caller`, `session`, `sessid` and `account` properties on the command body at runtime. Most often the calling type is `Session`.
In Evennia there are three types of objects that may call the command. It is important to be aware
of this since this will also assign appropriate `caller`, `session`, `sessid` and `account`
properties on the command body at runtime. Most often the calling type is `Session`.
* A [Session](Sessions). This is by far the most common case when a user is entering a command in their client.
* `caller` - this is set to the puppeted [Object](Objects) if such an object exists. If no puppet is found, `caller` is set equal to `account`. Only if an Account is not found either (such as before being logged in) will this be set to the Session object itself.
* A [Session](Sessions). This is by far the most common case when a user is entering a command in
their client.
* `caller` - this is set to the puppeted [Object](Objects) if such an object exists. If no
puppet is found, `caller` is set equal to `account`. Only if an Account is not found either (such as
before being logged in) will this be set to the Session object itself.
* `session` - a reference to the [Session](Sessions) object itself.
* `sessid` - `sessid.id`, a unique integer identifier of the session.
* `account` - the [Account](Accounts) object connected to this Session. None if not logged in.
* An [Account](Accounts). This only happens if `account.execute_cmd()` was used. No Session information can be obtained in this case.
* `caller` - this is set to the puppeted Object if such an object can be determined (without Session info this can only be determined in `MULTISESSION_MODE=0` or `1`). If no puppet is found, this is equal to `account`.
* An [Account](Accounts). This only happens if `account.execute_cmd()` was used. No Session
information can be obtained in this case.
* `caller` - this is set to the puppeted Object if such an object can be determined (without
Session info this can only be determined in `MULTISESSION_MODE=0` or `1`). If no puppet is found,
this is equal to `account`.
* `session` - `None*`
* `sessid` - `None*`
* `account` - Set to the Account object.
* An [Object](Objects). This only happens if `object.execute_cmd()` was used (for example by an NPC).
* An [Object](Objects). This only happens if `object.execute_cmd()` was used (for example by an
NPC).
* `caller` - This is set to the calling Object in question.
* `session` - `None*`
* `sessid` - `None*`
* `account` - `None`
> `*)`: There is a way to make the Session available also inside tests run directly on Accounts and Objects, and that is to pass it to `execute_cmd` like so: `account.execute_cmd("...", session=<Session>)`. Doing so *will* make the `.session` and `.sessid` properties available in the command.
> `*)`: There is a way to make the Session available also inside tests run directly on Accounts and
Objects, and that is to pass it to `execute_cmd` like so: `account.execute_cmd("...",
session=<Session>)`. Doing so *will* make the `.session` and `.sessid` properties available in the
command.
### Properties assigned to the command instance at run-time
Let's say account *Bob* with a character *BigGuy* enters the command *look at sword*. After the system having successfully identified this as the "look" command and determined that BigGuy really has access to a command named `look`, it chugs the `look` command class out of storage and either loads an existing Command instance from cache or creates one. After some more checks it then assigns it the following properties:
Let's say account *Bob* with a character *BigGuy* enters the command *look at sword*. After the
system having successfully identified this as the "look" command and determined that BigGuy really
has access to a command named `look`, it chugs the `look` command class out of storage and either
loads an existing Command instance from cache or creates one. After some more checks it then assigns
it the following properties:
- `caller` - The character BigGuy, in this example. This is a reference to the object executing the command. The value of this depends on what type of object is calling the command; see the previous section.
- `session` - the [Session](Sessions) Bob uses to connect to the game and control BigGuy (see also previous section).
- `caller` - The character BigGuy, in this example. This is a reference to the object executing the
command. The value of this depends on what type of object is calling the command; see the previous
section.
- `session` - the [Session](Sessions) Bob uses to connect to the game and control BigGuy (see also
previous section).
- `sessid` - the unique id of `self.session`, for quick lookup.
- `account` - the [Account](Accounts) Bob (see previous section).
- `cmdstring` - the matched key for the command. This would be *look* in our example.
- `args` - this is the rest of the string, except the command name. So if the string entered was *look at sword*, `args` would be " *at sword*". Note the space kept - Evennia would correctly interpret `lookat sword` too. This is useful for things like `/switches` that should not use space. In the `MuxCommand` class used for default commands, this space is stripped. Also see the `arg_regex` property if you want to enforce a space to make `lookat sword` give a command-not-found error.
- `obj` - the game [Object](Objects) on which this command is defined. This need not be the caller, but since `look` is a common (default) command, this is probably defined directly on *BigGuy* - so `obj` will point to BigGuy. Otherwise `obj` could be an Account or any interactive object with commands defined on it, like in the example of the "check time" command defined on a "Clock" object.
- `cmdset` - this is a reference to the merged CmdSet (see below) from which this command was matched. This variable is rarely used, it's main use is for the [auto-help system](Help-System#command-auto-help-system) (*Advanced note: the merged cmdset need NOT be the same as `BigGuy.cmdset`. The merged set can be a combination of the cmdsets from other objects in the room, for example*).
- `raw_string` - this is the raw input coming from the user, without stripping any surrounding whitespace. The only thing that is stripped is the ending newline marker.
- `args` - this is the rest of the string, except the command name. So if the string entered was
*look at sword*, `args` would be " *at sword*". Note the space kept - Evennia would correctly
interpret `lookat sword` too. This is useful for things like `/switches` that should not use space.
In the `MuxCommand` class used for default commands, this space is stripped. Also see the
`arg_regex` property if you want to enforce a space to make `lookat sword` give a command-not-found
error.
- `obj` - the game [Object](Objects) on which this command is defined. This need not be the caller,
but since `look` is a common (default) command, this is probably defined directly on *BigGuy* - so
`obj` will point to BigGuy. Otherwise `obj` could be an Account or any interactive object with
commands defined on it, like in the example of the "check time" command defined on a "Clock" object.
- `cmdset` - this is a reference to the merged CmdSet (see below) from which this command was
matched. This variable is rarely used, it's main use is for the [auto-help system](Help-
System#command-auto-help-system) (*Advanced note: the merged cmdset need NOT be the same as
`BigGuy.cmdset`. The merged set can be a combination of the cmdsets from other objects in the room,
for example*).
- `raw_string` - this is the raw input coming from the user, without stripping any surrounding
whitespace. The only thing that is stripped is the ending newline marker.
#### Other useful utility methods:
- `.get_help(caller, cmdset)` - Get the help entry for this command. By default the arguments are not
- `.get_help(caller, cmdset)` - Get the help entry for this command. By default the arguments are
not
used, but they could be used to implement alternate help-display systems.
- `.client_width()` - Shortcut for getting the client's screen-width. Note that not all clients will
- `.client_width()` - Shortcut for getting the client's screen-width. Note that not all clients will
truthfully report this value - that case the `settings.DEFAULT_SCREEN_WIDTH` will be returned.
- `.styled_table(*args, **kwargs)` - This returns an [EvTable](api:evennia.utils#module-evennia.utils.evtable) styled based on the
session calling this command. The args/kwargs are the same as for EvTable, except styling defaults are set.
- `.styled_table(*args, **kwargs)` - This returns an [EvTable](api:evennia.utils#module-
evennia.utils.evtable) styled based on the
session calling this command. The args/kwargs are the same as for EvTable, except styling defaults
are set.
- `.styled_header`, `_footer`, `separator` - These will produce styled decorations for
display to the user. They are useful for creating listings and forms with colors adjustable per-user.
display to the user. They are useful for creating listings and forms with colors adjustable per-
user.
### Defining your own command classes
Beyond the properties Evennia always assigns to the command at run-time (listed above), your job is to define the following class properties:
Beyond the properties Evennia always assigns to the command at run-time (listed above), your job is
to define the following class properties:
- `key` (string) - the identifier for the command, like `look`. This should (ideally) be unique. A key can consist of more than one word, like "press button" or "pull left lever". Note that *both* `key` and `aliases` below determine the identity of a command. So two commands are considered if either matches. This is important for merging cmdsets described below.
- `aliases` (optional list) - a list of alternate names for the command (`["glance", "see", "l"]`). Same name rules as for `key` applies.
- `locks` (string) - a [lock definition](Locks), usually on the form `cmd:<lockfuncs>`. Locks is a rather big topic, so until you learn more about locks, stick to giving the lockstring `"cmd:all()"` to make the command available to everyone (if you don't provide a lock string, this will be assigned for you).
- `help_category` (optional string) - setting this helps to structure the auto-help into categories. If none is set, this will be set to *General*.
- `save_for_next` (optional boolean). This defaults to `False`. If `True`, a copy of this command object (along with any changes you have done to it) will be stored by the system and can be accessed by the next command by retrieving `self.caller.ndb.last_cmd`. The next run command will either clear or replace the storage.
- `arg_regex` (optional raw string): Used to force the parser to limit itself and tell it when the command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is done with a regular expression. [See the arg_regex section](Commands#on-arg_regex) for the details.
- `auto_help` (optional boolean). Defaults to `True`. This allows for turning off the [auto-help system](Help-System#command-auto-help-system) on a per-command basis. This could be useful if you either want to write your help entries manually or hide the existence of a command from `help`'s generated list.
- `is_exit` (bool) - this marks the command as being used for an in-game exit. This is, by default, set by all Exit objects and you should not need to set it manually unless you make your own Exit system. It is used for optimization and allows the cmdhandler to easily disregard this command when the cmdset has its `no_exits` flag set.
- `is_channel` (bool)- this marks the command as being used for an in-game channel. This is, by default, set by all Channel objects and you should not need to set it manually unless you make your own Channel system. is used for optimization and allows the cmdhandler to easily disregard this command when its cmdset has its `no_channels` flag set.
- `msg_all_sessions` (bool): This affects the behavior of the `Command.msg` method. If unset (default), calling `self.msg(text)` from the Command will always only send text to the Session that actually triggered this Command. If set however, `self.msg(text)` will send to all Sessions relevant to the object this Command sits on. Just which Sessions receives the text depends on the object and the server's `MULTISESSION_MODE`.
- `key` (string) - the identifier for the command, like `look`. This should (ideally) be unique. A
key can consist of more than one word, like "press button" or "pull left lever". Note that *both*
`key` and `aliases` below determine the identity of a command. So two commands are considered if
either matches. This is important for merging cmdsets described below.
- `aliases` (optional list) - a list of alternate names for the command (`["glance", "see", "l"]`).
Same name rules as for `key` applies.
- `locks` (string) - a [lock definition](Locks), usually on the form `cmd:<lockfuncs>`. Locks is a
rather big topic, so until you learn more about locks, stick to giving the lockstring `"cmd:all()"`
to make the command available to everyone (if you don't provide a lock string, this will be assigned
for you).
- `help_category` (optional string) - setting this helps to structure the auto-help into categories.
If none is set, this will be set to *General*.
- `save_for_next` (optional boolean). This defaults to `False`. If `True`, a copy of this command
object (along with any changes you have done to it) will be stored by the system and can be accessed
by the next command by retrieving `self.caller.ndb.last_cmd`. The next run command will either clear
or replace the storage.
- `arg_regex` (optional raw string): Used to force the parser to limit itself and tell it when the
command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is
done with a regular expression. [See the arg_regex section](Commands#on-arg_regex) for the details.
- `auto_help` (optional boolean). Defaults to `True`. This allows for turning off the [auto-help
system](Help-System#command-auto-help-system) on a per-command basis. This could be useful if you
either want to write your help entries manually or hide the existence of a command from `help`'s
generated list.
- `is_exit` (bool) - this marks the command as being used for an in-game exit. This is, by default,
set by all Exit objects and you should not need to set it manually unless you make your own Exit
system. It is used for optimization and allows the cmdhandler to easily disregard this command when
the cmdset has its `no_exits` flag set.
- `is_channel` (bool)- this marks the command as being used for an in-game channel. This is, by
default, set by all Channel objects and you should not need to set it manually unless you make your
own Channel system. is used for optimization and allows the cmdhandler to easily disregard this
command when its cmdset has its `no_channels` flag set.
- `msg_all_sessions` (bool): This affects the behavior of the `Command.msg` method. If unset
(default), calling `self.msg(text)` from the Command will always only send text to the Session that
actually triggered this Command. If set however, `self.msg(text)` will send to all Sessions relevant
to the object this Command sits on. Just which Sessions receives the text depends on the object and
the server's `MULTISESSION_MODE`.
You should also implement at least two methods, `parse()` and `func()` (You could also implement `perm()`, but that's not needed unless you want to fundamentally change how access checks work).
You should also implement at least two methods, `parse()` and `func()` (You could also implement
`perm()`, but that's not needed unless you want to fundamentally change how access checks work).
- `at_pre_cmd()` is called very first on the command. If this function returns anything that evaluates to `True` the command execution is aborted at this point.
- `parse()` is intended to parse the arguments (`self.args`) of the function. You can do this in any way you like, then store the result(s) in variable(s) on the command object itself (i.e. on `self`). To take an example, the default mux-like system uses this method to detect "command switches" and store them as a list in `self.switches`. Since the parsing is usually quite similar inside a command scheme you should make `parse()` as generic as possible and then inherit from it rather than re-implementing it over and over. In this way, the default `MuxCommand` class implements a `parse()` for all child commands to use.
- `func()` is called right after `parse()` and should make use of the pre-parsed input to actually do whatever the command is supposed to do. This is the main body of the command. The return value from this method will be returned from the execution as a Twisted Deferred.
- `at_pre_cmd()` is called very first on the command. If this function returns anything that
evaluates to `True` the command execution is aborted at this point.
- `parse()` is intended to parse the arguments (`self.args`) of the function. You can do this in any
way you like, then store the result(s) in variable(s) on the command object itself (i.e. on `self`).
To take an example, the default mux-like system uses this method to detect "command switches" and
store them as a list in `self.switches`. Since the parsing is usually quite similar inside a command
scheme you should make `parse()` as generic as possible and then inherit from it rather than re-
implementing it over and over. In this way, the default `MuxCommand` class implements a `parse()`
for all child commands to use.
- `func()` is called right after `parse()` and should make use of the pre-parsed input to actually
do whatever the command is supposed to do. This is the main body of the command. The return value
from this method will be returned from the execution as a Twisted Deferred.
- `at_post_cmd()` is called after `func()` to handle eventual cleanup.
Finally, you should always make an informative [doc string](http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring) (`__doc__`) at the top of your class. This string is dynamically read by the [Help System](Help-System) to create the help entry for this command. You should decide on a way to format your help and stick to that.
Finally, you should always make an informative [doc
string](http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring) (`__doc__`) at the top of your
class. This string is dynamically read by the [Help System](Help-System) to create the help entry
for this command. You should decide on a way to format your help and stick to that.
Below is how you define a simple alternative "`smile`" command:
@ -179,19 +280,39 @@ within a *command set*. See the [Command Sets](Command-Sets) page.
### On arg_regex
The command parser is very general and does not require a space to end your command name. This means that the alias `:` to `emote` can be used like `:smiles` without modification. It also means `getstone` will get you the stone (unless there is a command specifically named `getstone`, then that will be used). If you want to tell the parser to require a certain separator between the command name and its arguments (so that `get stone` works but `getstone` gives you a 'command not found' error) you can do so with the `arg_regex` property.
The command parser is very general and does not require a space to end your command name. This means
that the alias `:` to `emote` can be used like `:smiles` without modification. It also means
`getstone` will get you the stone (unless there is a command specifically named `getstone`, then
that will be used). If you want to tell the parser to require a certain separator between the
command name and its arguments (so that `get stone` works but `getstone` gives you a 'command not
found' error) you can do so with the `arg_regex` property.
The `arg_regex` is a [raw regular expression string](http://docs.python.org/library/re.html). The regex will be compiled by the system at runtime. This allows you to customize how the part *immediately following* the command name (or alias) must look in order for the parser to match for this command. Some examples:
The `arg_regex` is a [raw regular expression string](http://docs.python.org/library/re.html). The
regex will be compiled by the system at runtime. This allows you to customize how the part
*immediately following* the command name (or alias) must look in order for the parser to match for
this command. Some examples:
- `commandname argument` (`arg_regex = r"\s.+"`): This forces the parser to require the command name to be followed by one or more spaces. Whatever is entered after the space will be treated as an argument. However, if you'd forget the space (like a command having no arguments), this would *not* match `commandname`.
- `commandname` or `commandname argument` (`arg_regex = r"\s.+|$"`): This makes both `look` and `look me` work but `lookme` will not.
- `commandname/switches arguments` (`arg_regex = r"(?:^(?:\s+|\/).*$)|^$"`. If you are using Evennia's `MuxCommand` Command parent, you may wish to use this since it will allow `/switche`s to work as well as having or not having a space.
- `commandname argument` (`arg_regex = r"\s.+"`): This forces the parser to require the command name
to be followed by one or more spaces. Whatever is entered after the space will be treated as an
argument. However, if you'd forget the space (like a command having no arguments), this would *not*
match `commandname`.
- `commandname` or `commandname argument` (`arg_regex = r"\s.+|$"`): This makes both `look` and
`look me` work but `lookme` will not.
- `commandname/switches arguments` (`arg_regex = r"(?:^(?:\s+|\/).*$)|^$"`. If you are using
Evennia's `MuxCommand` Command parent, you may wish to use this since it will allow `/switche`s to
work as well as having or not having a space.
The `arg_regex` allows you to customize the behavior of your commands. You can put it in the parent class of your command to customize all children of your Commands. However, you can also change the base default behavior for all Commands by modifying `settings.COMMAND_DEFAULT_ARG_REGEX`.
The `arg_regex` allows you to customize the behavior of your commands. You can put it in the parent
class of your command to customize all children of your Commands. However, you can also change the
base default behavior for all Commands by modifying `settings.COMMAND_DEFAULT_ARG_REGEX`.
## Exiting a command
Normally you just use `return` in one of your Command class' hook methods to exit that method. That will however still fire the other hook methods of the Command in sequence. That's usually what you want but sometimes it may be useful to just abort the command, for example if you find some unacceptable input in your parse method. To exit the command this way you can raise `evennia.InterruptCommand`:
Normally you just use `return` in one of your Command class' hook methods to exit that method. That
will however still fire the other hook methods of the Command in sequence. That's usually what you
want but sometimes it may be useful to just abort the command, for example if you find some
unacceptable input in your parse method. To exit the command this way you can raise
`evennia.InterruptCommand`:
```python
from evennia import InterruptCommand
@ -210,11 +331,19 @@ class MyCommand(Command):
## Pauses in commands
Sometimes you want to pause the execution of your command for a little while before continuing - maybe you want to simulate a heavy swing taking some time to finish, maybe you want the echo of your voice to return to you with an ever-longer delay. Since Evennia is running asynchronously, you cannot use `time.sleep()` in your commands (or anywhere, really). If you do, the *entire game* will be frozen for everyone! So don't do that. Fortunately, Evennia offers a really quick syntax for making pauses in commands.
Sometimes you want to pause the execution of your command for a little while before continuing -
maybe you want to simulate a heavy swing taking some time to finish, maybe you want the echo of your
voice to return to you with an ever-longer delay. Since Evennia is running asynchronously, you
cannot use `time.sleep()` in your commands (or anywhere, really). If you do, the *entire game* will
be frozen for everyone! So don't do that. Fortunately, Evennia offers a really quick syntax for
making pauses in commands.
In your `func()` method, you can use the `yield` keyword. This is a Python keyword that will freeze the current execution of your command and wait for more before processing.
In your `func()` method, you can use the `yield` keyword. This is a Python keyword that will freeze
the current execution of your command and wait for more before processing.
> Note that you *cannot* just drop `yield` into any code and expect it to pause. Evennia will only pause for you if you `yield` inside the Command's `func()` method. Don't expect it to work anywhere else.
> Note that you *cannot* just drop `yield` into any code and expect it to pause. Evennia will only
pause for you if you `yield` inside the Command's `func()` method. Don't expect it to work anywhere
else.
Here's an example of a command using a small pause of five seconds between messages:
@ -243,12 +372,20 @@ class CmdWait(Command):
self.msg("... And now another 2 seconds have passed.")
```
The important line is the `yield 5` and `yield 2` lines. It will tell Evennia to pause execution here and not continue until the number of seconds given has passed.
The important line is the `yield 5` and `yield 2` lines. It will tell Evennia to pause execution
here and not continue until the number of seconds given has passed.
There are two things to remember when using `yield` in your Command's `func` method:
1. The paused state produced by the `yield` is not saved anywhere. So if the server reloads in the middle of your command pausing, it will *not* resume when the server comes back up - the remainder of the command will never fire. So be careful that you are not freezing the character or account in a way that will not be cleared on reload.
2. If you use `yield` you may not also use `return <values>` in your `func` method. You'll get an error explaining this. This is due to how Python generators work. You can however use a "naked" `return` just fine. Usually there is no need for `func` to return a value, but if you ever do need to mix `yield` with a final return value in the same `func`, look at [twisted.internet.defer.returnValue](https://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#returnValue).
1. The paused state produced by the `yield` is not saved anywhere. So if the server reloads in the
middle of your command pausing, it will *not* resume when the server comes back up - the remainder
of the command will never fire. So be careful that you are not freezing the character or account in
a way that will not be cleared on reload.
2. If you use `yield` you may not also use `return <values>` in your `func` method. You'll get an
error explaining this. This is due to how Python generators work. You can however use a "naked"
`return` just fine. Usually there is no need for `func` to return a value, but if you ever do need
to mix `yield` with a final return value in the same `func`, look at
[twisted.internet.defer.returnValue](https://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#returnValue).
## Asking for user input
@ -284,28 +421,52 @@ class CmdConfirm(Command):
self.msg("No!")
```
This time, when the user enters the 'confirm' command, she will be asked if she wants to go on. Entering 'yes' or "y" (regardless of case) will give the first reply, otherwise the second reply will show.
This time, when the user enters the 'confirm' command, she will be asked if she wants to go on.
Entering 'yes' or "y" (regardless of case) will give the first reply, otherwise the second reply
will show.
> Note again that the `yield` keyword does not store state. If the game reloads while waiting for the user to answer, the user will have to start over. It is not a good idea to use `yield` for important or complex choices, a persistent [EvMenu](EvMenu) might be more appropriate in this case.
> Note again that the `yield` keyword does not store state. If the game reloads while waiting for
the user to answer, the user will have to start over. It is not a good idea to use `yield` for
important or complex choices, a persistent [EvMenu](EvMenu) might be more appropriate in this case.
## System commands
*Note: This is an advanced topic. Skip it if this is your first time learning about commands.*
There are several command-situations that are exceptional in the eyes of the server. What happens if the account enters an empty string? What if the 'command' given is infact the name of a channel the user wants to send a message to? Or if there are multiple command possibilities?
There are several command-situations that are exceptional in the eyes of the server. What happens if
the account enters an empty string? What if the 'command' given is infact the name of a channel the
user wants to send a message to? Or if there are multiple command possibilities?
Such 'special cases' are handled by what's called *system commands*. A system command is defined in the same way as other commands, except that their name (key) must be set to one reserved by the engine (the names are defined at the top of `evennia/commands/cmdhandler.py`). You can find (unused) implementations of the system commands in `evennia/commands/default/system_commands.py`. Since these are not (by default) included in any `CmdSet` they are not actually used, they are just there for show. When the special situation occurs, Evennia will look through all valid `CmdSet`s for your custom system command. Only after that will it resort to its own, hard-coded implementation.
Such 'special cases' are handled by what's called *system commands*. A system command is defined
in the same way as other commands, except that their name (key) must be set to one reserved by the
engine (the names are defined at the top of `evennia/commands/cmdhandler.py`). You can find (unused)
implementations of the system commands in `evennia/commands/default/system_commands.py`. Since these
are not (by default) included in any `CmdSet` they are not actually used, they are just there for
show. When the special situation occurs, Evennia will look through all valid `CmdSet`s for your
custom system command. Only after that will it resort to its own, hard-coded implementation.
Here are the exceptional situations that triggers system commands. You can find the command keys they use as properties on `evennia.syscmdkeys`:
Here are the exceptional situations that triggers system commands. You can find the command keys
they use as properties on `evennia.syscmdkeys`:
- No input (`syscmdkeys.CMD_NOINPUT`) - the account just pressed return without any input. Default is to do nothing, but it can be useful to do something here for certain implementations such as line editors that interpret non-commands as text input (an empty line in the editing buffer).
- Command not found (`syscmdkeys.CMD_NOMATCH`) - No matching command was found. Default is to display the "Huh?" error message.
- Several matching commands where found (`syscmdkeys.CMD_MULTIMATCH`) - Default is to show a list of matches.
- User is not allowed to execute the command (`syscmdkeys.CMD_NOPERM`) - Default is to display the "Huh?" error message.
- Channel (`syscmdkeys.CMD_CHANNEL`) - This is a [Channel](Communications) name of a channel you are subscribing to - Default is to relay the command's argument to that channel. Such commands are created by the Comm system on the fly depending on your subscriptions.
- New session connection (`syscmdkeys.CMD_LOGINSTART`). This command name should be put in the `settings.CMDSET_UNLOGGEDIN`. Whenever a new connection is established, this command is always called on the server (default is to show the login screen).
- No input (`syscmdkeys.CMD_NOINPUT`) - the account just pressed return without any input. Default
is to do nothing, but it can be useful to do something here for certain implementations such as line
editors that interpret non-commands as text input (an empty line in the editing buffer).
- Command not found (`syscmdkeys.CMD_NOMATCH`) - No matching command was found. Default is to
display the "Huh?" error message.
- Several matching commands where found (`syscmdkeys.CMD_MULTIMATCH`) - Default is to show a list of
matches.
- User is not allowed to execute the command (`syscmdkeys.CMD_NOPERM`) - Default is to display the
"Huh?" error message.
- Channel (`syscmdkeys.CMD_CHANNEL`) - This is a [Channel](Communications) name of a channel you are
subscribing to - Default is to relay the command's argument to that channel. Such commands are
created by the Comm system on the fly depending on your subscriptions.
- New session connection (`syscmdkeys.CMD_LOGINSTART`). This command name should be put in the
`settings.CMDSET_UNLOGGEDIN`. Whenever a new connection is established, this command is always
called on the server (default is to show the login screen).
Below is an example of redefining what happens when the account doesn't provide any input (e.g. just presses return). Of course the new system command must be added to a cmdset as well before it will work.
Below is an example of redefining what happens when the account doesn't provide any input (e.g. just
presses return). Of course the new system command must be added to a cmdset as well before it will
work.
```python
from evennia import syscmdkeys, Command
@ -321,9 +482,13 @@ Below is an example of redefining what happens when the account doesn't provide
*Note: This is an advanced topic.*
Normally Commands are created as fixed classes and used without modification. There are however situations when the exact key, alias or other properties is not possible (or impractical) to pre-code ([Exits](Commands#Exits) is an example of this).
Normally Commands are created as fixed classes and used without modification. There are however
situations when the exact key, alias or other properties is not possible (or impractical) to pre-
code ([Exits](Commands#Exits) is an example of this).
To create a command with a dynamic call signature, first define the command body normally in a class (set your `key`, `aliases` to default values), then use the following call (assuming the command class you created is named `MyCommand`):
To create a command with a dynamic call signature, first define the command body normally in a class
(set your `key`, `aliases` to default values), then use the following call (assuming the command
class you created is named `MyCommand`):
```python
cmd = MyCommand(key="newname",
@ -332,9 +497,12 @@ To create a command with a dynamic call signature, first define the command body
...)
```
*All* keyword arguments you give to the Command constructor will be stored as a property on the command object. This will overload existing properties defined on the parent class.
*All* keyword arguments you give to the Command constructor will be stored as a property on the
command object. This will overload existing properties defined on the parent class.
Normally you would define your class and only overload things like `key` and `aliases` at run-time. But you could in principle also send method objects (like `func`) as keyword arguments in order to make your command completely customized at run-time.
Normally you would define your class and only overload things like `key` and `aliases` at run-time.
But you could in principle also send method objects (like `func`) as keyword arguments in order to
make your command completely customized at run-time.
## Exits
@ -342,14 +510,28 @@ Normally you would define your class and only overload things like `key` and `al
Exits are examples of the use of a [Dynamic Command](Commands#Dynamic_Commands).
The functionality of [Exit](Objects) objects in Evennia is not hard-coded in the engine. Instead Exits are normal [typeclassed](Typeclasses) objects that auto-create a [CmdSet](Commands#CmdSets) on themselves when they load. This cmdset has a single dynamically created Command with the same properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit, this dynamic exit-command is triggered and (after access checks) moves the Character to the exit's destination.
Whereas you could customize the Exit object and its command to achieve completely different behaviour, you will usually be fine just using the appropriate `traverse_*` hooks on the Exit object. But if you are interested in really changing how things work under the hood, check out `evennia/objects/objects.py` for how the `Exit` typeclass is set up.
The functionality of [Exit](Objects) objects in Evennia is not hard-coded in the engine. Instead
Exits are normal [typeclassed](Typeclasses) objects that auto-create a [CmdSet](Commands#CmdSets) on
themselves when they load. This cmdset has a single dynamically created Command with the same
properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit,
this dynamic exit-command is triggered and (after access checks) moves the Character to the exit's
destination.
Whereas you could customize the Exit object and its command to achieve completely different
behaviour, you will usually be fine just using the appropriate `traverse_*` hooks on the Exit
object. But if you are interested in really changing how things work under the hood, check out
`evennia/objects/objects.py` for how the `Exit` typeclass is set up.
## Command instances are re-used
*Note: This is an advanced topic that can be skipped when first learning about Commands.*
A Command class sitting on an object is instantiated once and then re-used. So if you run a command from object1 over and over you are in fact running the same command instance over and over (if you run the same command but sitting on object2 however, it will be a different instance). This is usually not something you'll notice, since every time the Command-instance is used, all the relevant properties on it will be overwritten. But armed with this knowledge you can implement some of the more exotic command mechanism out there, like the command having a 'memory' of what you last entered so that you can back-reference the previous arguments etc.
A Command class sitting on an object is instantiated once and then re-used. So if you run a command
from object1 over and over you are in fact running the same command instance over and over (if you
run the same command but sitting on object2 however, it will be a different instance). This is
usually not something you'll notice, since every time the Command-instance is used, all the relevant
properties on it will be overwritten. But armed with this knowledge you can implement some of the
more exotic command mechanism out there, like the command having a 'memory' of what you last entered
so that you can back-reference the previous arguments etc.
> Note: On a server reload, all Commands are rebuilt and memory is flushed.
@ -386,7 +568,8 @@ Note how the in-memory address of the `testid` command never changes, but `xval`
*This is also an advanced topic.*
Commands can also be created and added to a cmdset on the fly. Creating a class instance with a keyword argument, will assign that keyword argument as a property on this paricular command:
Commands can also be created and added to a cmdset on the fly. Creating a class instance with a
keyword argument, will assign that keyword argument as a property on this paricular command:
```
class MyCmdSet(CmdSet):
@ -397,39 +580,64 @@ class MyCmdSet(CmdSet):
```
This will start the `MyCommand` with `myvar` and `foo` set as properties (accessable as `self.myvar` and `self.foo`). How they are used is up to the Command. Remember however the discussion from the previous section - since the Command instance is re-used, those properties will *remain* on the command as long as this cmdset and the object it sits is in memory (i.e. until the next reload). Unless `myvar` and `foo` are somehow reset when the command runs, they can be modified and that change will be remembered for subsequent uses of the command.
This will start the `MyCommand` with `myvar` and `foo` set as properties (accessable as `self.myvar`
and `self.foo`). How they are used is up to the Command. Remember however the discussion from the
previous section - since the Command instance is re-used, those properties will *remain* on the
command as long as this cmdset and the object it sits is in memory (i.e. until the next reload).
Unless `myvar` and `foo` are somehow reset when the command runs, they can be modified and that
change will be remembered for subsequent uses of the command.
## How commands actually work
*Note: This is an advanced topic mainly of interest to server developers.*
Any time the user sends text to Evennia, the server tries to figure out if the text entered corresponds to a known command. This is how the command handler sequence looks for a logged-in user:
Any time the user sends text to Evennia, the server tries to figure out if the text entered
corresponds to a known command. This is how the command handler sequence looks for a logged-in user:
1. A user enters a string of text and presses enter.
2. The user's Session determines the text is not some protocol-specific control sequence or OOB command, but sends it on to the command handler.
3. Evennia's *command handler* analyzes the Session and grabs eventual references to Account and eventual puppeted Characters (these will be stored on the command object later). The *caller* property is set appropriately.
4. If input is an empty string, resend command as `CMD_NOINPUT`. If no such command is found in cmdset, ignore.
2. The user's Session determines the text is not some protocol-specific control sequence or OOB
command, but sends it on to the command handler.
3. Evennia's *command handler* analyzes the Session and grabs eventual references to Account and
eventual puppeted Characters (these will be stored on the command object later). The *caller*
property is set appropriately.
4. If input is an empty string, resend command as `CMD_NOINPUT`. If no such command is found in
cmdset, ignore.
5. If command.key matches `settings.IDLE_COMMAND`, update timers but don't do anything more.
6. The command handler gathers the CmdSets available to *caller* at this time:
- The caller's own currently active CmdSet.
- CmdSets defined on the current account, if caller is a puppeted object.
- CmdSets defined on the Session itself.
- The active CmdSets of eventual objects in the same location (if any). This includes commands on [Exits](Objects#Exits).
- Sets of dynamically created *System commands* representing available [Communications](Communications#Channels).
7. All CmdSets *of the same priority* are merged together in groups. Grouping avoids order-dependent issues of merging multiple same-prio sets onto lower ones.
8. All the grouped CmdSets are *merged* in reverse priority into one combined CmdSet according to each set's merge rules.
9. Evennia's *command parser* takes the merged cmdset and matches each of its commands (using its key and aliases) against the beginning of the string entered by *caller*. This produces a set of candidates.
10. The *cmd parser* next rates the matches by how many characters they have and how many percent matches the respective known command. Only if candidates cannot be separated will it return multiple matches.
- If multiple matches were returned, resend as `CMD_MULTIMATCH`. If no such command is found in cmdset, return hard-coded list of matches.
- If no match was found, resend as `CMD_NOMATCH`. If no such command is found in cmdset, give hard-coded error message.
11. If a single command was found by the parser, the correct command object is plucked out of storage. This usually doesn't mean a re-initialization.
12. It is checked that the caller actually has access to the command by validating the *lockstring* of the command. If not, it is not considered as a suitable match and `CMD_NOMATCH` is triggered.
13. If the new command is tagged as a channel-command, resend as `CMD_CHANNEL`. If no such command is found in cmdset, use hard-coded implementation.
- The active CmdSets of eventual objects in the same location (if any). This includes commands
on [Exits](Objects#Exits).
- Sets of dynamically created *System commands* representing available
[Communications](Communications#Channels).
7. All CmdSets *of the same priority* are merged together in groups. Grouping avoids order-
dependent issues of merging multiple same-prio sets onto lower ones.
8. All the grouped CmdSets are *merged* in reverse priority into one combined CmdSet according to
each set's merge rules.
9. Evennia's *command parser* takes the merged cmdset and matches each of its commands (using its
key and aliases) against the beginning of the string entered by *caller*. This produces a set of
candidates.
10. The *cmd parser* next rates the matches by how many characters they have and how many percent
matches the respective known command. Only if candidates cannot be separated will it return multiple
matches.
- If multiple matches were returned, resend as `CMD_MULTIMATCH`. If no such command is found in
cmdset, return hard-coded list of matches.
- If no match was found, resend as `CMD_NOMATCH`. If no such command is found in cmdset, give
hard-coded error message.
11. If a single command was found by the parser, the correct command object is plucked out of
storage. This usually doesn't mean a re-initialization.
12. It is checked that the caller actually has access to the command by validating the *lockstring*
of the command. If not, it is not considered as a suitable match and `CMD_NOMATCH` is triggered.
13. If the new command is tagged as a channel-command, resend as `CMD_CHANNEL`. If no such command
is found in cmdset, use hard-coded implementation.
14. Assign several useful variables to the command instance (see previous sections).
15. Call `at_pre_command()` on the command instance.
16. Call `parse()` on the command instance. This is fed the remainder of the string, after the name of the command. It's intended to pre-parse the string into a form useful for the `func()` method.
17. Call `func()` on the command instance. This is the functional body of the command, actually doing useful things.
16. Call `parse()` on the command instance. This is fed the remainder of the string, after the name
of the command. It's intended to pre-parse the string into a form useful for the `func()` method.
17. Call `func()` on the command instance. This is the functional body of the command, actually
doing useful things.
18. Call `at_post_command()` on the command instance.
## Assorted notes
@ -447,6 +655,9 @@ thus do so asynchronously, using callbacks.
deferred.addCallback(callback, self.caller)
```
This is probably not relevant to any but the most advanced/exotic designs (one might use it to create a "nested" command structure for example).
This is probably not relevant to any but the most advanced/exotic designs (one might use it to
create a "nested" command structure for example).
The `save_for_next` class variable can be used to implement state-persistent commands. For example it can make a command operate on "it", where it is determined by what the previous command operated on.
The `save_for_next` class variable can be used to implement state-persistent commands. For example
it can make a command operate on "it", where it is determined by what the previous command operated
on.

View file

@ -1,50 +1,88 @@
# Communications
Apart from moving around in the game world and talking, players might need other forms of communication. This is offered by Evennia's `Comm` system. Stock evennia implements a 'MUX-like' system of channels, but there is nothing stopping you from changing things to better suit your taste.
Apart from moving around in the game world and talking, players might need other forms of
communication. This is offered by Evennia's `Comm` system. Stock evennia implements a 'MUX-like'
system of channels, but there is nothing stopping you from changing things to better suit your
taste.
Comms rely on two main database objects - `Msg` and `Channel`. There is also the `TempMsg` which mimics the API of a `Msg` but has no connection to the database.
Comms rely on two main database objects - `Msg` and `Channel`. There is also the `TempMsg` which
mimics the API of a `Msg` but has no connection to the database.
## Msg
The `Msg` object is the basic unit of communication in Evennia. A message works a little like an e-mail; it always has a sender (a [Account](Accounts)) and one or more recipients. The recipients may be either other Accounts, or a *Channel* (see below). You can mix recipients to send the message to both Channels and Accounts if you like.
The `Msg` object is the basic unit of communication in Evennia. A message works a little like an
e-mail; it always has a sender (a [Account](Accounts)) and one or more recipients. The recipients
may be either other Accounts, or a *Channel* (see below). You can mix recipients to send the message
to both Channels and Accounts if you like.
Once created, a `Msg` is normally not changed. It is peristently saved in the database. This allows for comprehensive logging of communications. This could be useful for allowing senders/receivers to have 'mailboxes' with the messages they want to keep.
Once created, a `Msg` is normally not changed. It is peristently saved in the database. This allows
for comprehensive logging of communications. This could be useful for allowing senders/receivers to
have 'mailboxes' with the messages they want to keep.
### Properties defined on `Msg`
- `senders` - this is a reference to one or many [Account](Accounts) or [Objects](Objects) (normally *Characters*) sending the message. This could also be an *External Connection* such as a message coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be mixed in any combination.
- `receivers` - a list of target [Accounts](Accounts), [Objects](Objects) (usually *Characters*) or *Channels* to send the message to. The types of receivers can be mixed in any combination.
- `senders` - this is a reference to one or many [Account](Accounts) or [Objects](Objects) (normally
*Characters*) sending the message. This could also be an *External Connection* such as a message
coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be
mixed in any combination.
- `receivers` - a list of target [Accounts](Accounts), [Objects](Objects) (usually *Characters*) or
*Channels* to send the message to. The types of receivers can be mixed in any combination.
- `header` - this is a text field for storing a title or header for the message.
- `message` - the actual text being sent.
- `date_sent` - when message was sent (auto-created).
- `locks` - a [lock definition](Locks).
- `hide_from` - this can optionally hold a list of objects, accounts or channels to hide this `Msg` from. This relationship is stored in the database primarily for optimization reasons, allowing for quickly post-filter out messages not intended for a given target. There is no in-game methods for setting this, it's intended to be done in code.
- `hide_from` - this can optionally hold a list of objects, accounts or channels to hide this `Msg`
from. This relationship is stored in the database primarily for optimization reasons, allowing for
quickly post-filter out messages not intended for a given target. There is no in-game methods for
setting this, it's intended to be done in code.
You create new messages in code using `evennia.create_message` (or `evennia.utils.create.create_message.`)
You create new messages in code using `evennia.create_message` (or
`evennia.utils.create.create_message.`)
## TempMsg
`evennia.comms.models` also has `TempMsg` which mimics the API of `Msg` but is not connected to the database. TempMsgs are used by Evennia for channel messages by default. They can be used for any system expecting a `Msg` but when you don't actually want to save anything.
`evennia.comms.models` also has `TempMsg` which mimics the API of `Msg` but is not connected to the
database. TempMsgs are used by Evennia for channel messages by default. They can be used for any
system expecting a `Msg` but when you don't actually want to save anything.
## Channels
Channels are [Typeclassed](Typeclasses) entities, which mean they can be easily extended and their functionality modified. To change which channel typeclass Evennia uses, change settings.BASE_CHANNEL_TYPECLASS.
Channels are [Typeclassed](Typeclasses) entities, which mean they can be easily extended and their
functionality modified. To change which channel typeclass Evennia uses, change
settings.BASE_CHANNEL_TYPECLASS.
Channels act as generic distributors of messages. Think of them as "switch boards" redistributing `Msg` or `TempMsg` objects. Internally they hold a list of "listening" objects and any `Msg` (or `TempMsg`) sent to the channel will be distributed out to all channel listeners. Channels have [Locks](Locks) to limit who may listen and/or send messages through them.
Channels act as generic distributors of messages. Think of them as "switch boards" redistributing
`Msg` or `TempMsg` objects. Internally they hold a list of "listening" objects and any `Msg` (or
`TempMsg`) sent to the channel will be distributed out to all channel listeners. Channels have
[Locks](Locks) to limit who may listen and/or send messages through them.
The *sending* of text to a channel is handled by a dynamically created [Command](Commands) that always have the same name as the channel. This is created for each channel by the global `ChannelHandler`. The Channel command is added to the Account's cmdset and normal command locks are used to determine which channels are possible to write to. When subscribing to a channel, you can then just write the channel name and the text to send.
The *sending* of text to a channel is handled by a dynamically created [Command](Commands) that
always have the same name as the channel. This is created for each channel by the global
`ChannelHandler`. The Channel command is added to the Account's cmdset and normal command locks are
used to determine which channels are possible to write to. When subscribing to a channel, you can
then just write the channel name and the text to send.
The default ChannelCommand (which can be customized by pointing `settings.CHANNEL_COMMAND_CLASS` to your own command), implements a few convenient features:
The default ChannelCommand (which can be customized by pointing `settings.CHANNEL_COMMAND_CLASS` to
your own command), implements a few convenient features:
- It only sends `TempMsg` objects. Instead of storing individual entries in the database it instead dumps channel output a file log in `server/logs/channel_<channelname>.log`. This is mainly for practical reasons - we find one rarely need to query individual Msg objects at a later date. Just stupidly dumping the log to a file also means a lot less database overhead.
- It adds a `/history` switch to view the 20 last messages in the channel. These are read from the end of the log file. One can also supply a line number to start further back in the file (but always 20 entries at a time). It's used like this:
- It only sends `TempMsg` objects. Instead of storing individual entries in the database it instead
dumps channel output a file log in `server/logs/channel_<channelname>.log`. This is mainly for
practical reasons - we find one rarely need to query individual Msg objects at a later date. Just
stupidly dumping the log to a file also means a lot less database overhead.
- It adds a `/history` switch to view the 20 last messages in the channel. These are read from the
end of the log file. One can also supply a line number to start further back in the file (but always
20 entries at a time). It's used like this:
> public/history
> public/history 35
There are two default channels created in stock Evennia - `MudInfo` and `Public`. `MudInfo` receives server-related messages meant for Admins whereas `Public` is open to everyone to chat on (all new accounts are automatically joined to it when logging in, it is useful for asking questions). The default channels are defined by the `DEFAULT_CHANNELS` list (see `evennia/settings_default.py` for more details).
There are two default channels created in stock Evennia - `MudInfo` and `Public`. `MudInfo`
receives server-related messages meant for Admins whereas `Public` is open to everyone to chat on
(all new accounts are automatically joined to it when logging in, it is useful for asking
questions). The default channels are defined by the `DEFAULT_CHANNELS` list (see
`evennia/settings_default.py` for more details).
You create new channels with `evennia.create_channel` (or `evennia.utils.create.create_channel`).
@ -52,7 +90,11 @@ In code, messages are sent to a channel using the `msg` or `tempmsg` methods of
channel.msg(msgobj, header=None, senders=None, persistent=True)
The argument `msgobj` can be either a string, a previously constructed `Msg` or a `TempMsg` - in the latter cases all the following keywords are ignored since the message objects already contains all this information. If `msgobj` is a string, the other keywords are used for creating a new `Msg` or `TempMsg` on the fly, depending on if `persistent` is set or not. By default, a `TempMsg` is emitted for channel communication (since the default ChannelCommand instead logs to a file).
The argument `msgobj` can be either a string, a previously constructed `Msg` or a `TempMsg` - in the
latter cases all the following keywords are ignored since the message objects already contains all
this information. If `msgobj` is a string, the other keywords are used for creating a new `Msg` or
`TempMsg` on the fly, depending on if `persistent` is set or not. By default, a `TempMsg` is emitted
for channel communication (since the default ChannelCommand instead logs to a file).
```python
# assume we have a 'sender' object and a channel named 'mychan'
@ -67,4 +109,5 @@ The argument `msgobj` can be either a string, a previously constructed `Msg` or
- `aliases` - alternative native names for channels
- `desc` - optional description of channel (seen in listings)
- `keep_log` (bool) - if the channel should store messages (default)
- `locks` - A [lock definition](Locks). Channels normally use the access_types `send, control` and `listen`.
- `locks` - A [lock definition](Locks). Channels normally use the access_types `send, control` and
`listen`.

View file

@ -16,7 +16,8 @@ When you first connect to your game you are greeted by Evennia's default connect
Enter help for more info. look will re-show this screen.
==============================================================
Effective, but not very exciting. You will most likely want to change this to be more unique for your game. This is simple:
Effective, but not very exciting. You will most likely want to change this to be more unique for
your game. This is simple:
1. Edit `mygame/server/conf/connection_screens.py`.
1. [Reload](Start-Stop-Reload) Evennia.
@ -32,4 +33,4 @@ You can also customize the [Commands](Commands) available to use while the conne
shown (`connect`, `create` etc). These commands are a bit special since when the screen is running
the account is not yet logged in. A command is made available at the login screen by adding them to
`UnloggedinCmdSet` in `mygame/commands/default_cmdset.py`. See [Commands](Commands) and the
tutorial section on how to add new commands to a default command set.
tutorial section on how to add new commands to a default command set.

View file

@ -20,11 +20,13 @@ For Evennia, continuous integration allows an automated build process to:
* Reload the game.
## Preparation
To prepare a CI environment for your `MU*`, it will be necessary to set up some prerequisite software for your server.
To prepare a CI environment for your `MU*`, it will be necessary to set up some prerequisite
software for your server.
Among those you will need:
* A Continuous Integration Environment.
* I recommend [TeamCity](https://www.jetbrains.com/teamcity/) which has an in-depth [Setup Guide](https://confluence.jetbrains.com/display/TCD8/Installing+and+Configuring+the+TeamCity+Server)
* I recommend [TeamCity](https://www.jetbrains.com/teamcity/) which has an in-depth [Setup
Guide](https://confluence.jetbrains.com/display/TCD8/Installing+and+Configuring+the+TeamCity+Server)
* [Source Control](Version-Control)
* This could be Git or SVN or any other available SC.
@ -35,7 +37,8 @@ build integration environment on Linux.
After meeting the preparation steps for your specific environment, log on to your teamcity interface
at `http://<your server>:8111/`.
Create a new project named "Evennia" and in it construct a new template called continuous-integration.
Create a new project named "Evennia" and in it construct a new template called continuous-
integration.
### A Quick Overview
Templates are fancy objects in TeamCity that allow an administrator to define build steps that are
@ -69,8 +72,10 @@ For each step we'll being use the "Command Line Runner" (a fancy name for a shel
CONFIG="%system.teamcity.build.checkoutDir%/server/conf/settings.py"
MYCONF="%system.teamcity.build.checkoutDir%/server/conf/my.cnf"
sed -e 's/TELNET_PORTS = [4000]/TELNET_PORTS = [%game.ports%]/g' "$CONFIG" > "$CONFIG".tmp && mv "$CONFIG".tmp "$CONFIG"
sed -e 's/WEBSERVER_PORTS = [(4001, 4002)]/WEBSERVER_PORTS = [%game.webports%]/g' "$CONFIG" > "$CONFIG".tmp && mv "$CONFIG".tmp "$CONFIG"
sed -e 's/TELNET_PORTS = [4000]/TELNET_PORTS = [%game.ports%]/g' "$CONFIG" > "$CONFIG".tmp && mv
"$CONFIG".tmp "$CONFIG"
sed -e 's/WEBSERVER_PORTS = [(4001, 4002)]/WEBSERVER_PORTS = [%game.webports%]/g' "$CONFIG" >
"$CONFIG".tmp && mv "$CONFIG".tmp "$CONFIG"
# settings.py MySQL DB configuration
echo Configuring Game Database...
@ -102,7 +107,8 @@ parameters that are populated when the build itself is ran. When creating projec
template, we'll be able to fill in or override those parameters for project-specific configuration.
* Go ahead and create another build step called "Make Database Migration"
* If you're using SQLLite on your game, it will be prudent to change working directory on this step to: %game.dir%
* If you're using SQLLite on your game, it will be prudent to change working directory on this
step to: %game.dir%
* In this script include:
```bash
@ -123,7 +129,8 @@ template, we'll be able to fill in or override those parameters for project-spec
```
* Create yet another build step, this time named: "Execute Database Migration":
* If you're using SQLLite on your game, it will be prudent to change working directory on this step to: %game.dir%
* If you're using SQLLite on your game, it will be prudent to change working directory on this
step to: %game.dir%
```bash
#!/bin/bash
# Apply the database migration.
@ -147,7 +154,8 @@ done in a 'work' directory on TeamCity's build agent. From that directory we wil
to where our game actually exists on the local server.
* Create a new build step called "Publish Build":
* If you're using SQLLite on your game, be sure to order this step ABOVE the Database Migration steps. The build order will matter!
* If you're using SQLLite on your game, be sure to order this step ABOVE the Database Migration
steps. The build order will matter!
```bash
#!/bin/bash
# Publishes the build to the proper build directory.
@ -203,10 +211,12 @@ Now it's time for the last few steps to set up a CI environment.
* This will be the category that holds our actual game.
* Create a new Build Configuration in Production with the name of your MUSH.
* Base this configuration off of the continuous-integration template we made earlier.
* In the build configuration, enter VCS roots and create a new VCS root that points to the branch/version control that you are using.
* In the build configuration, enter VCS roots and create a new VCS root that points to the
branch/version control that you are using.
* Go to the parameters page and fill in the undefined parameters for your specific configuration.
* If you wish for the CI to run every time a commit is made, go to the VCS triggers and add one for "On Every Commit".
* If you wish for the CI to run every time a commit is made, go to the VCS triggers and add one for
"On Every Commit".
And you're done! At this point, you can return to the project overview page and queue a new build
for your game. If everything was set up correctly, the build will complete successfully. Additional
build steps could be added or removed at this point, adding some features like Unit Testing or more!
build steps could be added or removed at this point, adding some features like Unit Testing or more!

View file

@ -14,7 +14,8 @@ Markdown files are simple text files that can be edited with a normal text edito
contain raw HTML directives (but that is very rarely needed). They primarly use
the [Markdown][commonmark] syntax. See [the syntax section below](#Editing-syntax) for more help.
> Note: Don't edit the files in `evennia/docs/source/api/`. These are auto-generated and your changes
> Note: Don't edit the files in `evennia/docs/source/api/`. These are auto-generated and your
changes
> will be lost.
## Building the docs locally
@ -176,7 +177,8 @@ available at https://evennia.github.io/evennia/latest/.
# Editing syntax
The format used for Evennia's docs is [Markdown][commonmark-help] (Commonmark). While markdown supports a
The format used for Evennia's docs is [Markdown][commonmark-help] (Commonmark). While markdown
supports a
few alternative forms for some of these, we try to stick to the below forms for consistency.
### Italic/Bold
@ -188,14 +190,16 @@ We generally use underscores for italics and double-asterisks for bold:
### Headings
We use `#` to indicate sections/headings. The more `#` the more of a sub-heading it is (will get smaller
We use `#` to indicate sections/headings. The more `#` the more of a sub-heading it is (will get
smaller
and smaller font).
- `# Heading`
- `## SubHeading`
- `## SubSubHeading`
> Don't reuse the same heading/subheading name over and over in the same document. While Markdown does not prevent
> Don't reuse the same heading/subheading name over and over in the same document. While Markdown
does not prevent
it, it makes it impossible to link to those duplicates properly (see next section).
### Lists
@ -241,7 +245,8 @@ an explicit [Note](#Note).
- `[linktext](url_or_ref)` - gives a clickable link [linktext][linkdemo].
The `url_or_ref` can either be a full `http://...` url or an internal _reference_. For example, use
`[my document](My-Document)` to link to the document `evennia/docs/source/My-Document.md`. Avoid using
`[my document](My-Document)` to link to the document `evennia/docs/source/My-Document.md`. Avoid
using
full `http://` linking unless really referring to an external resource.
- `[linktext](ref#heading-name)`
@ -252,7 +257,8 @@ would be a link `[cool stuff](My-Document#Cool-Stuff)`.
- `[linktext][linkref]` - refer to a reference defined later in the document.
Urls can get long and if you are using the same url in many places it can get a little cluttered. So you can also put
Urls can get long and if you are using the same url in many places it can get a little cluttered. So
you can also put
the url as a 'footnote' at the end of your document
and refer to it by putting your reference within square brackets `[ ]`. Here's an example:
@ -291,7 +297,8 @@ The Evennia documentation supports some special reference shortcuts in links:
This will create a link to the auto-generated `evennia/source/api/evennia.objects.rst` document.
Since api-docs are generated alongside the documentation, this will always be the api docs for the
Since api-docs are generated alongside the documentation, this will always be the api docs for
the
current version/branch of the docs.
##### Bug reports/feature request
@ -309,7 +316,8 @@ The Evennia documentation supports some special reference shortcuts in links:
### Verbatim text
It's common to want to mark something to be displayed verbatim - just as written - without any
Markdown parsing. In running text, this is done using backticks (\`), like \`verbatim text\` becomes `verbatim text`.
Markdown parsing. In running text, this is done using backticks (\`), like \`verbatim text\` becomes
`verbatim text`.
If you want to put the verbatim text on its own line, you can do so easily by simply indenting
it 4 spaces (add empty lines on each side for readability too):
@ -333,7 +341,8 @@ Everything within these backticks will be verbatim.
### Code blocks
A special case is code examples - we want them to get code-highlighting for readability. This is done by using
A special case is code examples - we want them to get code-highlighting for readability. This is
done by using
the triple-backticks and specify which language we use:
````
@ -356,7 +365,8 @@ def a_python_func(x):
Markdown is easy to read and use. But while it does most of what we need, there are some things it's
not quite as expressive as it needs to be. For this we need to fall back to the [ReST][ReST] markup
language which the documentation system uses under the hood. This is done by specifying `eval_rst` as
language which the documentation system uses under the hood. This is done by specifying `eval_rst`
as
the name of the `language` of a literal block:
````
@ -367,7 +377,8 @@ the name of the `language` of a literal block:
```
````
There is also a short-hand form for starting a [ReST directive][ReST-directives] without need for `eval_rst`:
There is also a short-hand form for starting a [ReST directive][ReST-directives] without need for
`eval_rst`:
````
```directive:: possible-option
@ -597,21 +608,27 @@ for more flexibility. It also provides a link to the code block, identified by i
self.caller.msg(self.args.strip())
```
Here, `:linenos:` turns on line-numbers and `:emphasize-lines:` allows for emphasizing certain lines
in a different color. The `:caption:` shows an instructive text and `:name:` is used to reference this
in a different color. The `:caption:` shows an instructive text and `:name:` is used to reference
this
block through the link that will appear (so it should be unique for a give document).
> The default markdown syntax will actually generate a code-block ReST instruction like this
> automatically for us behind the scenes. The automatic generation can't know things like emphasize-lines
> automatically for us behind the scenes. The automatic generation can't know things like emphasize-
lines
> or caption since that's not a part of the Markdown specification.
# Technical
Evennia leverages [Sphinx][sphinx] with the [recommonmark][recommonmark] extension, which allows us to write our
docs in light-weight Markdown (more specifically [CommonMark][commonmark], like on github) rather than ReST.
The recommonmark extension however also allows us to use ReST selectively in the places were it is more
Evennia leverages [Sphinx][sphinx] with the [recommonmark][recommonmark] extension, which allows us
to write our
docs in light-weight Markdown (more specifically [CommonMark][commonmark], like on github) rather
than ReST.
The recommonmark extension however also allows us to use ReST selectively in the places were it is
more
expressive than the simpler (but much easier) Markdown.
For [autodoc-generation][sphinx-autodoc] generation, we use the sphinx-[napoleon][sphinx-napoleon] extension
For [autodoc-generation][sphinx-autodoc] generation, we use the sphinx-[napoleon][sphinx-napoleon]
extension
to understand our friendly Google-style docstrings used in classes and functions etc.
@ -620,7 +637,8 @@ to understand our friendly Google-style docstrings used in classes and functions
[recommonmark]: https://recommonmark.readthedocs.io/en/latest/index.html
[commonmark]: https://spec.commonmark.org/current/
[commonmark-help]: https://commonmark.org/help/
[sphinx-autodoc]: http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc
[sphinx-autodoc]: http://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-
sphinx.ext.autodoc
[sphinx-napoleon]: http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html
[getting-started]: Getting-Started
[contributing]: Contributing
@ -631,4 +649,4 @@ to understand our friendly Google-style docstrings used in classes and functions
[linkdemo]: #Links
[retext]: https://github.com/retext-project/retext
[grip]: https://github.com/joeyespo/grip
[pycharm]: https://www.jetbrains.com/pycharm/
[pycharm]: https://www.jetbrains.com/pycharm/

View file

@ -37,7 +37,8 @@ We always need more eyes and hands on the code. Even if you don't feel confident
and reporting when stuff doesn't make sense helps us a lot.
The most elegant way to contribute code to Evennia is to use GitHub to create a *fork* of the
Evennia repository and make your changes to that. Refer to the [Forking Evennia](Version-Control#forking-evennia) version
Evennia repository and make your changes to that. Refer to the [Forking Evennia](Version-
Control#forking-evennia) version
control instructions for detailed instructions.
Once you have a fork set up, you can not only work on your own game in a separate branch, you can
@ -65,23 +66,53 @@ directory contains game systems that are specialized or useful only to certain t
are welcome to contribute to the `contrib/` directory. Such contributions should always happen via a
Forked repository as described above.
* If you are unsure if your idea/code is suitable as a contrib, *ask the devs before putting any work into it*. This can also be a good idea in order to not duplicate efforts. This can also act as a check that your implementation idea is sound. We are, for example, unlikely to accept contribs that require large modifications of the game directory structure.
* If your code is intended *primarily* as an example or shows a concept/principle rather than a working system, it is probably not suitable for `contrib/`. You are instead welcome to use it as part of a [new tutorial][tutorials]!
* The code should ideally be contained within a single Python module. But if the contribution is large this may not be practical and it should instead be grouped in its own subdirectory (not as loose modules).
* The contribution should preferably be isolated (only make use of core Evennia) so it can easily be dropped into use. If it does depend on other contribs or third-party modules, these must be clearly documented and part of the installation instructions.
* If you are unsure if your idea/code is suitable as a contrib, *ask the devs before putting any
work into it*. This can also be a good idea in order to not duplicate efforts. This can also act as
a check that your implementation idea is sound. We are, for example, unlikely to accept contribs
that require large modifications of the game directory structure.
* If your code is intended *primarily* as an example or shows a concept/principle rather than a
working system, it is probably not suitable for `contrib/`. You are instead welcome to use it as
part of a [new tutorial][tutorials]!
* The code should ideally be contained within a single Python module. But if the contribution is
large this may not be practical and it should instead be grouped in its own subdirectory (not as
loose modules).
* The contribution should preferably be isolated (only make use of core Evennia) so it can easily be
dropped into use. If it does depend on other contribs or third-party modules, these must be clearly
documented and part of the installation instructions.
* The code itself should follow Evennia's [Code style guidelines][codestyle].
* The code must be well documented as described in our [documentation style guide](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#doc-strings). Expect that your code will be read and should be possible to understand by others. Include comments as well as a header in all modules. If a single file, the header should include info about how to include the contrib in a game (installation instructions). If stored in a subdirectory, this info should go into a new `README.md` file within that directory.
* Within reason, your contribution should be designed as genre-agnostic as possible. Limit the amount of game-style-specific code. Assume your code will be applied to a very different game than you had in mind when creating it.
* To make the licensing situation clear we assume all contributions are released with the same [license as Evennia](Licensing). If this is not possible for some reason, talk to us and we'll handle it on a case-by-case basis.
* Your contribution must be covered by [unit tests](Unit-Testing). Having unit tests will both help make your code more stable and make sure small changes does not break it without it being noticed, it will also help us test its functionality and merge it quicker. If your contribution is a single module, you can add your unit tests to `evennia/contribs/tests.py`. If your contribution is bigger and in its own sub-directory you could just put the tests in your own `tests.py` file (Evennia will find it automatically).
* Merging of your code into Evennia is not guaranteed. Be ready to receive feedback and to be asked to make corrections or fix bugs. Furthermore, merging a contrib means the Evennia project takes on the responsibility of maintaining and supporting it. For various reasons this may be deemed to be beyond our manpower. However, if your code were to *not* be accepted for merger for some reason, we will instead add a link to your online repository so people can still find and use your work if they want.
* The code must be well documented as described in our [documentation style
guide](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#doc-strings). Expect that your
code will be read and should be possible to understand by others. Include comments as well as a
header in all modules. If a single file, the header should include info about how to include the
contrib in a game (installation instructions). If stored in a subdirectory, this info should go into
a new `README.md` file within that directory.
* Within reason, your contribution should be designed as genre-agnostic as possible. Limit the
amount of game-style-specific code. Assume your code will be applied to a very different game than
you had in mind when creating it.
* To make the licensing situation clear we assume all contributions are released with the same
[license as Evennia](Licensing). If this is not possible for some reason, talk to us and we'll
handle it on a case-by-case basis.
* Your contribution must be covered by [unit tests](Unit-Testing). Having unit tests will both help
make your code more stable and make sure small changes does not break it without it being noticed,
it will also help us test its functionality and merge it quicker. If your contribution is a single
module, you can add your unit tests to `evennia/contribs/tests.py`. If your contribution is bigger
and in its own sub-directory you could just put the tests in your own `tests.py` file (Evennia will
find it automatically).
* Merging of your code into Evennia is not guaranteed. Be ready to receive feedback and to be asked
to make corrections or fix bugs. Furthermore, merging a contrib means the Evennia project takes on
the responsibility of maintaining and supporting it. For various reasons this may be deemed to be
beyond our manpower. However, if your code were to *not* be accepted for merger for some reason, we
will instead add a link to your online repository so people can still find and use your work if they
want.
[ohloh]: http://www.ohloh.net/p/evennia
[patron]: https://www.patreon.com/griatch
[donate]: https://www.paypal.com/en/cgi-bin/webscr?cmd=_flow&SESSION=TWy_epDPSWqNr4UJCOtVWxl-pO1X1jbKiv_-UBBFWIuVDEZxC0M_2pM6ywO&dispatch=5885d80a13c0db1f8e263663d3faee8d66f31424b43e9a70645c907a6cbd8fb4
[donate]: https://www.paypal.com/en/cgi-bin/webscr?cmd=_flow&SESSION=TWy_epDPSWqNr4UJCOtVWxl-
pO1X1jbKiv_-
UBBFWIuVDEZxC0M_2pM6ywO&dispatch=5885d80a13c0db1f8e263663d3faee8d66f31424b43e9a70645c907a6cbd8fb4
[forking]: https://github.com/evennia/evennia/wiki/Version-Control#wiki-forking-from-evennia
[pullrequest]: https://github.com/evennia/evennia/pulls
[issues]: https://github.com/evennia/evennia/issues
[patch]: https://secure.wikimedia.org/wikipedia/en/wiki/Patch_%28computing%29
[codestyle]: https://github.com/evennia/evennia/blob/master/CODING_STYLE.md
[tutorials]: https://github.com/evennia/evennia/wiki/Tutorials
[tutorials]: https://github.com/evennia/evennia/wiki/Tutorials

View file

@ -4,7 +4,9 @@
This tutorial is moderately difficult in content. You might want to be familiar and at ease with
some Python concepts (like properties) and possibly Django concepts (like queries), although this
tutorial will try to walk you through the process and give enough explanations each time. If you don't feel very confident with math, don't hesitate to pause, go to the example section, which shows a tiny map, and try to walk around the code or read the explanation.
tutorial will try to walk you through the process and give enough explanations each time. If you
don't feel very confident with math, don't hesitate to pause, go to the example section, which shows
a tiny map, and try to walk around the code or read the explanation.
Evennia doesn't have a coordinate system by default. Rooms and other objects are linked by location
and content:
@ -97,7 +99,8 @@ class Room(DefaultRoom):
```
If you aren't familiar with the concept of properties in Python, I encourage you to read a good
tutorial on the subject. [This article on Python properties](https://www.programiz.com/python-programming/property)
tutorial on the subject. [This article on Python properties](https://www.programiz.com/python-
programming/property)
is well-explained and should help you understand the idea.
Let's look at our properties for `x`. First of all is the read property.
@ -170,7 +173,8 @@ class methods, since we want to get rooms.
### Finding one room
First, a simple one: how to find a room at a given coordinate? Say, what is the room at X=0, Y=0, Z=0?
First, a simple one: how to find a room at a given coordinate? Say, what is the room at X=0, Y=0,
Z=0?
```python
class Room(DefaultRoom):
@ -199,7 +203,8 @@ class Room(DefaultRoom):
return None
```
This solution includes a bit of [Django queries](https://docs.djangoproject.com/en/1.11/topics/db/queries/).
This solution includes a bit of [Django
queries](https://docs.djangoproject.com/en/1.11/topics/db/queries/).
Basically, what we do is reach for the object manager and search for objects with the matching tags.
Again, don't spend too much time worrying about the mechanism, the method is quite easy to use:
@ -207,14 +212,16 @@ Again, don't spend too much time worrying about the mechanism, the method is qui
Room.get_room_at(5, 2, -3)
```
Notice that this is a class method: you will call it from `Room` (the class), not an instance. Though you still can:
Notice that this is a class method: you will call it from `Room` (the class), not an instance.
Though you still can:
@py here.get_room_at(3, 8, 0)
### Finding several rooms
Here's another useful method that allows us to look for rooms around a given coordinate. This is
more advanced search and doing some calculation, beware! Look at the following section if you're lost.
more advanced search and doing some calculation, beware! Look at the following section if you're
lost.
```python
from math import sqrt
@ -280,7 +287,8 @@ This gets more serious.
1. We have specified coordinates as parameters. We determine a broad range using the distance.
That is, for each coordinate, we create a list of possible matches. See the example below.
2. We then search for the rooms within this broader range. It gives us a square
around our location. Some rooms are definitely outside the range. Again, see the example below to follow the logic.
around our location. Some rooms are definitely outside the range. Again, see the example below
to follow the logic.
3. We filter down the list and sort it by distance from the specified coordinates.
Notice that we only search starting at step 2. Thus, the Django search doesn't look and cache all
@ -301,7 +309,10 @@ An example might help. Consider this very simple map (a textual description fol
1 2 3 4
```
The X coordinates are given below. The Y coordinates are given on the left. This is a simple square with 16 rooms: 4 on each line, 4 lines of them. All the rooms are identified by letters in this example: the first line at the top has rooms A to D, the second E to H, the third I to L and the fourth M to P. The bottom-left room, X=1 and Y=1, is M. The upper-right room X=4 and Y=4 is D.
The X coordinates are given below. The Y coordinates are given on the left. This is a simple
square with 16 rooms: 4 on each line, 4 lines of them. All the rooms are identified by letters in
this example: the first line at the top has rooms A to D, the second E to H, the third I to L and
the fourth M to P. The bottom-left room, X=1 and Y=1, is M. The upper-right room X=4 and Y=4 is D.
So let's say we want to find all the neighbors, distance 1, from the room J. J is at X=2, Y=2.
@ -310,8 +321,12 @@ So we use:
Room.get_rooms_around(x=2, y=2, z=0, distance=1)
# we'll assume a z coordinate of 0 for simplicity
1. First, this method gets all the rooms in a square around J. So it gets E F G, I J K, M N O. If you want, draw the square around these coordinates to see what's happening.
2. Next, we browse over this list and check the real distance between J (X=2, Y=2) and the room. The four corners of the square are not in this circle. For instance, the distance between J and M is not 1. If you draw a circle of center J and radius 1, you'll notice that the four corners of our square (E, G, M and O) are not in this circle. So we remove them.
1. First, this method gets all the rooms in a square around J. So it gets E F G, I J K, M N O. If
you want, draw the square around these coordinates to see what's happening.
2. Next, we browse over this list and check the real distance between J (X=2, Y=2) and the room.
The four corners of the square are not in this circle. For instance, the distance between J and M
is not 1. If you draw a circle of center J and radius 1, you'll notice that the four corners of our
square (E, G, M and O) are not in this circle. So we remove them.
3. We sort by distance from J.
So in the end we might obtain something like this:
@ -331,4 +346,4 @@ You can try with more examples if you want to see this in action.
### To conclude
You can definitely use this system to map other objects, not just rooms. You can easily remove the
`Z coordinate too, if you simply need X and Y.
`Z coordinate too, if you simply need X and Y.

View file

@ -5,14 +5,16 @@
their own custom client protocol.*
A [PortalSession](Sessions#Portal-and-Server-Sessions) is the basic data object representing an external
A [PortalSession](Sessions#Portal-and-Server-Sessions) is the basic data object representing an
external
connection to the Evennia [Portal](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
*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
@ -25,7 +27,10 @@ You <->
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).
(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
@ -47,7 +52,8 @@ modules, you could do the following:
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`.
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
@ -93,7 +99,10 @@ Writing a stable communication protocol from scratch is not something we'll cove
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.
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:
@ -192,7 +201,8 @@ class MyCustomClient(TwistedClient, Session):
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.
The principle here is that the Twisted-specific methods are overridden to redirect inputs/outputs to
the Evennia-specific methods.
### Sending data out
@ -202,11 +212,18 @@ To send data out through this protocol, you'd need to get its Session and then y
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.
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](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](Inputfuncs) page for how to do this.
Just because the protocol is there, does not mean Evennia knows what to do with it. An
[Inputfunc](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](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
@ -219,4 +236,4 @@ websockets. You'll find that whereas telnet is a textbook example of a Twisted p
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.
ways.

View file

@ -97,13 +97,25 @@ class CmdConnect(Command):
Okay, let's review this code, but if you're used to Evennia commands, it shouldn't be too strange:
1. We import `search_channel`. This is a little helper function that we will use to search for channels by name and aliases, found in `evennia.utils.search`. It's just more convenient.
1. We import `search_channel`. This is a little helper function that we will use to search for
channels by name and aliases, found in `evennia.utils.search`. It's just more convenient.
2. Our class `CmdConnect` contains the body of our command to join a channel.
3. Notice the key of this command is simply `"+"`. When you enter `+something` in the game, it will try to find a command key `+something`. Failing that, it will look at other potential matches. Evennia is smart enough to understand that when we type `+something`, `+` is the command key and `something` is the command argument. This will, of course, fail if you have a command beginning by `+` conflicting with the `CmdConnect` key.
4. We have altered some class attributes, like `auto_help`. If you want to know what they do and why they have changed here, you can check the [documentation on commands](Commands).
5. In the command body, we begin by extracting the channel name. Remember that this name should be in the command arguments (that is, in `self.args`). Following the same example, if a player enters `+something`, `self.args` should contain `"something"`. We use `search_channel` to see if this channel exists.
6. We then check the access level of the channel, to see if the caller can listen to it (not necessarily use it to speak, mind you, just listen to others speak, as these are two different locks on Evennia).
7. Finally, we connect the caller if he's not already connected to the channel. We use the channel's `connect` method to do this. Pretty straightforward eh?
3. Notice the key of this command is simply `"+"`. When you enter `+something` in the game, it will
try to find a command key `+something`. Failing that, it will look at other potential matches.
Evennia is smart enough to understand that when we type `+something`, `+` is the command key and
`something` is the command argument. This will, of course, fail if you have a command beginning by
`+` conflicting with the `CmdConnect` key.
4. We have altered some class attributes, like `auto_help`. If you want to know what they do and
why they have changed here, you can check the [documentation on commands](Commands).
5. In the command body, we begin by extracting the channel name. Remember that this name should be
in the command arguments (that is, in `self.args`). Following the same example, if a player enters
`+something`, `self.args` should contain `"something"`. We use `search_channel` to see if this
channel exists.
6. We then check the access level of the channel, to see if the caller can listen to it (not
necessarily use it to speak, mind you, just listen to others speak, as these are two different locks
on Evennia).
7. Finally, we connect the caller if he's not already connected to the channel. We use the
channel's `connect` method to do this. Pretty straightforward eh?
Now we'll add a command to leave a channel. It's almost the same, turned upside down:
@ -426,7 +438,8 @@ add:
caller (TypedObject): A Character or Account who has entered an ambiguous command.
Returns:
A string with identifying information to disambiguate the object, conventionally with a preceding space.
A string with identifying information to disambiguate the object, conventionally with a
preceding space.
"""
return " (channel)"
```
@ -467,4 +480,5 @@ close from the code I've provided here. Notice, however, that this resource is
external to Evennia and not maintained by anyone but the original author of
this article.
[Read the full example on Github](https://github.com/vincent-lg/avenew/blob/master/commands/comms.py)
[Read the full example on Github](https://github.com/vincent-
lg/avenew/blob/master/commands/comms.py)

View file

@ -2,7 +2,8 @@
Sometimes, an error is not trivial to resolve. A few simple `print` statements is not enough to find
the cause of the issue. Running a *debugger* can then be very helpful and save a lot of time. Debugging
the cause of the issue. Running a *debugger* can then be very helpful and save a lot of time.
Debugging
means running Evennia under control of a special *debugger* program. This allows you to stop the
action at a given point, view the current state and step forward through the program to see how its
logic works.
@ -87,7 +88,8 @@ in your console, and you will find it here. Below is an example with `pdb`.
### Listing surrounding lines of code
When you have the `pdb` prompt `(Pdb)`, you can type in different commands to explore the code. The first one you should know is `list` (you can type `l` for short):
When you have the `pdb` prompt `(Pdb)`, you can type in different commands to explore the code. The
first one you should know is `list` (you can type `l` for short):
```
(Pdb) l
@ -205,7 +207,8 @@ None
(Pdb)
```
We have entered the `test` command without parameter, so no object could be found in the search (`self.args` is an empty string).
We have entered the `test` command without parameter, so no object could be found in the search
(`self.args` is an empty string).
Let's allow the command to continue and try to use an object name as parameter (although, we should
fix that bug too, it would be better):
@ -279,12 +282,15 @@ command is not needed much in `pudb` since it displays the code directly in its
| Pdb/PuDB command | To do what |
| ----------- | ---------- |
| list (or l) | List the lines around the point of execution (not needed for `pudb`, it will show this directly). |
| list (or l) | List the lines around the point of execution (not needed for `pudb`, it will show
this directly). |
| print (or p) | Display one or several variables. |
| `!` | Run Python code (using a `!` is often optional). |
| continue (or c) | Continue execution and terminate the debugger for this time. |
| next (or n) | Execute the current line and goes to the next one. |
| step (or s) | Step inside of a function or method to examine it. |
| `<RETURN>` | Repeat the last command (don't type `n` repeatedly, just type it once and then press `<RETURN>` to repeat it). |
| `<RETURN>` | Repeat the last command (don't type `n` repeatedly, just type it once and then press
`<RETURN>` to repeat it). |
If you want to learn more about debugging with Pdb, you will find an [interesting tutorial on that topic here](https://pymotw.com/3/pdb/).
If you want to learn more about debugging with Pdb, you will find an [interesting tutorial on that
topic here](https://pymotw.com/3/pdb/).

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,34 @@
# Default Exit Errors
Evennia allows for exits to have any name. The command "kitchen" is a valid exit name as well as "jump out the window" or "north". An exit actually consists of two parts: an [Exit Object](Objects) and an [Exit Command](Commands) stored on said exit object. The command has the same key and aliases as the object, which is why you can see the exit in the room and just write its name to traverse it.
Evennia allows for exits to have any name. The command "kitchen" is a valid exit name as well as
"jump out the window" or "north". An exit actually consists of two parts: an [Exit Object](Objects)
and an [Exit Command](Commands) stored on said exit object. The command has the same key and aliases
as the object, which is why you can see the exit in the room and just write its name to traverse it.
If you try to enter the name of a non-existing exit, it is thus the same as trying a non-exising command; Evennia doesn't care about the difference:
If you try to enter the name of a non-existing exit, it is thus the same as trying a non-exising
command; Evennia doesn't care about the difference:
> jump out the window
Command 'jump out the window' is not available. Type "help" for help.
Many games don't need this type of freedom however. They define only the cardinal directions as valid exit names (Evennia's `@tunnel` command also offers this functionality). In this case, the error starts to look less logical:
Many games don't need this type of freedom however. They define only the cardinal directions as
valid exit names (Evennia's `@tunnel` command also offers this functionality). In this case, the
error starts to look less logical:
> west
Command 'west' is not available. Maybe you meant "@set" or "@reset"?
Since we for our particular game *know* that west is an exit direction, it would be better if the error message just told us that we couldn't go there.
Since we for our particular game *know* that west is an exit direction, it would be better if the
error message just told us that we couldn't go there.
## Adding default error commands
To solve this you need to be aware of how to [write and add new commands](Adding-Command-Tutorial). What you need to do is to create new commands for all directions you want to support in your game. In this example all we'll do is echo an error message, but you could certainly consider more advanced uses. You add these commands to the default command set. Here is an example of such a set of commands:
To solve this you need to be aware of how to [write and add new commands](Adding-Command-Tutorial).
What you need to do is to create new commands for all directions you want to support in your game.
In this example all we'll do is echo an error message, but you could certainly consider more
advanced uses. You add these commands to the default command set. Here is an example of such a set
of commands:
```python
# for example in a file mygame/commands/movecommands.py
@ -50,7 +61,8 @@ class CmdExitErrorWest(CmdExitError):
aliases = ["w"]
```
Make sure to add the directional commands (not their parent) to the `CharacterCmdSet` class in `mygame/commands/default_cmdsets.py`:
Make sure to add the directional commands (not their parent) to the `CharacterCmdSet` class in
`mygame/commands/default_cmdsets.py`:
```python
# in mygame/commands/default_cmdsets.py
@ -68,12 +80,17 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
self.add(movecommands.CmdExitErrorWest())
```
After a `@reload` these commands (assuming you don't get any errors - check your log) will be loaded. What happens henceforth is that if you are in a room with an Exitobject (let's say it's "north"), the proper Exit-command will overload your error command (also named "north"). But if you enter an direction without having a matching exit for it, you will fallback to your default error commands:
After a `@reload` these commands (assuming you don't get any errors - check your log) will be
loaded. What happens henceforth is that if you are in a room with an Exitobject (let's say it's
"north"), the proper Exit-command will overload your error command (also named "north"). But if you
enter an direction without having a matching exit for it, you will fallback to your default error
commands:
> east
You cannot move east.
Further expansions by the exit system (including manipulating the way the Exit command itself is created) can be done by modifying the [Exit typeclass](Typeclasses) directly.
Further expansions by the exit system (including manipulating the way the Exit command itself is
created) can be done by modifying the [Exit typeclass](Typeclasses) directly.
## Additional Comments
@ -89,8 +106,17 @@ So why didn't we create a single error command above? Something like this:
"west", "w"]
#[...]
```
The anwer is that this would *not* work and understanding why is important in order to not be confused when working with commands and command sets.
The anwer is that this would *not* work and understanding why is important in order to not be
confused when working with commands and command sets.
The reason it doesn't work is because Evennia's [command system](Commands) compares commands *both* by `key` and by `aliases`. If *either* of those match, the two commands are considered *identical* as far as cmdset merging system is concerned.
The reason it doesn't work is because Evennia's [command system](Commands) compares commands *both*
by `key` and by `aliases`. If *either* of those match, the two commands are considered *identical*
as far as cmdset merging system is concerned.
So the above example would work fine as long as there were no Exits at all in the room. But what happens when we enter a room with an exit "north"? The Exit's cmdset is merged onto the default one, and since there is an alias match, the system determines our `CmdExitError` to be identical. It is thus overloaded by the Exit command (which also correctly defaults to a higher priority). The result is that you can go through the north exit normally but none of the error messages for the other directions are available since the single error command was completely overloaded by the single matching "north" exit-command.
So the above example would work fine as long as there were no Exits at all in the room. But what
happens when we enter a room with an exit "north"? The Exit's cmdset is merged onto the default one,
and since there is an alias match, the system determines our `CmdExitError` to be identical. It is
thus overloaded by the Exit command (which also correctly defaults to a higher priority). The result
is that you can go through the north exit normally but none of the error messages for the other
directions are available since the single error command was completely overloaded by the single
matching "north" exit-command.

View file

@ -1,7 +1,8 @@
# Developer Central
This page serves as a central nexus for information on using Evennia as well as developing the library itself.
This page serves as a central nexus for information on using Evennia as well as developing the
library itself.
### General Evennia development information
@ -91,13 +92,17 @@ This page serves as a central nexus for information on using Evennia as well as
### Developer brainstorms and whitepages
- [API refactoring](API-refactoring), discussing what parts of the Evennia API needs a refactoring/cleanup/simplification
- [Docs refactoring](Docs-refactoring), discussing how to reorganize and structure this wiki/docs better going forward
- [API refactoring](API-refactoring), discussing what parts of the Evennia API needs a
refactoring/cleanup/simplification
- [Docs refactoring](Docs-refactoring), discussing how to reorganize and structure this wiki/docs
better going forward
- [Webclient brainstorm](Webclient-brainstorm), some ideas for a future webclient gui
- [Roadmap](Roadmap), a tentative list of future major features
- [Change log](https://github.com/evennia/evennia/blob/master/CHANGELOG.md) of big Evennia updates over time
- [Change log](https://github.com/evennia/evennia/blob/master/CHANGELOG.md) of big Evennia updates
over time
[group]: https://groups.google.com/forum/#!forum/evennia
[online-form]: https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0
[issues]: https://github.com/evennia/evennia/issues
[online-form]:
https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0
[issues]: https://github.com/evennia/evennia/issues

View file

@ -1,15 +1,28 @@
# Dialogues in events
- Next tutorial: [adding a voice-operated elevator with events](A-voice-operated-elevator-using-events).
- Next tutorial: [adding a voice-operated elevator with events](A-voice-operated-elevator-using-
events).
This tutorial will walk you through the steps to create several dialogues with characters, using the [in-game Python system](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md). This tutorial assumes the in-game Python system is installed in your game. If it isn't, you can follow the installation steps given in [the documentation on in-game Python](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md), and come back on this tutorial once the system is installed. **You do not need to read** the entire documentation, it's a good reference, but not the easiest way to learn about it. Hence these tutorials.
This tutorial will walk you through the steps to create several dialogues with characters, using the
[in-game Python
system](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md).
This tutorial assumes the in-game Python system is installed in your game. If it isn't, you can
follow the installation steps given in [the documentation on in-game
Python](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md), and
come back on this tutorial once the system is installed. **You do not need to read** the entire
documentation, it's a good reference, but not the easiest way to learn about it. Hence these
tutorials.
The in-game Python system allows to run code on individual objects in some situations. You don't have to modify the source code to add these features, past the installation. The entire system makes it easy to add specific features to some objects, but not all. This is why it can be very useful to create a dialogue system taking advantage of the in-game Python system.
The in-game Python system allows to run code on individual objects in some situations. You don't
have to modify the source code to add these features, past the installation. The entire system
makes it easy to add specific features to some objects, but not all. This is why it can be very
useful to create a dialogue system taking advantage of the in-game Python system.
> What will we try to do?
In this tutorial, we are going to create a basic dialogue to have several characters automatically respond to specific messages said by others.
In this tutorial, we are going to create a basic dialogue to have several characters automatically
respond to specific messages said by others.
## A first example with a first character
@ -17,15 +30,25 @@ Let's create a character to begin with.
@charcreate a merchant
This will create a merchant in the room where you currently are. It doesn't have anything, like a description, you can decorate it a bit if you like.
This will create a merchant in the room where you currently are. It doesn't have anything, like a
description, you can decorate it a bit if you like.
As said above, the in-game Python system consists in linking objects with arbitrary code. This code will be executed in some circumstances. Here, the circumstance is "when someone says something in the same room", and might be more specific like "when someone says hello". We'll decide what code to run (we'll actually type the code in-game). Using the vocabulary of the in-game Python system, we'll create a callback: a callback is just a set of lines of code that will run under some conditions.
As said above, the in-game Python system consists in linking objects with arbitrary code. This code
will be executed in some circumstances. Here, the circumstance is "when someone says something in
the same room", and might be more specific like "when someone says hello". We'll decide what code
to run (we'll actually type the code in-game). Using the vocabulary of the in-game Python system,
we'll create a callback: a callback is just a set of lines of code that will run under some
conditions.
You can have an overview of every "conditions" in which callbacks can be created using the `@call` command (short for `@callback`). You need to give it an object as argument. Here for instance, we could do:
You can have an overview of every "conditions" in which callbacks can be created using the `@call`
command (short for `@callback`). You need to give it an object as argument. Here for instance, we
could do:
@call a merchant
You should see a table with three columns, showing the list of events existing on our newly-created merchant. There are quite a lot of them, as it is, althougn no line of code has been set yet. For our system, you might be more interested by the line describing the `say` event:
You should see a table with three columns, showing the list of events existing on our newly-created
merchant. There are quite a lot of them, as it is, althougn no line of code has been set yet. For
our system, you might be more interested by the line describing the `say` event:
| say | 0 (0) | After another character has said something in |
| | | the character's room. |
@ -36,11 +59,18 @@ We'll create a callback on the `say` event, called when we say "hello" in the me
Before seeing what this command displays, let's see the command syntax itself:
- `@call` is the command name, `/add` is a switch. You can read the help of the command to get the help of available switches and a brief overview of syntax.
- We then enter the object's name, here "a merchant". You can enter the ID too ("#3" in my case), which is useful to edit the object when you're not in the same room. You can even enter part of the name, as usual.
- `@call` is the command name, `/add` is a switch. You can read the help of the command to get the
help of available switches and a brief overview of syntax.
- We then enter the object's name, here "a merchant". You can enter the ID too ("#3" in my case),
which is useful to edit the object when you're not in the same room. You can even enter part of the
name, as usual.
- An equal sign, a simple separator.
- The event's name. Here, it's "say". The available events are displayed when you use `@call` without switch.
- After a space, we enter the conditions in which this callback should be called. Here, the conditions represent what the other character should say. We enter "hello". Meaning that if someone says something containing "hello" in the room, the callback we are now creating will be called.
- The event's name. Here, it's "say". The available events are displayed when you use `@call`
without switch.
- After a space, we enter the conditions in which this callback should be called. Here, the
conditions represent what the other character should say. We enter "hello". Meaning that if
someone says something containing "hello" in the room, the callback we are now creating will be
called.
When you enter this command, you should see something like this:
@ -70,10 +100,16 @@ Variables you can use in this event:
That's some list of information. What's most important to us now is:
- The "say" event is called whenever someone else speaks in the room.
- We can set callbacks to fire when specific keywords are present in the phrase by putting them as additional parameters. Here we have set this parameter to "hello". We can have several keywords separated by a comma (we'll see this in more details later).
- We have three default variables we can use in this callback: `speaker` which contains the character who speaks, `character` which contains the character who's modified by the in-game Python system (here, or merchant), and `message` which contains the spoken phrase.
- We can set callbacks to fire when specific keywords are present in the phrase by putting them as
additional parameters. Here we have set this parameter to "hello". We can have several keywords
separated by a comma (we'll see this in more details later).
- We have three default variables we can use in this callback: `speaker` which contains the
character who speaks, `character` which contains the character who's modified by the in-game Python
system (here, or merchant), and `message` which contains the spoken phrase.
This concept of variables is important. If it makes things more simple to you, think of them as parameters in a function: they can be used inside of the function body because they have been set when the function was called.
This concept of variables is important. If it makes things more simple to you, think of them as
parameters in a function: they can be used inside of the function body because they have been set
when the function was called.
This command has opened an editor where we can type our Python code.
@ -86,7 +122,8 @@ This command has opened an editor where we can type our Python code.
For our first test, let's type something like:
```python
character.location.msg_contents("{character} shrugs and says: 'well, yes, hello to you!'", mapping=dict(character=character))
character.location.msg_contents("{character} shrugs and says: 'well, yes, hello to you!'",
mapping=dict(character=character))
```
Once you have entered this line, you can type `:wq` to save the editor and quit it.
@ -102,10 +139,16 @@ If you say something that doesn't contain "hello", our callback won't execute.
**In summary**:
1. When we say something in the room, using the "say" command, the "say" event of all characters (except us) is called.
2. The in-game Python system looks at what we have said, and checks whether one of our callbacks in the "say" event contains a keyword that we have spoken.
1. When we say something in the room, using the "say" command, the "say" event of all characters
(except us) is called.
2. The in-game Python system looks at what we have said, and checks whether one of our callbacks in
the "say" event contains a keyword that we have spoken.
3. If so, call it, defining the event variables as we have seen.
4. The callback is then executed as normal Python code. Here we have called the `msg_contents` method on the character's location (probably a room) to display a message to the entire room. We have also used mapping to easily display the character's name. This is not specific to the in-game Python system. If you feel overwhelmed by the code we've used, just shorten it and use something more simple, for instance:
4. The callback is then executed as normal Python code. Here we have called the `msg_contents`
method on the character's location (probably a room) to display a message to the entire room. We
have also used mapping to easily display the character's name. This is not specific to the in-game
Python system. If you feel overwhelmed by the code we've used, just shorten it and use something
more simple, for instance:
```python
speaker.msg("You have said something to me.")
@ -113,17 +156,20 @@ speaker.msg("You have said something to me.")
## The same callback for several keywords
It's easy to create a callback that will be triggered if the sentence contains one of several keywords.
It's easy to create a callback that will be triggered if the sentence contains one of several
keywords.
@call/add merchant = say trade, trader, goods
And in the editor that opens:
```python
character.location.msg_contents("{character} says: 'Ho well, trade's fine as long as roads are safe.'", mapping=dict(character=character))
character.location.msg_contents("{character} says: 'Ho well, trade's fine as long as roads are
safe.'", mapping=dict(character=character))
```
Then you can say something with either "trade", "trader" or "goods" in your sentence, which should call the callback:
Then you can say something with either "trade", "trader" or "goods" in your sentence, which should
call the callback:
```
You say, "and how is your trade going?"
@ -134,18 +180,25 @@ We can set several keywords when adding the callback. We just need to separate
## A longer callback
So far, we have only set one line in our callbacks. Which is useful, but we often need more. For an entire dialogue, you might want to do a bit more than that.
So far, we have only set one line in our callbacks. Which is useful, but we often need more. For
an entire dialogue, you might want to do a bit more than that.
@call/add merchant = say bandit, bandits
And in the editor you can paste the following lines:
```python
character.location.msg_contents("{character} says: 'Bandits he?'", mapping=dict(character=character))
character.location.msg_contents("{character} scratches his head, considering.", mapping=dict(character=character))
character.location.msg_contents("{character} whispers: 'Aye, saw some of them, north from here. No trouble o' mine, but...'", mapping=dict(character=character))
speaker.msg("{character} looks at you more closely.".format(character=character.get_display_name(speaker)))
speaker.msg("{character} continues in a low voice: 'Ain't my place to say, but if you need to find 'em, they're encamped some distance away from the road, I guess near a cave or something.'.".format(character=character.get_display_name(speaker)))
character.location.msg_contents("{character} says: 'Bandits he?'",
mapping=dict(character=character))
character.location.msg_contents("{character} scratches his head, considering.",
mapping=dict(character=character))
character.location.msg_contents("{character} whispers: 'Aye, saw some of them, north from here. No
trouble o' mine, but...'", mapping=dict(character=character))
speaker.msg("{character} looks at you more
closely.".format(character=character.get_display_name(speaker)))
speaker.msg("{character} continues in a low voice: 'Ain't my place to say, but if you need to find
'em, they're encamped some distance away from the road, I guess near a cave or
something.'.".format(character=character.get_display_name(speaker)))
```
Now try to ask the merchant about bandits:
@ -156,24 +209,41 @@ a merchant(#3) says: 'Bandits he?'
a merchant(#3) scratches his head, considering.
a merchant(#3) whispers: 'Aye, saw some of them, north from here. No trouble o' mine, but...'
a merchant(#3) looks at you more closely.
a merchant(#3) continues in a low voice: 'Ain't my place to say, but if you need to find 'em, they're encamped some distance away from the road, I guess near a cave or something.'.
a merchant(#3) continues in a low voice: 'Ain't my place to say, but if you need to find 'em,
they're encamped some distance away from the road, I guess near a cave or something.'.
```
Notice here that the first lines of dialogue are spoken to the entire room, but then the merchant is talking directly to the speaker, and only the speaker hears it. There's no real limit to what you can do with this.
Notice here that the first lines of dialogue are spoken to the entire room, but then the merchant is
talking directly to the speaker, and only the speaker hears it. There's no real limit to what you
can do with this.
- You can set a mood system, storing attributes in the NPC itself to tell you in what mood he is, which will influence the information he will give... perhaps the accuracy of it as well.
- You can set a mood system, storing attributes in the NPC itself to tell you in what mood he is,
which will influence the information he will give... perhaps the accuracy of it as well.
- You can add random phrases spoken in some context.
- You can use other actions (you're not limited to having the merchant say something, you can ask him to move, gives you something, attack if you have a combat system, or whatever else).
- You can use other actions (you're not limited to having the merchant say something, you can ask
him to move, gives you something, attack if you have a combat system, or whatever else).
- The callbacks are in pure Python, so you can write conditions or loops.
- You can add in "pauses" between some instructions using chained events. This tutorial won't describe how to do that however. You already have a lot to play with.
- You can add in "pauses" between some instructions using chained events. This tutorial won't
describe how to do that however. You already have a lot to play with.
## Tutorial F.A.Q.
- **Q:** can I create several characters who would answer to specific dialogue?
- **A:** of course. Te in-game Python system is so powerful because you can set unique code for various objects. You can have several characters answering to different things. You can even have different characters in the room answering to greetings. All callbacks will be executed one after another.
- **A:** of course. Te in-game Python system is so powerful because you can set unique code for
various objects. You can have several characters answering to different things. You can even have
different characters in the room answering to greetings. All callbacks will be executed one after
another.
- **Q:** can I have two characters answering to the same dialogue in exactly the same way?
- **A:** It's possible but not so easy to do. Usually, event grouping is set in code, and depends on different games. However, if it is for some infrequent occurrences, it's easy to do using [chained events](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md#chained-events).
- **A:** It's possible but not so easy to do. Usually, event grouping is set in code, and depends
on different games. However, if it is for some infrequent occurrences, it's easy to do using
[chained
events](https://github.com/evennia/evennia/blob/master/evennia/contrib/ingame_python/README.md#chained-
events).
- **Q:** is it possible to deploy callbacks on all characters sharing the same prototype?
- **A:** not out of the box. This depends on individual settings in code. One can imagine that all characters of some type would share some events, but this is game-specific. Rooms of the same zone could share the same events as well. It is possible to do but requires modification of the source code.
- **A:** not out of the box. This depends on individual settings in code. One can imagine that all
characters of some type would share some events, but this is game-specific. Rooms of the same zone
could share the same events as well. It is possible to do but requires modification of the source
code.
- Next tutorial: [adding a voice-operated elevator with events](A-voice-operated-elevator-using-events).
- Next tutorial: [adding a voice-operated elevator with events](A-voice-operated-elevator-using-
events).

View file

@ -5,22 +5,33 @@ This is an overview of the directories relevant to Evennia coding.
## The Game directory
The game directory is created with `evennia --init <name>`. In the Evennia documentation we always assume it's called `mygame`. Apart from the `server/` subfolder within, you could reorganize this folder if you preferred a different code structure for your game.
The game directory is created with `evennia --init <name>`. In the Evennia documentation we always
assume it's called `mygame`. Apart from the `server/` subfolder within, you could reorganize this
folder if you preferred a different code structure for your game.
- `mygame/`
- `commands/` - Overload default [Commands](Commands) or add your own Commands/[Command sets](Command-Sets) here.
- `commands/` - Overload default [Commands](Commands) or add your own Commands/[Command
sets](Command-Sets) here.
- `server`/ - The structure of this folder should not change since Evennia expects it.
- [`conf/`](https://github.com/evennia/evennia/tree/master/evennia/game_template/server) - All server configuration files sits here. The most important file is `settings.py`.
- [`conf/`](https://github.com/evennia/evennia/tree/master/evennia/game_template/server) - All
server configuration files sits here. The most important file is `settings.py`.
- `logs/` - Portal log files are stored here (Server is logging to the terminal by default)
- `typeclasses/` - this folder contains empty templates for overloading default game entities of Evennia. Evennia will automatically use the changes in those templates for the game entities it creates.
- `typeclasses/` - this folder contains empty templates for overloading default game entities of
Evennia. Evennia will automatically use the changes in those templates for the game entities it
creates.
- `web/` - This holds the [Web features](Web-Features) of your game.
- `world/` - this is a "miscellaneous" folder holding everything related to the world you are building, such as build scripts and rules modules that don't fit with one of the other folders.
- `world/` - this is a "miscellaneous" folder holding everything related to the world you are
building, such as build scripts and rules modules that don't fit with one of the other folders.
## Evennia library layout:
If you cloned the GIT repo following the instructions, you will have a folder named `evennia`. The top level of it contains Python package specific stuff such as a readme file, `setup.py` etc. It also has two subfolders`bin/` and `evennia/` (again).
If you cloned the GIT repo following the instructions, you will have a folder named `evennia`. The
top level of it contains Python package specific stuff such as a readme file, `setup.py` etc. It
also has two subfolders`bin/` and `evennia/` (again).
The `bin/` directory holds OS-specific binaries that will be used when installing Evennia with `pip` as per the [Getting started](Getting-Started) instructions. The library itself is in the `evennia` subfolder. From your code you will access this subfolder simply by `import evennia`.
The `bin/` directory holds OS-specific binaries that will be used when installing Evennia with `pip`
as per the [Getting started](Getting-Started) instructions. The library itself is in the `evennia`
subfolder. From your code you will access this subfolder simply by `import evennia`.
- evennia
- [`__init__.py`](Evennia-API) - The "flat API" of Evennia resides here.
@ -38,11 +49,20 @@ The `bin/` directory holds OS-specific binaries that will be used when installin
- [`scripts/`](Scripts) - Out-of-game entities equivalence to Objects, also with timer support.
- [`server/`](Portal-And-Server) - Core server code and Session handling.
- `portal/` - Portal proxy and connection protocols.
- [`settings_default.py`](Server-Conf#Settings-file) - Root settings of Evennia. Copy settings from here to `mygame/server/settings.py` file.
- [`settings_default.py`](Server-Conf#Settings-file) - Root settings of Evennia. Copy settings
from here to `mygame/server/settings.py` file.
- [`typeclasses/`](Typeclasses) - Abstract classes for the typeclass storage and database system.
- [`utils/`](Coding-Utils) - Various miscellaneous useful coding resources.
- [`web/`](Web-Features) - Web resources and webserver. Partly copied into game directory on initialization.
- [`web/`](Web-Features) - Web resources and webserver. Partly copied into game directory on
initialization.
All directories contain files ending in `.py`. These are Python *modules* and are the basic units of Python code. The roots of directories also have (usually empty) files named `__init__.py`. These are required by Python so as to be able to find and import modules in other directories. When you have run Evennia at least once you will find that there will also be `.pyc` files appearing, these are pre-compiled binary versions of the `.py` files to speed up execution.
All directories contain files ending in `.py`. These are Python *modules* and are the basic units of
Python code. The roots of directories also have (usually empty) files named `__init__.py`. These are
required by Python so as to be able to find and import modules in other directories. When you have
run Evennia at least once you will find that there will also be `.pyc` files appearing, these are
pre-compiled binary versions of the `.py` files to speed up execution.
The root of the `evennia` folder has an `__init__.py` file containing the "[flat API](Evennia-API)". This holds shortcuts to various subfolders in the evennia library. It is provided to make it easier to find things; it allows you to just import `evennia` and access things from that rather than having to import from their actual locations inside the source tree.
The root of the `evennia` folder has an `__init__.py` file containing the "[flat API](Evennia-API)".
This holds shortcuts to various subfolders in the evennia library. It is provided to make it easier
to find things; it allows you to just import `evennia` and access things from that rather than
having to import from their actual locations inside the source tree.

View file

@ -2,19 +2,28 @@
This is a whitepage for free discussion about the wiki docs and refactorings needed.
Note that this is not a forum. To keep things clean, each opinion text should ideally present a clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be held in chat or forum.
Note that this is not a forum. To keep things clean, each opinion text should ideally present a
clear argument or lay out a suggestion. Asking for clarification and any side-discussions should be
held in chat or forum.
### Griatch (Aug 13, 2019)
This is how to make a discussion entry for the whitepage. Use any markdown formatting you need. Also remember to copy your work to the clipboard before saving the page since if someone else edited the page since you started, you'll have to reload and write again.
This is how to make a discussion entry for the whitepage. Use any markdown formatting you need. Also
remember to copy your work to the clipboard before saving the page since if someone else edited the
page since you started, you'll have to reload and write again.
#### (Sept 23, 2019)
[This (now closed) issue by DamnedScholar](https://github.com/evennia/evennia/issues/1431) gives the following suggestion:
> I think it would be useful for the pages that explain how to use various features of Evennia to have explicit and easily visible links to the respective API entry or entries. Some pages do, but not all. I imagine this as a single entry at the top of the page [...].
[This (now closed) issue by DamnedScholar](https://github.com/evennia/evennia/issues/1431) gives the
following suggestion:
> I think it would be useful for the pages that explain how to use various features of Evennia to
have explicit and easily visible links to the respective API entry or entries. Some pages do, but
not all. I imagine this as a single entry at the top of the page [...].
[This (now closed) issue by taladan](https://github.com/evennia/evennia/issues/1578) gives the following suggestion:
> It would help me (and probably a couple of others) if there is a way to show the file path where a particular thing exists. Maybe up under the 'last edited' line we could have a line like:
[This (now closed) issue by taladan](https://github.com/evennia/evennia/issues/1578) gives the
following suggestion:
> It would help me (and probably a couple of others) if there is a way to show the file path where a
particular thing exists. Maybe up under the 'last edited' line we could have a line like:
evennia/locks/lockhandler.py
This would help in development to quickly refer to where a resource is located.
@ -22,15 +31,22 @@ This would help in development to quickly refer to where a resource is located.
### Kovitikus (Sept. 11, 2019)
[Batch Code](Batch-Code-Processor) should have a link in the developer area. It is currently only listed in the tutorials section as an afterthought to a tutorial title.
[Batch Code](Batch-Code-Processor) should have a link in the developer area. It is currently only
listed in the tutorials section as an afterthought to a tutorial title.
***
In regards to the general structure of each wiki page: I'd like to see a table of contents at the top of each one, so that it can be quickly navigated and is immediately apparent what sections are covered on the page. Similar to the current [Getting Started](Getting-Started) page.
In regards to the general structure of each wiki page: I'd like to see a table of contents at the
top of each one, so that it can be quickly navigated and is immediately apparent what sections are
covered on the page. Similar to the current [Getting Started](Getting-Started) page.
***
The structuring of the page should also include a quick reference cheatsheet for certain aspects. Such as [Tags](Tags) including a quick reference section at the top that lists an example of every available method you can use in a clear and consistent format, along with a comment. Readers shouldn't have to decipher the article to gather such basic information and it should instead be available at first glance.
The structuring of the page should also include a quick reference cheatsheet for certain aspects.
Such as [Tags](Tags) including a quick reference section at the top that lists an example of every
available method you can use in a clear and consistent format, along with a comment. Readers
shouldn't have to decipher the article to gather such basic information and it should instead be
available at first glance.
Example of a quick reference:
@ -62,7 +78,10 @@ ETC...
***
In regards to comment structure, I often find that smushing together lines with comments to be too obscure. White space should be used to clearly delineate what information the comment is for. I understand that the current format is that a comment references whatever is below it, but newbies may not know that until they realize it.
In regards to comment structure, I often find that smushing together lines with comments to be too
obscure. White space should be used to clearly delineate what information the comment is for. I
understand that the current format is that a comment references whatever is below it, but newbies
may not know that until they realize it.
Example of poor formatting:
```
@ -90,6 +109,9 @@ If I want to find information on the correct syntax for is_typeclass(), here's w
* Ctrl+F on utils page. No results.
* Ctrl+F on utils.utils page. No results.
* Ctrl+F in my IDE. Results.
* Fortunately, there's only one result for def is_typeclass. If this was at_look, there would be several results, and I'd have to go through each of those individually, and most of them would just call return_appearance
* Fortunately, there's only one result for def is_typeclass. If this was at_look, there would be
several results, and I'd have to go through each of those individually, and most of them would just
call return_appearance
An important part of a refactor, in my opinion, is separating out the "Tutorials" from the "Reference" documentation.
An important part of a refactor, in my opinion, is separating out the "Tutorials" from the
"Reference" documentation.

View file

@ -3,20 +3,38 @@
## Introduction
An often desired feature in a MUD is to show an in-game map to help navigation. The [Static in-game map](Static-In-Game-Map) tutorial solves this by creating a *static* map, meaning the map is pre-drawn once and for all - the rooms are then created to match that map. When walking around, parts of the static map is then cut out and displayed next to the room description.
An often desired feature in a MUD is to show an in-game map to help navigation. The [Static in-game
map](Static-In-Game-Map) tutorial solves this by creating a *static* map, meaning the map is pre-
drawn once and for all - the rooms are then created to match that map. When walking around, parts of
the static map is then cut out and displayed next to the room description.
In this tutorial we'll instead do it the other way around; We will dynamically draw the map based on the relationships we find between already existing rooms.
In this tutorial we'll instead do it the other way around; We will dynamically draw the map based on
the relationships we find between already existing rooms.
## The Grid of Rooms
There are at least two requirements needed for this tutorial to work.
1. The structure of your mud has to follow a logical layout. Evennia supports the layout of your world to be 'logically' impossible with rooms looping to themselves or exits leading to the other side of the map. Exits can also be named anything, from "jumping out the window" to "into the fifth dimension". This tutorial assumes you can only move in the cardinal directions (N, E, S and W).
2. Rooms must be connected and linked together for the map to be generated correctly. Vanilla Evennia comes with a admin command [@tunnel](Default-Command-Help#tunnel-cmdtunnel) that allows a user to create rooms in the cardinal directions, but additional work is needed to assure that rooms are connected. For example, if you `@tunnel east` and then immediately do `@tunnel west` you'll find that you have created two completely stand-alone rooms. So care is needed if you want to create a "logical" layout. In this tutorial we assume you have such a grid of rooms that we can generate the map from.
1. The structure of your mud has to follow a logical layout. Evennia supports the layout of your
world to be 'logically' impossible with rooms looping to themselves or exits leading to the other
side of the map. Exits can also be named anything, from "jumping out the window" to "into the fifth
dimension". This tutorial assumes you can only move in the cardinal directions (N, E, S and W).
2. Rooms must be connected and linked together for the map to be generated correctly. Vanilla
Evennia comes with a admin command [@tunnel](Default-Command-Help#tunnel-cmdtunnel) that allows a
user to create rooms in the cardinal directions, but additional work is needed to assure that rooms
are connected. For example, if you `@tunnel east` and then immediately do `@tunnel west` you'll find
that you have created two completely stand-alone rooms. So care is needed if you want to create a
"logical" layout. In this tutorial we assume you have such a grid of rooms that we can generate the
map from.
## Concept
Before getting into the code, it is beneficial to understand and conceptualize how this is going to work. The idea is analogous to a worm that starts at your current position. It chooses a direction and 'walks' outward from it, mapping its route as it goes. Once it has traveled a pre-set distance it stops and starts over in another direction. An important note is that we want a system which is easily callable and not too complicated. Therefore we will wrap this entire code into a custom Python class (not a typeclass as this doesn't use any core objects from evennia itself).
Before getting into the code, it is beneficial to understand and conceptualize how this is going to
work. The idea is analogous to a worm that starts at your current position. It chooses a direction
and 'walks' outward from it, mapping its route as it goes. Once it has traveled a pre-set distance
it stops and starts over in another direction. An important note is that we want a system which is
easily callable and not too complicated. Therefore we will wrap this entire code into a custom
Python class (not a typeclass as this doesn't use any core objects from evennia itself).
We are going to create something that displays like this when you type 'look':
@ -33,11 +51,17 @@ We are going to create something that displays like this when you type 'look':
Exits: North, East, South
```
Your current location is defined by `[@]` while the `[.]`s are other rooms that the "worm" has seen since departing from your location.
Your current location is defined by `[@]` while the `[.]`s are other rooms that the "worm" has seen
since departing from your location.
## Setting up the Map Display
First we must define the components for displaying the map. For the "worm" to know what symbol to draw on the map we will have it check an Attribute on the room it visits called `sector_type`. For this tutorial we understand two symbols - a normal room and the room with us in it. We also define a fallback symbol for rooms without said Attribute - that way the map will still work even if we didn't prepare the room correctly. Assuming your game folder is named `mygame`, we create this code in `mygame/world/map.py.`
First we must define the components for displaying the map. For the "worm" to know what symbol to
draw on the map we will have it check an Attribute on the room it visits called `sector_type`. For
this tutorial we understand two symbols - a normal room and the room with us in it. We also define a
fallback symbol for rooms without said Attribute - that way the map will still work even if we
didn't prepare the room correctly. Assuming your game folder is named `mygame`, we create this code
in `mygame/world/map.py.`
```python
# in mygame/world/map.py
@ -49,7 +73,9 @@ SYMBOLS = { None : ' . ', # for rooms without sector_type Attribute
'SECT_INSIDE': '[.]' }
```
Since trying to access an unset Attribute returns `None`, this means rooms without the `sector_type` Atttribute will show as ` . `. Next we start building the custom class `Map`. It will hold all methods we need.
Since trying to access an unset Attribute returns `None`, this means rooms without the `sector_type`
Atttribute will show as ` . `. Next we start building the custom class `Map`. It will hold all
methods we need.
```python
# in mygame/world/map.py
@ -66,12 +92,18 @@ class Map(object):
```
- `self.caller` is normally your Character object, the one using the map.
- `self.max_width/length` determine the max width and length of the map that will be generated. Note that it's important that these variables are set to *odd* numbers to make sure the display area has a center point.
- ` self.worm_has_mapped` is building off the worm analogy above. This dictionary will store all rooms the "worm" has mapped as well as its relative position within the grid. This is the most important variable as it acts as a 'checker' and 'address book' that is able to tell us where the worm has been and what it has mapped so far.
- `self.max_width/length` determine the max width and length of the map that will be generated. Note
that it's important that these variables are set to *odd* numbers to make sure the display area has
a center point.
- ` self.worm_has_mapped` is building off the worm analogy above. This dictionary will store all
rooms the "worm" has mapped as well as its relative position within the grid. This is the most
important variable as it acts as a 'checker' and 'address book' that is able to tell us where the
worm has been and what it has mapped so far.
- `self.curX/Y` are coordinates representing the worm's current location on the grid.
Before any sort of mapping can actually be done we need to create an empty display area and do some sanity checks on it by using the following methods.
Before any sort of mapping can actually be done we need to create an empty display area and do some
sanity checks on it by using the following methods.
```python
# in mygame/world/map.py
@ -96,7 +128,8 @@ class Map(object):
else False
```
Before we can set our worm on its way, we need to know some of the computer science behind all this called 'Graph Traversing'. In Pseudo code what we are trying to accomplish is this:
Before we can set our worm on its way, we need to know some of the computer science behind all this
called 'Graph Traversing'. In Pseudo code what we are trying to accomplish is this:
```python
# pseudo code
@ -116,33 +149,47 @@ def draw_room_on_map(room, max_distance):
self.draw_room_on_map(exit.destination, max_distance - 1)
```
The beauty of Python is that our actual code of doing this doesn't differ much if at all from this Pseudo code example.
The beauty of Python is that our actual code of doing this doesn't differ much if at all from this
Pseudo code example.
- `max_distance` is a variable indicating to our Worm how many rooms AWAY from your current location will it map. Obviously the larger the number the more time it will take if your current location has many many rooms around you.
- `max_distance` is a variable indicating to our Worm how many rooms AWAY from your current location
will it map. Obviously the larger the number the more time it will take if your current location has
many many rooms around you.
The first hurdle here is what value to use for 'max_distance'. There is no reason for the worm to travel further than what is actually displayed to you. For example, if your current location is placed in the center of a display area of size `max_length = max_width = 9`, then the worm need only go `4` spaces in either direction:
The first hurdle here is what value to use for 'max_distance'. There is no reason for the worm to
travel further than what is actually displayed to you. For example, if your current location is
placed in the center of a display area of size `max_length = max_width = 9`, then the worm need only
go `4` spaces in either direction:
```
[.][.][.][.][@][.][.][.][.]
4 3 2 1 0 1 2 3 4
```
The max_distance can be set dynamically based on the size of the display area. As your width/length changes it becomes a simple algebraic linear relationship which is simply `max_distance = (min(max_width, max_length) -1) / 2`.
The max_distance can be set dynamically based on the size of the display area. As your width/length
changes it becomes a simple algebraic linear relationship which is simply `max_distance =
(min(max_width, max_length) -1) / 2`.
## Building the Mapper
Now we can start to fill our Map object with some methods. We are still missing a few methods that are very important:
Now we can start to fill our Map object with some methods. We are still missing a few methods that
are very important:
* `self.draw(self, room)` - responsible for actually drawing room to grid.
* `self.has_drawn(self, room)` - checks to see if the room has been mapped and worm has already been here.
* `self.median(self, number)` - a simple utility method that finds the median (middle point) from 0, n
* `self.update_pos(self, room, exit_name)` - updates the worm's physical position by reassigning self.curX/Y. .accordingly
* `self.start_loc_on_grid(self)` - the very first initial draw on the grid representing your location in the middle of the grid
* `self.has_drawn(self, room)` - checks to see if the room has been mapped and worm has already been
here.
* `self.median(self, number)` - a simple utility method that finds the median (middle point) from 0,
n
* `self.update_pos(self, room, exit_name)` - updates the worm's physical position by reassigning
self.curX/Y. .accordingly
* `self.start_loc_on_grid(self)` - the very first initial draw on the grid representing your
location in the middle of the grid
* 'self.show_map` - after everything is done convert the map into a readable string`
* `self.draw_room_on_map(self, room, max_distance)` - the main method that ties it all together.`
Now that we know which methods we need, let's refine our initial `__init__(self)` to pass some conditional statements and set it up to start building the display.
Now that we know which methods we need, let's refine our initial `__init__(self)` to pass some
conditional statements and set it up to start building the display.
```python
@ -167,9 +214,11 @@ class Map(object):
```
Here we check to see if the parameters for the grid are okay, then we create an empty canvas and map our initial location as the first room!
Here we check to see if the parameters for the grid are okay, then we create an empty canvas and map
our initial location as the first room!
As mentioned above, the code for the `self.draw_room_on_map()` is not much different than the Pseudo code. The method is shown below:
As mentioned above, the code for the `self.draw_room_on_map()` is not much different than the Pseudo
code. The method is shown below:
```python
# in mygame/world/map.py, in the Map class
@ -229,7 +278,10 @@ def start_loc_on_grid(self):
self.curX, self.curY = x, y # updating worms current location
```
After the system has drawn the current map it checks to see if the `max_distance` is `0` (since this is the inital start phase it is not). Now we handle the iteration once we have each individual exit in the room. The first thing it does is check if the room the Worm is in has been mapped already.. lets define that...
After the system has drawn the current map it checks to see if the `max_distance` is `0` (since this
is the inital start phase it is not). Now we handle the iteration once we have each individual exit
in the room. The first thing it does is check if the room the Worm is in has been mapped already..
lets define that...
```python
@ -237,7 +289,9 @@ def has_drawn(self, room):
return True if room in self.worm_has_mapped.keys() else False
```
If `has_drawn` returns `False` that means the worm has found a room that hasn't been mapped yet. It will then 'move' there. The self.curX/Y sort of lags behind, so we have to make sure to track the position of the worm; we do this in `self.update_pos()` below.
If `has_drawn` returns `False` that means the worm has found a room that hasn't been mapped yet. It
will then 'move' there. The self.curX/Y sort of lags behind, so we have to make sure to track the
position of the worm; we do this in `self.update_pos()` below.
```python
def update_pos(self, room, exit_name):
@ -258,9 +312,11 @@ def update_pos(self, room, exit_name):
self.curX += 1
```
Once the system updates the position of the worm it feeds the new room back into the original `draw_room_on_map()` and starts the process all over again..
Once the system updates the position of the worm it feeds the new room back into the original
`draw_room_on_map()` and starts the process all over again..
That is essentially the entire thing. The final method is to bring it all together and make a nice presentational string out of it using the `self.show_map()` method.
That is essentially the entire thing. The final method is to bring it all together and make a nice
presentational string out of it using the `self.show_map()` method.
```python
def show_map(self):
@ -274,9 +330,11 @@ def show_map(self):
## Using the Map
In order for the map to get triggered we store it on the Room typeclass. If we put it in `return_appearance` we will get the map back every time we look at the room.
In order for the map to get triggered we store it on the Room typeclass. If we put it in
`return_appearance` we will get the map back every time we look at the room.
> `return_appearance` is a default Evennia hook available on all objects; it is called e.g. by the `look` command to get the description of something (the room in this case).
> `return_appearance` is a default Evennia hook available on all objects; it is called e.g. by the
`look` command to get the description of something (the room in this case).
```python
# in mygame/typeclasses/rooms.py
@ -295,11 +353,24 @@ class Room(DefaultRoom):
return string
```
Obviously this method of generating maps doesn't take into account of any doors or exits that are hidden.. etc.. but hopefully it serves as a good base to start with. Like previously mentioned, it is very important to have a solid foundation on rooms before implementing this. You can try this on vanilla evennia by using @tunnel and essentially you can just create a long straight/edgy non-looping rooms that will show on your in-game map.
Obviously this method of generating maps doesn't take into account of any doors or exits that are
hidden.. etc.. but hopefully it serves as a good base to start with. Like previously mentioned, it
is very important to have a solid foundation on rooms before implementing this. You can try this on
vanilla evennia by using @tunnel and essentially you can just create a long straight/edgy non-
looping rooms that will show on your in-game map.
The above example will display the map above the room description. You could also use an [EvTable](github:evennia.utils.evtable) to place description and map next to each other. Some other things you can do is to have a [Command](Commands) that displays with a larger radius, maybe with a legend and other features.
The above example will display the map above the room description. You could also use an
[EvTable](github:evennia.utils.evtable) to place description and map next to each other. Some other
things you can do is to have a [Command](Commands) that displays with a larger radius, maybe with a
legend and other features.
Below is the whole `map.py` for your reference. You need to update your `Room` typeclass (see above) to actually call it. Remember that to see different symbols for a location you also need to set the `sector_type` Attribute on the room to one of the keys in the `SYMBOLS` dictionary. So in this example, to make a room be mapped as `[.]` you would set the room's `sector_type` to `"SECT_INSIDE"`. Try it out with `@set here/sector_type = "SECT_INSIDE"`. If you wanted all new rooms to have a given sector symbol, you could change the default in the `SYMBOLS´ dictionary below, or you could add the Attribute in the Room's `at_object_creation` method.
Below is the whole `map.py` for your reference. You need to update your `Room` typeclass (see above)
to actually call it. Remember that to see different symbols for a location you also need to set the
`sector_type` Attribute on the room to one of the keys in the `SYMBOLS` dictionary. So in this
example, to make a room be mapped as `[.]` you would set the room's `sector_type` to
`"SECT_INSIDE"`. Try it out with `@set here/sector_type = "SECT_INSIDE"`. If you wanted all new
rooms to have a given sector symbol, you could change the default in the `SYMBOLS´ dictionary below,
or you could add the Attribute in the Room's `at_object_creation` method.
```python
#mygame/world/map.py
@ -419,4 +490,6 @@ class Map(object):
## Final Comments
The Dynamic map could be expanded with further capabilities. For example, it could mark exits or allow NE, SE etc directions as well. It could have colors for different terrain types. One could also look into up/down directions and figure out how to display that in a good way.
The Dynamic map could be expanded with further capabilities. For example, it could mark exits or
allow NE, SE etc directions as well. It could have colors for different terrain types. One could
also look into up/down directions and figure out how to display that in a good way.

View file

@ -1,7 +1,9 @@
# EvEditor
Evennia offers a powerful in-game line editor in `evennia.utils.eveditor.EvEditor`. This editor, mimicking the well-known VI line editor. It offers line-by-line editing, undo/redo, line deletes, search/replace, fill, dedent and more.
Evennia offers a powerful in-game line editor in `evennia.utils.eveditor.EvEditor`. This editor,
mimicking the well-known VI line editor. It offers line-by-line editing, undo/redo, line deletes,
search/replace, fill, dedent and more.
### Launching the editor
@ -16,10 +18,15 @@ EvEditor(caller,
```
- `caller` (Object or Account): The user of the editor.
- `loadfunc` (callable, optional): This is a function called when the editor is first started. It is called with `caller` as its only argument. The return value from this function is used as the starting text in the editor buffer.
- `savefunc` (callable, optional): This is called when the user saves their buffer in the editor is called with two arguments, `caller` and `buffer`, where `buffer` is the current buffer.
- `quitfunc` (callable, optional): This is called when the user quits the editor. If given, all cleanup and exit messages to the user must be handled by this function.
- `key` (str, optional): This text will be displayed as an identifier and reminder while editing. It has no other mechanical function.
- `loadfunc` (callable, optional): This is a function called when the editor is first started. It
is called with `caller` as its only argument. The return value from this function is used as the
starting text in the editor buffer.
- `savefunc` (callable, optional): This is called when the user saves their buffer in the editor is
called with two arguments, `caller` and `buffer`, where `buffer` is the current buffer.
- `quitfunc` (callable, optional): This is called when the user quits the editor. If given, all
cleanup and exit messages to the user must be handled by this function.
- `key` (str, optional): This text will be displayed as an identifier and reminder while editing.
It has no other mechanical function.
- `persistent` (default `False`): if set to `True`, the editor will survive a reboot.
### Example of usage
@ -60,7 +67,10 @@ class CmdSetTestAttr(Command):
### Persistent editor
If you set the `persistent` keyword to `True` when creating the editor, it will remain open even when reloading the game. In order to be persistent, an editor needs to have its callback functions (`loadfunc`, `savefunc` and `quitfunc`) as top-level functions defined in the module. Since these functions will be stored, Python will need to find them.
If you set the `persistent` keyword to `True` when creating the editor, it will remain open even
when reloading the game. In order to be persistent, an editor needs to have its callback functions
(`loadfunc`, `savefunc` and `quitfunc`) as top-level functions defined in the module. Since these
functions will be stored, Python will need to find them.
```python
from evennia import Command
@ -99,7 +109,8 @@ class CmdSetTestAttr(Command):
### Line editor usage
The editor mimics the `VIM` editor as best as possible. The below is an excerpt of the return from the in-editor help command (`:h`).
The editor mimics the `VIM` editor as best as possible. The below is an excerpt of the return from
the in-editor help command (`:h`).
```
<txt> - any non-command is appended to the end of the buffer.
@ -145,14 +156,26 @@ The editor mimics the `VIM` editor as best as possible. The below is an excerpt
### The EvEditor to edit code
The `EvEditor` is also used to edit some Python code in Evennia. The `@py` command supports an `/edit` switch that will open the EvEditor in code mode. This mode isn't significantly different from the standard one, except it handles automatic indentation of blocks and a few options to control this behavior.
The `EvEditor` is also used to edit some Python code in Evennia. The `@py` command supports an
`/edit` switch that will open the EvEditor in code mode. This mode isn't significantly different
from the standard one, except it handles automatic indentation of blocks and a few options to
control this behavior.
- `:<` to remove a level of indentation for the future lines.
- `:+` to add a level of indentation for the future lines.
- `:=` to disable automatic indentation altogether.
Automatic indentation is there to make code editing more simple. Python needs correct indentation, not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The EvEditor will try to guess the next level of indentation. If you type a block "if", for instance, the EvEditor will propose you an additional level of indentation at the next line. This feature cannot be perfect, however, and sometimes, you will have to use the above options to handle indentation.
Automatic indentation is there to make code editing more simple. Python needs correct indentation,
not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The
EvEditor will try to guess the next level of indentation. If you type a block "if", for instance,
the EvEditor will propose you an additional level of indentation at the next line. This feature
cannot be perfect, however, and sometimes, you will have to use the above options to handle
indentation.
`:=` can be used to turn automatic indentation off completely. This can be very useful when trying to paste several lines of code that are already correctly indented, for instance.
`:=` can be used to turn automatic indentation off completely. This can be very useful when trying
to paste several lines of code that are already correctly indented, for instance.
To see the EvEditor in code mode, you can use the `@py/edit` command. Type in your code (on one or several lines). You can then use the `:w` option (save without quitting) and the code you have typed will be executed. The `:!` will do the same thing. Executing code while not closing the editor can be useful if you want to test the code you have typed but add new lines after your test.
To see the EvEditor in code mode, you can use the `@py/edit` command. Type in your code (on one or
several lines). You can then use the `:w` option (save without quitting) and the code you have
typed will be executed. The `:!` will do the same thing. Executing code while not closing the
editor can be useful if you want to test the code you have typed but add new lines after your test.

View file

@ -73,26 +73,34 @@ EvMenu(caller, menu_data,
new [CmdSet](Command-Sets) assigned to it, for handling the menu.
- `menu_data` (str, module or dict): is a module or python path to a module where the global-level
functions will each be considered to be a menu node. Their names in the module will be the names
by which they are referred to in the module. Importantly, function names starting with an underscore
`_` will be ignored by the loader. Alternatively, this can be a direct mapping `{"nodename":function, ...}`.
by which they are referred to in the module. Importantly, function names starting with an
underscore
`_` will be ignored by the loader. Alternatively, this can be a direct mapping
`{"nodename":function, ...}`.
- `startnode` (str): is the name of the menu-node to start the menu at. Changing this means that
you can jump into a menu tree at different positions depending on circumstance and thus possibly
re-use menu entries.
- `cmdset_mergetype` (str): This is usually one of "Replace" or "Union" (see [CmdSets](Command-Sets).
- `cmdset_mergetype` (str): This is usually one of "Replace" or "Union" (see [CmdSets](Command-
Sets).
The first means that the menu is exclusive - the user has no access to any other commands while
in the menu. The Union mergetype means the menu co-exists with previous commands (and may overload
in the menu. The Union mergetype means the menu co-exists with previous commands (and may
overload
them, so be careful as to what to name your menu entries in this case).
- `cmdset_priority` (int): The priority with which to merge in the menu cmdset. This allows for
advanced usage.
- `auto_quit`, `auto_look`, `auto_help` (bool): If either of these are `True`, the menu
automatically makes a `quit`, `look` or `help` command available to the user. The main reason why
you'd want to turn this off is if you want to use the aliases "q", "l" or "h" for something in your
menu. Nevertheless, at least `quit` is highly recommend - if `False`, the menu *must* itself supply
an "exit node" (a node without any options), or the user will be stuck in the menu until the server
you'd want to turn this off is if you want to use the aliases "q", "l" or "h" for something in
your
menu. Nevertheless, at least `quit` is highly recommend - if `False`, the menu *must* itself
supply
an "exit node" (a node without any options), or the user will be stuck in the menu until the
server
reloads (or eternally if the menu is `persistent`)!
- `cmd_on_exit` (str): This command string will be executed right *after* the menu has closed down.
From experience, it's useful to trigger a "look" command to make sure the user is aware of the
change of state; but any command can be used. If set to `None`, no command will be triggered after
change of state; but any command can be used. If set to `None`, no command will be triggered
after
exiting the menu.
- `persistent` (bool) - if `True`, the menu will survive a reload (so the user will not be kicked
out by the reload - make sure they can exit on their own!)
@ -134,7 +142,8 @@ def menunodename3(caller, raw_string, **kwargs):
```
> While all of the above forms are okay, it's recommended to stick to the third and last form since it
> While all of the above forms are okay, it's recommended to stick to the third and last form since
it
> gives the most flexibility. The previous forms are mainly there for backwards compatibility with
> existing menus from a time when EvMenu was less able.
@ -173,11 +182,13 @@ help text is not given, the menu will give a generic error message when using `h
#### options
The `options` list describe all the choices available to the user when viewing this node. If `options` is
The `options` list describe all the choices available to the user when viewing this node. If
`options` is
returned as `None`, it means that this node is an *Exit node* - any text is displayed and then the
menu immediately exits, running the `exit_cmd` if given.
Otherwise, `options` should be a list (or tuple) of dictionaries, one for each option. If only one option is
Otherwise, `options` should be a list (or tuple) of dictionaries, one for each option. If only one
option is
available, a single dictionary can also be returned. This is how it could look:
@ -212,7 +223,8 @@ Defend: Hold back and defend yourself
##### option-key 'key'
The option's `key` is what the user should enter in order to choose that option. If given as a tuple, the
The option's `key` is what the user should enter in order to choose that option. If given as a
tuple, the
first string of that tuple will be what is shown on-screen while the rest are aliases for picking
that option. In the above example, the user could enter "Attack" (or "attack", it's not
case-sensitive), "a" or "att" in order to attack the goblin. Aliasing is useful for adding custom
@ -306,7 +318,8 @@ function as
Here, `raw_string` is always the input the user entered to make that choice and `kwargs` are the
same as those `kwargs` that already entered the *current* node (they are passed on).
Alternatively the `goto` could point to a "goto-callable". Such callables are usually defined in the same
Alternatively the `goto` could point to a "goto-callable". Such callables are usually defined in the
same
module as the menu nodes and given names starting with `_` (to avoid being parsed as nodes
themselves). These callables will be called the same as a node function - `callable(caller,
raw_string, **kwargs)`, where `raw_string` is what the user entered on this node and `**kwargs` is
@ -318,7 +331,8 @@ kwargs passed into it depending on which option was actually chosen.
The "goto callable" must either return a string `"nodename"` or a tuple `("nodename", mykwargs)`.
This will lead to the next node being called as either `nodename(caller, raw_string, **kwargs)` or
`nodename(caller, raw_string, **mykwargs)` - so this allows changing (or replacing) the options going
`nodename(caller, raw_string, **mykwargs)` - so this allows changing (or replacing) the options
going
into the next node depending on what option was chosen.
There is one important case - if the goto-callable returns `None` for a `nodename`, *the current
@ -418,15 +432,20 @@ See `evennia/utils/evmenu.py` for the details of their default implementations.
- **[Dynamic goto](EvMenu#example-dynamic-goto)** - jumping to different nodes based on response
- **[Set caller properties](EvMenu#example-set-caller-properties)** - a menu that changes things
- **[Getting arbitrary input](EvMenu#example-get-arbitrary-input)** - entering text
- **[Storing data between nodes](EvMenu#example-storing-data-between-nodes)** - keeping states and information while in the menu
- **[Repeating the same node](EvMenu#example-repeating-the-same-node)** - validating within the node before moving to the next
- **[Storing data between nodes](EvMenu#example-storing-data-between-nodes)** - keeping states and
information while in the menu
- **[Repeating the same node](EvMenu#example-repeating-the-same-node)** - validating within the node
before moving to the next
- **[Full Menu](EvMenu#example-full-menu):** a complete example
- **[Yes/No prompt](EvMenu#example-yesno-prompt)** - entering text with limited possible responses (this is *not* using EvMenu but the conceptually similar yet technically unrelated `get_input` helper function accessed as `evennia.utils.evmenu.get_input`).
- **[Yes/No prompt](EvMenu#example-yesno-prompt)** - entering text with limited possible responses
(this is *not* using EvMenu but the conceptually similar yet technically unrelated `get_input`
helper function accessed as `evennia.utils.evmenu.get_input`).
### Example: Simple branching menu
Below is an example of a simple branching menu node leading to different other nodes depending on choice:
Below is an example of a simple branching menu node leading to different other nodes depending on
choice:
```python
# in mygame/world/mychargen.py
@ -656,7 +675,8 @@ all nodes. At the end we look at it and, if we accept the character the menu wil
result to permanent storage and exit.
> One point to remember though is that storage on `caller.ndb._menutree` is not persistent across
> `@reloads`. If you are using a persistent menu (using `EvMenu(..., persistent=True)` you should use
> `@reloads`. If you are using a persistent menu (using `EvMenu(..., persistent=True)` you should
use
> `caller.db` to store in-menu data like this as well. You must then yourself make sure to clean it
> when the user exits the menu.
@ -978,4 +998,4 @@ menu will be assigned a [CmdSet](Command-Sets) with the commands they need to na
This means that if you were to, from inside the menu, assign a new command set to the caller, *you
may override the Menu Cmdset and kill the menu*. If you want to assign cmdsets to the caller as part
of the menu, you should store the cmdset on `caller.ndb._menutree` and wait to actually assign it
until the exit node.
until the exit node.

View file

@ -35,4 +35,3 @@ paging.
The pager takes several more keyword arguments for controlling the message output. See the
[evmore-API](github:evennia.utils.evmore) for more info.

View file

@ -1,11 +1,18 @@
# Evennia API
Evennia makes much of its programming tools available directly from the top-level `evennia` package. This is often referred to as Evennia's "flat" [Application Programming Interface](https://en.wikipedia.org/wiki/Application_programming_interface) (API). The flat API tries to collect and bring the most commonly used resources to the front in a way where everything is available at a glance (in a flat display), making it a good place to start to learn Evennia.
Evennia makes much of its programming tools available directly from the top-level `evennia` package.
This is often referred to as Evennia's "flat" [Application Programming
Interface](https://en.wikipedia.org/wiki/Application_programming_interface) (API). The flat API
tries to collect and bring the most commonly used resources to the front in a way where everything
is available at a glance (in a flat display), making it a good place to start to learn Evennia.
> Evennia's flat (and full) API can be perused through the auto-generated [API Library refence](github:evennia).
> Evennia's flat (and full) API can be perused through the auto-generated [API Library
refence](github:evennia).
A good, interactive way to explore the flat API is to use [IPython](http://ipython.org/), a more flexible version of the default Python shell. Inside your virtual environment you can install IPython simply by
A good, interactive way to explore the flat API is to use [IPython](http://ipython.org/), a more
flexible version of the default Python shell. Inside your virtual environment you can install
IPython simply by
pip install ipython
@ -26,16 +33,22 @@ Followed by
evennia.<TAB>
That is, write `evennia.` and press the TAB key. What pops up is the contents of the `evennia` top-level package - in other words [the "flat" API](github:evennia#the-flat-api).
That is, write `evennia.` and press the TAB key. What pops up is the contents of the `evennia` top-
level package - in other words [the "flat" API](github:evennia#the-flat-api).
evennia.DefaultObject?
Starting to write the name of an API entity and pressing `<TAB>` will auto-complete the name. Adding a question mark (`?`) to its name will show you its documentation. Append `??` to get the actual source code. This way you can quickly explore Evennia and see what is available.
Starting to write the name of an API entity and pressing `<TAB>` will auto-complete the name. Adding
a question mark (`?`) to its name will show you its documentation. Append `??` to get the actual
source code. This way you can quickly explore Evennia and see what is available.
## To remember when importing from `evennia`
Properties on the root of the `evennia` package are *not* modules in their own right. They are just shortcut properties stored in the `evennia/__init__.py` module. That means that you cannot use dot-notation to `import` nested module-names over `evennia`. The rule of thumb is that you cannot use `import` for more than one level down. Hence you can do
Properties on the root of the `evennia` package are *not* modules in their own right. They are just
shortcut properties stored in the `evennia/__init__.py` module. That means that you cannot use dot-
notation to `import` nested module-names over `evennia`. The rule of thumb is that you cannot use
`import` for more than one level down. Hence you can do
```python
import evennia
@ -55,4 +68,10 @@ but you *cannot* import two levels down
from evennia.default_cmds import CmdLook # error!
```
This will give you an `ImportError` telling you that the module `default_cmds` cannot be found - this is becasue `default_cmds` is just a *variable* stored in `evennia.__init__.py`; this cannot be imported from. If you really want full control over which level of package you import you can always bypass the root package and import directly from from the real location. For example `evennia.DefaultObject` is a shortcut to `evennia.objects.objects.DefaultObject`. Using this full path will have the import mechanism work normally. See `evennia/__init__.py` to see where the package imports from.
This will give you an `ImportError` telling you that the module `default_cmds` cannot be found -
this is becasue `default_cmds` is just a *variable* stored in `evennia.__init__.py`; this cannot be
imported from. If you really want full control over which level of package you import you can always
bypass the root package and import directly from from the real location. For example
`evennia.DefaultObject` is a shortcut to `evennia.objects.objects.DefaultObject`. Using this full
path will have the import mechanism work normally. See `evennia/__init__.py` to see where the
package imports from.

View file

@ -29,7 +29,9 @@ mind.
## Manual Settings
If you don't want to use the wizard (maybe because you already have the client installed from an earlier version), you can also configure your index entry in your settings file (`mygame/server/conf/settings.py`). Add the following:
If you don't want to use the wizard (maybe because you already have the client installed from an
earlier version), you can also configure your index entry in your settings file
(`mygame/server/conf/settings.py`). Add the following:
```python
GAME_INDEX_ENABLED = True
@ -66,4 +68,4 @@ If you don't specify neither `telnet_hostname + port` nor
`web_client_url`, the Game index will list your game as _Not yet public_.
Non-public games are moved to the bottom of the index since there is no way
for people to try them out. But it's a good way to show you are out there, even
if you are not ready for players yet.
if you are not ready for players yet.

View file

@ -1,35 +1,75 @@
# Evennia Introduction
> *A MUD (originally Multi-User Dungeon, with later variants Multi-User Dimension and Multi-User Domain) is a multiplayer real-time virtual world described primarily in text. MUDs combine elements of role-playing games, hack and slash, player versus player, interactive fiction and online chat. Players can read or view descriptions of rooms, objects, other players, non-player characters, and actions performed in the virtual world. Players typically interact with each other and the world by typing commands that resemble a natural language.* - [Wikipedia](http://en.wikipedia.org/wiki/MUD)
> *A MUD (originally Multi-User Dungeon, with later variants Multi-User Dimension and Multi-User
Domain) is a multiplayer real-time virtual world described primarily in text. MUDs combine elements
of role-playing games, hack and slash, player versus player, interactive fiction and online chat.
Players can read or view descriptions of rooms, objects, other players, non-player characters, and
actions performed in the virtual world. Players typically interact with each other and the world by
typing commands that resemble a natural language.* - [Wikipedia](http://en.wikipedia.org/wiki/MUD)
If you are reading this, it's quite likely you are dreaming of creating and running a text-based massively-multiplayer game ([MUD/MUX/MUSH](http://tinyurl.com/c5sc4bm) etc) of your very own. You might just be starting to think about it, or you might have lugged around that *perfect* game in your mind for years ... you know *just* how good it would be, if you could only make it come to reality. We know how you feel. That is, after all, why Evennia came to be.
If you are reading this, it's quite likely you are dreaming of creating and running a text-based
massively-multiplayer game ([MUD/MUX/MUSH](http://tinyurl.com/c5sc4bm) etc) of your very own. You
might just be starting to think about it, or you might have lugged around that *perfect* game in
your mind for years ... you know *just* how good it would be, if you could only make it come to
reality. We know how you feel. That is, after all, why Evennia came to be.
Evennia is in principle a MUD-building system: a bare-bones Python codebase and server intended to be highly extendable for any style of game. "Bare-bones" in this context means that we try to impose as few game-specific things on you as possible. So whereas we for convenience offer basic building blocks like objects, characters, rooms, default commands for building and administration etc, we don't prescribe any combat rules, mob AI, races, skills, character classes or other things that will be different from game to game anyway. It is possible that we will offer some such systems as contributions in the future, but these will in that case all be optional.
Evennia is in principle a MUD-building system: a bare-bones Python codebase and server intended to
be highly extendable for any style of game. "Bare-bones" in this context means that we try to impose
as few game-specific things on you as possible. So whereas we for convenience offer basic building
blocks like objects, characters, rooms, default commands for building and administration etc, we
don't prescribe any combat rules, mob AI, races, skills, character classes or other things that will
be different from game to game anyway. It is possible that we will offer some such systems as
contributions in the future, but these will in that case all be optional.
What we *do* however, is to provide a solid foundation for all the boring database, networking, and behind-the-scenes administration stuff that all online games need whether they like it or not. Evennia is *fully persistent*, that means things you drop on the ground somewhere will still be there a dozen server reboots later. Through Django we support a large variety of different database systems (a database is created for you automatically if you use the defaults).
What we *do* however, is to provide a solid foundation for all the boring database, networking, and
behind-the-scenes administration stuff that all online games need whether they like it or not.
Evennia is *fully persistent*, that means things you drop on the ground somewhere will still be
there a dozen server reboots later. Through Django we support a large variety of different database
systems (a database is created for you automatically if you use the defaults).
Using the full power of Python throughout the server offers some distinct advantages. All your coding, from object definitions and custom commands to AI scripts and economic systems is done in normal Python modules rather than some ad-hoc scripting language. The fact that you script the game in the same high-level language that you code it in allows for very powerful and custom game implementations indeed.
Using the full power of Python throughout the server offers some distinct advantages. All your
coding, from object definitions and custom commands to AI scripts and economic systems is done in
normal Python modules rather than some ad-hoc scripting language. The fact that you script the game
in the same high-level language that you code it in allows for very powerful and custom game
implementations indeed.
The server ships with a default set of player commands that are similar to the MUX command set. We *do not* aim specifically to be a MUX server, but we had to pick some default to go with (see [this](Soft-Code) for more about our original motivations). It's easy to remove or add commands, or to have the command syntax mimic other systems, like Diku, LP, MOO and so on. Or why not create a new and better command system of your own design.
The server ships with a default set of player commands that are similar to the MUX command set. We
*do not* aim specifically to be a MUX server, but we had to pick some default to go with (see
[this](Soft-Code) for more about our original motivations). It's easy to remove or add commands, or
to have the command syntax mimic other systems, like Diku, LP, MOO and so on. Or why not create a
new and better command system of your own design.
## Can I test it somewhere?
Evennia's demo server can be found at [demo.evennia.com](http://demo.evennia.com). If you prefer to connect to the demo via your own telnet client you can do so at `silvren.com`, port `4280`. Here is a [screenshot](Screenshot).
Evennia's demo server can be found at [demo.evennia.com](http://demo.evennia.com). If you prefer to
connect to the demo via your own telnet client you can do so at `silvren.com`, port `4280`. Here is
a [screenshot](Screenshot).
Once you installed Evennia yourself it comes with its own tutorial - this shows off some of the possibilities _and_ gives you a small single-player quest to play. The tutorial takes only one single in-game command to install as explained [here](Tutorial-World-Introduction).
Once you installed Evennia yourself it comes with its own tutorial - this shows off some of the
possibilities _and_ gives you a small single-player quest to play. The tutorial takes only one
single in-game command to install as explained [here](Tutorial-World-Introduction).
## Brief summary of features
### Technical
- Game development is done by the server importing your normal Python modules. Specific server features are implemented by overloading hooks that the engine calls appropriately.
- All game entities are simply Python classes that handle database negotiations behind the scenes without you needing to worry.
- Command sets are stored on individual objects (including characters) to offer unique functionality and object-specific commands. Sets can be updated and modified on the fly to expand/limit player input options during play.
- Scripts are used to offer asynchronous/timed execution abilities. Scripts can also be persistent. There are easy mechanisms to thread particularly long-running processes and built-in ways to start "tickers" for games that wants them.
- In-game communication channels are modular and can be modified to any functionality, including mailing systems and full logging of all messages.
- Game development is done by the server importing your normal Python modules. Specific server
features are implemented by overloading hooks that the engine calls appropriately.
- All game entities are simply Python classes that handle database negotiations behind the scenes
without you needing to worry.
- Command sets are stored on individual objects (including characters) to offer unique functionality
and object-specific commands. Sets can be updated and modified on the fly to expand/limit player
input options during play.
- Scripts are used to offer asynchronous/timed execution abilities. Scripts can also be persistent.
There are easy mechanisms to thread particularly long-running processes and built-in ways to start
"tickers" for games that wants them.
- In-game communication channels are modular and can be modified to any functionality, including
mailing systems and full logging of all messages.
- Server can be fully rebooted/reloaded without users disconnecting.
- An Account can freely connect/disconnect from game-objects, offering an easy way to implement multi-character systems and puppeting.
- Each Account can optionally control multiple Characters/Objects at the same time using the same login information.
- An Account can freely connect/disconnect from game-objects, offering an easy way to implement
multi-character systems and puppeting.
- Each Account can optionally control multiple Characters/Objects at the same time using the same
login information.
- Spawning of individual objects via a prototypes-like system.
- Tagging can be used to implement zones and object groupings.
- All source code is extensively documented.
@ -38,15 +78,21 @@ Once you installed Evennia yourself it comes with its own tutorial - this shows
### Default content
- Basic classes for Objects, Characters, Rooms and Exits
- Basic login system, using the Account's login name as their in-game Character's name for simplicity
- Basic login system, using the Account's login name as their in-game Character's name for
simplicity
- "MUX-like" command set with administration, building, puppeting, channels and social commands
- In-game Tutorial
- Contributions folder with working, but optional, code such as alternative login, menus, character generation and more
- Contributions folder with working, but optional, code such as alternative login, menus, character
generation and more
### Standards/Protocols supported
- TCP/websocket HTML5 browser web client, with ajax/comet fallback for older browsers
- Telnet and Telnet + SSL with mud-specific extensions ([MCCP](http://tintin.sourceforge.net/mccp/), [MSSP](http://tintin.sourceforge.net/mssp/), [TTYPE](http://tintin.sourceforge.net/mtts/), [MSDP](http://tintin.sourceforge.net/msdp/), [GMCP](https://www.ironrealms.com/rapture/manual/files/FeatGMCP-txt.html), [MXP](https://www.zuggsoft.com/zmud/mxp.htm) links)
- Telnet and Telnet + SSL with mud-specific extensions ([MCCP](http://tintin.sourceforge.net/mccp/),
[MSSP](http://tintin.sourceforge.net/mssp/), [TTYPE](http://tintin.sourceforge.net/mtts/),
[MSDP](http://tintin.sourceforge.net/msdp/),
[GMCP](https://www.ironrealms.com/rapture/manual/files/FeatGMCP-txt.html),
[MXP](https://www.zuggsoft.com/zmud/mxp.htm) links)
- ANSI and xterm256 colours
- SSH
- HTTP - Website served by in-built webserver and connected to same database as game.
@ -58,37 +104,80 @@ For more extensive feature information, see the [Developer Central](Developer-Ce
## What you need to know to work with Evennia
Assuming you have Evennia working (see the [quick start instructions](Getting-Started)) and have gotten as far as to start the server and connect to it with the client of your choice, here's what you need to know depending on your skills and needs.
Assuming you have Evennia working (see the [quick start instructions](Getting-Started)) and have
gotten as far as to start the server and connect to it with the client of your choice, here's what
you need to know depending on your skills and needs.
### I don't know (or don't want to do) any programming - I just want to run a game!
Evennia comes with a default set of commands for the Python newbies and for those who need to get a game running *now*. Stock Evennia is enough for running a simple 'Talker'-type game - you can build and describe rooms and basic objects, have chat channels, do emotes and other things suitable for a social or free-form MU\*. Combat, mobs and other game elements are not included, so you'll have a very basic game indeed if you are not willing to do at least *some* coding.
Evennia comes with a default set of commands for the Python newbies and for those who need to get a
game running *now*. Stock Evennia is enough for running a simple 'Talker'-type game - you can build
and describe rooms and basic objects, have chat channels, do emotes and other things suitable for a
social or free-form MU\*. Combat, mobs and other game elements are not included, so you'll have a
very basic game indeed if you are not willing to do at least *some* coding.
### I know basic Python, or I am willing to learn
Evennia's source code is extensively documented and is [viewable online](https://github.com/evennia/evennia). We also have a comprehensive [online manual](https://github.com/evennia/evennia/wiki) with lots of examples. But while Python is considered a very easy programming language to get into, you do have a learning curve to climb if you are new to programming. You should probably sit down
with a Python beginner's [tutorial](http://docs.python.org/tutorial/) (there are plenty of them on the web if you look around) so you at least know what you are seeing. See also our [link page](Links#wiki-litterature) for some reading suggestions. To efficiently code your dream game in Evennia you don't need to be a Python guru, but you do need to be able to read example code containing at least these basic Python features:
Evennia's source code is extensively documented and is [viewable
online](https://github.com/evennia/evennia). We also have a comprehensive [online
manual](https://github.com/evennia/evennia/wiki) with lots of examples. But while Python is
considered a very easy programming language to get into, you do have a learning curve to climb if
you are new to programming. You should probably sit down
with a Python beginner's [tutorial](http://docs.python.org/tutorial/) (there are plenty of them on
the web if you look around) so you at least know what you are seeing. See also our [link
page](Links#wiki-litterature) for some reading suggestions. To efficiently code your dream game in
Evennia you don't need to be a Python guru, but you do need to be able to read example code
containing at least these basic Python features:
- Importing and using python [modules](http://docs.python.org/3.7/tutorial/modules.html)
- Using [variables](http://www.tutorialspoint.com/python/python_variable_types.htm), [conditional statements](http://docs.python.org/tutorial/controlflow.html#if-statements), [loops](http://docs.python.org/tutorial/controlflow.html#for-statements) and [functions](http://docs.python.org/tutorial/controlflow.html#defining-functions)
- Using [lists, dictionaries and list comprehensions](http://docs.python.org/tutorial/datastructures.html)
- Using [variables](http://www.tutorialspoint.com/python/python_variable_types.htm), [conditional
statements](http://docs.python.org/tutorial/controlflow.html#if-statements),
[loops](http://docs.python.org/tutorial/controlflow.html#for-statements) and
[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions)
- Using [lists, dictionaries and list
comprehensions](http://docs.python.org/tutorial/datastructures.html)
- Doing [string handling and formatting](http://docs.python.org/tutorial/introduction.html#strings)
- Have a basic understanding of [object-oriented programming](http://www.tutorialspoint.com/python/python_classes_objects.htm), using [Classes](http://docs.python.org/tutorial/classes.html), their methods and properties
- Have a basic understanding of [object-oriented
programming](http://www.tutorialspoint.com/python/python_classes_objects.htm), using
[Classes](http://docs.python.org/tutorial/classes.html), their methods and properties
Obviously, the more things you feel comfortable with, the easier time you'll have to find your way. With just basic knowledge you should be able to define your own [Commands](Commands), create custom [Objects](Objects) as well as make your world come alive with basic [Scripts](Scripts). You can definitely build a whole advanced and customized game from extending Evennia's examples only.
Obviously, the more things you feel comfortable with, the easier time you'll have to find your way.
With just basic knowledge you should be able to define your own [Commands](Commands), create custom
[Objects](Objects) as well as make your world come alive with basic [Scripts](Scripts). You can
definitely build a whole advanced and customized game from extending Evennia's examples only.
### I know my Python stuff and I am willing to use it!
Even if you started out as a Python beginner, you will likely get to this point after working on your game for a while. With more general knowledge in Python the full power of Evennia opens up for you. Apart from modifying commands, objects and scripts, you can develop everything from advanced mob AI and economic systems, through sophisticated combat and social mini games, to redefining how commands, players, rooms or channels themselves work. Since you code your game by importing normal Python modules, there are few limits to what you can accomplish.
Even if you started out as a Python beginner, you will likely get to this point after working on
your game for a while. With more general knowledge in Python the full power of Evennia opens up for
you. Apart from modifying commands, objects and scripts, you can develop everything from advanced
mob AI and economic systems, through sophisticated combat and social mini games, to redefining how
commands, players, rooms or channels themselves work. Since you code your game by importing normal
Python modules, there are few limits to what you can accomplish.
If you *also* happen to know some web programming (HTML, CSS, Javascript) there is also a web presence (a website and a mud web client) to play around with ...
If you *also* happen to know some web programming (HTML, CSS, Javascript) there is also a web
presence (a website and a mud web client) to play around with ...
### Where to from here?
From here you can continue browsing the [online documentation]([online documentation](index)) to find more info about Evennia. Or you can jump into the [Tutorials](Tutorials) and get your hands dirty with code right away. You can also read the developer's [dev blog](https://evennia.blogspot.com/) for many tidbits and snippets about Evennia's development and structure.
From here you can continue browsing the [online documentation]([online documentation](index)) to
find more info about Evennia. Or you can jump into the [Tutorials](Tutorials) and get your hands
dirty with code right away. You can also read the developer's [dev
blog](https://evennia.blogspot.com/) for many tidbits and snippets about Evennia's development and
structure.
Some more hints:
1. Get engaged in the community. Make an introductory post to our [mailing list/forum](https://groups.google.com/forum/#!forum/evennia) and get to know people. It's also highly recommended you hop onto our [Developer chat](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) on IRC. This allows you to chat directly with other developers new and old as well as with the devs of Evennia itself. This chat is logged (you can find links on http://www.evennia.com) and can also be searched from the same place for discussion topics you are interested in.
2. Read the [Game Planning](Game-Planning) wiki page. It gives some ideas for your work flow and the state of mind you should aim for - including cutting down the scope of your game for its first release.
3. Do the [Tutorial for basic MUSH-like game](Tutorial-for-basic-MUSH-like-game) carefully from beginning to end and try to understand what does what. Even if you are not interested in a MUSH for your own game, you will end up with a small (very small) game that you can build or learn from.
1. Get engaged in the community. Make an introductory post to our [mailing
list/forum](https://groups.google.com/forum/#!forum/evennia) and get to know people. It's also
highly recommended you hop onto our [Developer
chat](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb)
on IRC. This allows you to chat directly with other developers new and old as well as with the devs
of Evennia itself. This chat is logged (you can find links on http://www.evennia.com) and can also
be searched from the same place for discussion topics you are interested in.
2. Read the [Game Planning](Game-Planning) wiki page. It gives some ideas for your work flow and the
state of mind you should aim for - including cutting down the scope of your game for its first
release.
3. Do the [Tutorial for basic MUSH-like game](Tutorial-for-basic-MUSH-like-game) carefully from
beginning to end and try to understand what does what. Even if you are not interested in a MUSH for
your own game, you will end up with a small (very small) game that you can build or learn from.

View file

@ -1,20 +1,41 @@
# Evennia for Diku Users
Evennia represents a learning curve for those who used to code on [Diku](https://en.wikipedia.org/wiki/DikuMUD) type MUDs. While coding in Python is easy if you already know C, the main effort is to get rid of old C programming habits. Trying to code Python the way you code C will not only look ugly, it will lead to less optimal and harder to maintain code. Reading Evennia example code is a good way to get a feel for how different problems are approached in Python.
Evennia represents a learning curve for those who used to code on
[Diku](https://en.wikipedia.org/wiki/DikuMUD) type MUDs. While coding in Python is easy if you
already know C, the main effort is to get rid of old C programming habits. Trying to code Python the
way you code C will not only look ugly, it will lead to less optimal and harder to maintain code.
Reading Evennia example code is a good way to get a feel for how different problems are approached
in Python.
Overall, Python offers an extensive library of resources, safe memory management and excellent handling of errors. While Python code does not run as fast as raw C code does, the difference is not all that important for a text-based game. The main advantage of Python is an extremely fast development cycle with and easy ways to create game systems that would take many times more code and be much harder to make stable and maintainable in C.
Overall, Python offers an extensive library of resources, safe memory management and excellent
handling of errors. While Python code does not run as fast as raw C code does, the difference is not
all that important for a text-based game. The main advantage of Python is an extremely fast
development cycle with and easy ways to create game systems that would take many times more code and
be much harder to make stable and maintainable in C.
### Core Differences
- As mentioned, the main difference between Evennia and a Diku-derived codebase is that Evennia is written purely in Python. Since Python is an interpreted language there is no compile stage. It is modified and extended by the server loading Python modules at run-time. It also runs on all computer platforms Python runs on (which is basically everywhere).
- Vanilla Diku type engines save their data in custom *flat file* type storage solutions. By contrast, Evennia stores all game data in one of several supported SQL databases. Whereas flat files have the advantage of being easier to implement, they (normally) lack many expected safety features and ways to effectively extract subsets of the stored data. For example, if the server loses power while writing to a flatfile it may become corrupt and the data lost. A proper database solution is not susceptible to this - at no point is the data in a state where it cannot be recovered. Databases are also highly optimized for querying large data sets efficiently.
- As mentioned, the main difference between Evennia and a Diku-derived codebase is that Evennia is
written purely in Python. Since Python is an interpreted language there is no compile stage. It is
modified and extended by the server loading Python modules at run-time. It also runs on all computer
platforms Python runs on (which is basically everywhere).
- Vanilla Diku type engines save their data in custom *flat file* type storage solutions. By
contrast, Evennia stores all game data in one of several supported SQL databases. Whereas flat files
have the advantage of being easier to implement, they (normally) lack many expected safety features
and ways to effectively extract subsets of the stored data. For example, if the server loses power
while writing to a flatfile it may become corrupt and the data lost. A proper database solution is
not susceptible to this - at no point is the data in a state where it cannot be recovered. Databases
are also highly optimized for querying large data sets efficiently.
### Some Familiar Things
Diku expresses the character object referenced normally by:
`struct char ch*` then all character-related fields can be accessed by `ch->`. In Evennia, one must pay attention to what object you are using, and when you are accessing another through back-handling, that you are accessing the right object. In Diku C, accessing character object is normally done by:
`struct char ch*` then all character-related fields can be accessed by `ch->`. In Evennia, one must
pay attention to what object you are using, and when you are accessing another through back-
handling, that you are accessing the right object. In Diku C, accessing character object is normally
done by:
```c
/* creating pointer of both character and room struct */
@ -28,7 +49,10 @@ void(struct char ch*, struct room room*){
};
```
As an example for creating Commands in Evennia via the `from evennia import Command` the character object that calls the command is denoted by a class property as `self.caller`. In this example `self.caller` is essentially the 'object' that has called the Command, but most of the time it is an Account object. For a more familiar Diku feel, create a variable that becomes the account object as:
As an example for creating Commands in Evennia via the `from evennia import Command` the character
object that calls the command is denoted by a class property as `self.caller`. In this example
`self.caller` is essentially the 'object' that has called the Command, but most of the time it is an
Account object. For a more familiar Diku feel, create a variable that becomes the account object as:
```python
#mygame/commands/command.py
@ -51,7 +75,8 @@ class CmdMyCmd(Command):
```
As mentioned above, care must be taken what specific object you are working with. If focused on a room object and you need to access the account object:
As mentioned above, care must be taken what specific object you are working with. If focused on a
room object and you need to access the account object:
```python
#mygame/typeclasses/room.py
@ -78,11 +103,17 @@ class MyRoom(DefaultRoom):
## Emulating Evennia to Look and Feel Like A Diku/ROM
To emulate a Diku Mud on Evennia some work has to be done before hand. If there is anything that all coders and builders remember from Diku/Rom days is the presence of VNUMs. Essentially all data was saved in flat files and indexed by VNUMs for easy access. Evennia has the ability to emulate VNUMS to the extent of categorising rooms/mobs/objs/trigger/zones[...] into vnum ranges.
To emulate a Diku Mud on Evennia some work has to be done before hand. If there is anything that all
coders and builders remember from Diku/Rom days is the presence of VNUMs. Essentially all data was
saved in flat files and indexed by VNUMs for easy access. Evennia has the ability to emulate VNUMS
to the extent of categorising rooms/mobs/objs/trigger/zones[...] into vnum ranges.
Evennia has objects that are called Scripts. As defined, they are the 'out of game' instances that exist within the mud, but never directly interacted with. Scripts can be used for timers, mob AI, and even a stand alone databases.
Evennia has objects that are called Scripts. As defined, they are the 'out of game' instances that
exist within the mud, but never directly interacted with. Scripts can be used for timers, mob AI,
and even a stand alone databases.
Because of their wonderful structure all mob, room, zone, triggers, etc.. data can be saved in independently created global scripts.
Because of their wonderful structure all mob, room, zone, triggers, etc.. data can be saved in
independently created global scripts.
Here is a sample mob file from a Diku Derived flat file.
@ -115,9 +146,14 @@ BareHandAttack: 12
E
T 95
```
Each line represents something that the MUD reads in and does something with it. This isn't easy to read, but let's see if we can emulate this as a dictionary to be stored on a database script created in Evennia.
Each line represents something that the MUD reads in and does something with it. This isn't easy to
read, but let's see if we can emulate this as a dictionary to be stored on a database script created
in Evennia.
First, let's create a global script that does absolutely nothing and isn't attached to anything. You can either create this directly in-game with the @py command or create it in another file to do some checks and balances if for whatever reason the script needs to be created again. Progmatically it can be done like so:
First, let's create a global script that does absolutely nothing and isn't attached to anything. You
can either create this directly in-game with the @py command or create it in another file to do some
checks and balances if for whatever reason the script needs to be created again. Progmatically it
can be done like so:
```python
from evennia import create_script
@ -126,7 +162,8 @@ mob_db = create_script("typeclasses.scripts.DefaultScript", key="mobdb",
persistent=True, obj=None)
mob_db.db.vnums = {}
```
Just by creating a simple script object and assigning it a 'vnums' attribute as a type dictionary. Next we have to create the mob layout..
Just by creating a simple script object and assigning it a 'vnums' attribute as a type dictionary.
Next we have to create the mob layout..
```python
# vnum : mob_data
@ -146,14 +183,17 @@ mob_vnum_1 = {
mob_db.db.vnums[1] = mob_vnum_1
```
This is a very 'caveman' example, but it gets the idea across. You can use the keys in the `mob_db.vnums` to act as the mob vnum while the rest contains the data..
This is a very 'caveman' example, but it gets the idea across. You can use the keys in the
`mob_db.vnums` to act as the mob vnum while the rest contains the data..
Much simpler to read and edit. If you plan on taking this route, you must keep in mind that by default evennia 'looks' at different properties when using the `look` command for instance. If you create an instance of this mob and make its `self.key = 1`, by default evennia will say
Much simpler to read and edit. If you plan on taking this route, you must keep in mind that by
default evennia 'looks' at different properties when using the `look` command for instance. If you
create an instance of this mob and make its `self.key = 1`, by default evennia will say
`Here is : 1`
You must restructure all default commands so that the mud looks at different properties defined on your mob.
You must restructure all default commands so that the mud looks at different properties defined on
your mob.

View file

@ -1,40 +1,95 @@
# Evennia for MUSH Users
*This page is adopted from an article originally posted for the MUSH community [here on musoapbox.net](http://musoapbox.net/topic/1150/evennia-for-mushers).*
*This page is adopted from an article originally posted for the MUSH community [here on
musoapbox.net](http://musoapbox.net/topic/1150/evennia-for-mushers).*
[MUSH](https://en.wikipedia.org/wiki/MUSH)es are text multiplayer games traditionally used for heavily roleplay-focused game styles. They are often (but not always) utilizing game masters and human oversight over code automation. MUSHes are traditionally built on the TinyMUSH-family of game servers, like PennMUSH, TinyMUSH, TinyMUX and RhostMUSH. Also their siblings [MUCK](https://en.wikipedia.org/wiki/TinyMUCK) and [MOO](https://en.wikipedia.org/wiki/MOO) are often mentioned together with MUSH since they all inherit from the same [TinyMUD](https://en.wikipedia.org/wiki/MUD_trees#TinyMUD_family_tree) base. A major feature is the ability to modify and program the game world from inside the game by using a custom scripting language. We will refer to this online scripting as *softcode* here.
[MUSH](https://en.wikipedia.org/wiki/MUSH)es are text multiplayer games traditionally used for
heavily roleplay-focused game styles. They are often (but not always) utilizing game masters and
human oversight over code automation. MUSHes are traditionally built on the TinyMUSH-family of game
servers, like PennMUSH, TinyMUSH, TinyMUX and RhostMUSH. Also their siblings
[MUCK](https://en.wikipedia.org/wiki/TinyMUCK) and [MOO](https://en.wikipedia.org/wiki/MOO) are
often mentioned together with MUSH since they all inherit from the same
[TinyMUD](https://en.wikipedia.org/wiki/MUD_trees#TinyMUD_family_tree) base. A major feature is the
ability to modify and program the game world from inside the game by using a custom scripting
language. We will refer to this online scripting as *softcode* here.
Evennia works quite differently from a MUSH both in its overall design and under the hood. The same things are achievable, just in a different way. Here are some fundamental differences to keep in mind if you are coming from the MUSH world.
Evennia works quite differently from a MUSH both in its overall design and under the hood. The same
things are achievable, just in a different way. Here are some fundamental differences to keep in
mind if you are coming from the MUSH world.
## Developers vs Players
In MUSH, users tend to code and expand all aspects of the game from inside it using softcode. A MUSH can thus be said to be managed solely by *Players* with different levels of access. Evennia on the other hand, differentiates between the role of the *Player* and the *Developer*.
In MUSH, users tend to code and expand all aspects of the game from inside it using softcode. A MUSH
can thus be said to be managed solely by *Players* with different levels of access. Evennia on the
other hand, differentiates between the role of the *Player* and the *Developer*.
- An Evennia *Developer* works in Python from *outside* the game, in what MUSH would consider “hardcode”. Developers implement larger-scale code changes and can fundamentally change how the game works. They then load their changes into the running Evennia server. Such changes will usually not drop any connected players.
- An Evennia *Player* operates from *inside* the game. Some staff-level players are likely to double as developers. Depending on access level, players can modify and expand the game's world by digging new rooms, creating new objects, alias commands, customize their experience and so on. Trusted staff may get access to Python via the `@py` command, but this would be a security risk for normal Players to use. So the *Player* usually operates by making use of the tools prepared for them by the *Developer* - tools that can be as rigid or flexible as the developer desires.
- An Evennia *Developer* works in Python from *outside* the game, in what MUSH would consider
“hardcode”. Developers implement larger-scale code changes and can fundamentally change how the game
works. They then load their changes into the running Evennia server. Such changes will usually not
drop any connected players.
- An Evennia *Player* operates from *inside* the game. Some staff-level players are likely to double
as developers. Depending on access level, players can modify and expand the game's world by digging
new rooms, creating new objects, alias commands, customize their experience and so on. Trusted staff
may get access to Python via the `@py` command, but this would be a security risk for normal Players
to use. So the *Player* usually operates by making use of the tools prepared for them by the
*Developer* - tools that can be as rigid or flexible as the developer desires.
## Collaborating on a game - Python vs Softcode
For a *Player*, collaborating on a game need not be too different between MUSH and Evennia. The building and description of the game world can still happen mostly in-game using build commands, using text tags and [inline functions](TextTags#inline-functions) to prettify and customize the experience. Evennia offers external ways to build a world but those are optional. There is also nothing *in principle* stopping a Developer from offering a softcode-like language to Players if that is deemed necessary.
For a *Player*, collaborating on a game need not be too different between MUSH and Evennia. The
building and description of the game world can still happen mostly in-game using build commands,
using text tags and [inline functions](TextTags#inline-functions) to prettify and customize the
experience. Evennia offers external ways to build a world but those are optional. There is also
nothing *in principle* stopping a Developer from offering a softcode-like language to Players if
that is deemed necessary.
For *Developers* of the game, the difference is larger: Code is mainly written outside the game in Python modules rather than in-game on the command line. Python is a very popular and well-supported language with tons of documentation and help to be found. The Python standard library is also a great help for not having to reinvent the wheel. But that said, while Python is considered one of the easier languages to learn and use it is undoubtedly very different from MUSH softcode.
For *Developers* of the game, the difference is larger: Code is mainly written outside the game in
Python modules rather than in-game on the command line. Python is a very popular and well-supported
language with tons of documentation and help to be found. The Python standard library is also a
great help for not having to reinvent the wheel. But that said, while Python is considered one of
the easier languages to learn and use it is undoubtedly very different from MUSH softcode.
While softcode allows collaboration in-game, Evennia's external coding instead opens up the possibility for collaboration using professional version control tools and bug tracking using websites like github (or bitbucket for a free private repo). Source code can be written in proper text editors and IDEs with refactoring, syntax highlighting and all other conveniences. In short, collaborative development of an Evennia game is done in the same way most professional collaborative development is done in the world, meaning all the best tools can be used.
While softcode allows collaboration in-game, Evennia's external coding instead opens up the
possibility for collaboration using professional version control tools and bug tracking using
websites like github (or bitbucket for a free private repo). Source code can be written in proper
text editors and IDEs with refactoring, syntax highlighting and all other conveniences. In short,
collaborative development of an Evennia game is done in the same way most professional collaborative
development is done in the world, meaning all the best tools can be used.
## `@parent` vs `@typeclass` and `@spawn`
Inheritance works differently in Python than in softcode. Evennia has no concept of a "master object" that other objects inherit from. There is in fact no reason at all to introduce "virtual objects" in the game world - code and data are kept separate from one another.
Inheritance works differently in Python than in softcode. Evennia has no concept of a "master
object" that other objects inherit from. There is in fact no reason at all to introduce "virtual
objects" in the game world - code and data are kept separate from one another.
In Python (which is an [object oriented](https://en.wikipedia.org/wiki/Object-oriented_programming) language) one instead creates *classes* - these are like blueprints from which you spawn any number of *object instances*. Evennia also adds the extra feature that every instance is persistent in the database (this means no SQL is ever needed). To take one example, a unique character in Evennia is an instances of the class `Character`.
In Python (which is an [object oriented](https://en.wikipedia.org/wiki/Object-oriented_programming)
language) one instead creates *classes* - these are like blueprints from which you spawn any number
of *object instances*. Evennia also adds the extra feature that every instance is persistent in the
database (this means no SQL is ever needed). To take one example, a unique character in Evennia is
an instances of the class `Character`.
One parallel to MUSH's `@parent` command may be Evennia's `@typeclass` command, which changes which class an already existing object is an instance of. This way you can literally turn a `Character` into a `Flowerpot` on the spot.
One parallel to MUSH's `@parent` command may be Evennia's `@typeclass` command, which changes which
class an already existing object is an instance of. This way you can literally turn a `Character`
into a `Flowerpot` on the spot.
if you are new to object oriented design it's important to note that all object instances of a class does *not* have to be identical. If they did, all Characters would be named the same. Evennia allows to customize individual objects in many different ways. One way is through *Attributes*, which are database-bound properties that can be linked to any object. For example, you could have an `Orc` class that defines all the stuff an Orc should be able to do (probably in turn inheriting from some `Monster` class shared by all monsters). Setting different Attributes on different instances (different strength, equipment, looks etc) would make each Orc unique despite all sharing the same class.
if you are new to object oriented design it's important to note that all object instances of a class
does *not* have to be identical. If they did, all Characters would be named the same. Evennia allows
to customize individual objects in many different ways. One way is through *Attributes*, which are
database-bound properties that can be linked to any object. For example, you could have an `Orc`
class that defines all the stuff an Orc should be able to do (probably in turn inheriting from some
`Monster` class shared by all monsters). Setting different Attributes on different instances
(different strength, equipment, looks etc) would make each Orc unique despite all sharing the same
class.
The `@spawn` command allows one to conveniently choose between different "sets" of Attributes to put on each new Orc (like the "warrior" set or "shaman" set) . Such sets can even inherit one another which is again somewhat remniscent at least of the *effect* of `@parent` and the object-based inheritance of MUSH.
The `@spawn` command allows one to conveniently choose between different "sets" of Attributes to
put on each new Orc (like the "warrior" set or "shaman" set) . Such sets can even inherit one
another which is again somewhat remniscent at least of the *effect* of `@parent` and the object-
based inheritance of MUSH.
There are other differences for sure, but that should give some feel for things. Enough with the theory. Let's get down to more practical matters next. To install, see the [Getting Started instructions](Getting-Started).
There are other differences for sure, but that should give some feel for things. Enough with the
theory. Let's get down to more practical matters next. To install, see the [Getting Started
instructions](Getting-Started).
## A first step making things more familiar
@ -42,9 +97,15 @@ We will here give two examples of customizing Evennia to be more familiar to a M
### Activating a multi-descer
By default Evennias `desc` command updates your description and thats it. There is a more feature-rich optional “multi-descer” in `evennia/contrib/multidesc.py` though. This alternative allows for managing and combining a multitude of keyed descriptions.
By default Evennias `desc` command updates your description and thats it. There is a more feature-
rich optional “multi-descer” in `evennia/contrib/multidesc.py` though. This alternative allows for
managing and combining a multitude of keyed descriptions.
To activate the multi-descer, `cd` to your game folder and into the `commands` sub-folder. There youll find the file `default_cmdsets.py`. In Python lingo all `*.py` files are called *modules*. Open the module in a text editor. We wont go into Evennia in-game *Commands* and *Command sets* further here, but suffice to say Evennia allows you to change which commands (or versions of commands) are available to the player from moment to moment depending on circumstance.
To activate the multi-descer, `cd` to your game folder and into the `commands` sub-folder. There
youll find the file `default_cmdsets.py`. In Python lingo all `*.py` files are called *modules*.
Open the module in a text editor. We wont go into Evennia in-game *Commands* and *Command sets*
further here, but suffice to say Evennia allows you to change which commands (or versions of
commands) are available to the player from moment to moment depending on circumstance.
Add two new lines to the module as seen below:
@ -74,11 +135,21 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
# [...]
```
Note that Python cares about indentation, so make sure to indent with the same number of spaces as shown above!
Note that Python cares about indentation, so make sure to indent with the same number of spaces as
shown above!
So what happens above? We [import the module](http://www.linuxtopia.org/online_books/programming_books/python_programming/python_ch28s03.html) `evennia/contrib/multidescer.py` at the top. Once imported we can access stuff inside that module using full stop (`.`). The multidescer is defined as a class `CmdMultiDesc` (we could find this out by opening said module in a text editor). At the bottom we create a new instance of this class and add it to the `CharacterCmdSet` class. For the sake of this tutorial we only need to know that `CharacterCmdSet` contains all commands that should be be available to the `Character` by default.
So what happens above? We [import the
module](http://www.linuxtopia.org/online_books/programming_books/python_programming/python_ch28s03.html)
`evennia/contrib/multidescer.py` at the top. Once imported we can access stuff inside that module
using full stop (`.`). The multidescer is defined as a class `CmdMultiDesc` (we could find this out
by opening said module in a text editor). At the bottom we create a new instance of this class and
add it to the `CharacterCmdSet` class. For the sake of this tutorial we only need to know that
`CharacterCmdSet` contains all commands that should be be available to the `Character` by default.
This whole thing will be triggered when the command set is first created, which happens on server start. So we need to reload Evennia with `@reload` - no one will be disconnected by doing this. If all went well you should now be able to use `desc` (or `+desc`) and find that you have more possibilities:
This whole thing will be triggered when the command set is first created, which happens on server
start. So we need to reload Evennia with `@reload` - no one will be disconnected by doing this. If
all went well you should now be able to use `desc` (or `+desc`) and find that you have more
possibilities:
```text
> help +desc # get help on the command
@ -89,17 +160,26 @@ This whole thing will be triggered when the command set is first created, which
A big guy. His eyes are blue.
```
If there are errors, a *traceback* will show in the server log - several lines of text showing where the error occurred. Find where the error is by locating the line number related to the `default_cmdsets.py` file (it's the only one you've changed so far). Most likely you mis-spelled something or missed the indentation. Fix it and either `@reload` again or run `evennia start` as needed.
If there are errors, a *traceback* will show in the server log - several lines of text showing
where the error occurred. Find where the error is by locating the line number related to the
`default_cmdsets.py` file (it's the only one you've changed so far). Most likely you mis-spelled
something or missed the indentation. Fix it and either `@reload` again or run `evennia start` as
needed.
### Customizing the multidescer syntax
As seen above the multidescer uses syntax like this (where `|/` are Evennia's tags for line breaks) :
As seen above the multidescer uses syntax like this (where `|/` are Evennia's tags for line breaks)
:
```text
> +desc/set basic + |/|/ + cape + footwear + |/|/ + attitude
```
This use of `+ ` was prescribed by the *Developer* that coded this `+desc` command. What if the *Player* doesnt like this syntax though? Do players need to pester the dev to change it? Not necessarily. While Evennia does not allow the player to build their own multi-descer on the command line, it does allow for *re-mapping* the command syntax to one they prefer. This is done using the `nick` command.
This use of `+ ` was prescribed by the *Developer* that coded this `+desc` command. What if the
*Player* doesnt like this syntax though? Do players need to pester the dev to change it? Not
necessarily. While Evennia does not allow the player to build their own multi-descer on the command
line, it does allow for *re-mapping* the command syntax to one they prefer. This is done using the
`nick` command.
Heres a nick that changes how to input the command above:
@ -107,7 +187,10 @@ Heres a nick that changes how to input the command above:
> nick setdesc $1 $2 $3 $4 = +desc/set $1 + |/|/ + $2 + $3 + |/|/ + $4
```
The string on the left will be matched against your input and if matching, it will be replaced with the string on the right. The `$`-type tags will store space-separated arguments and put them into the replacement. The nick allows [shell-like wildcards](http://www.linfo.org/wildcard.html), so you can use `*`, `?`, `[...]`, `[!...]` etc to match parts of the input.
The string on the left will be matched against your input and if matching, it will be replaced with
the string on the right. The `$`-type tags will store space-separated arguments and put them into
the replacement. The nick allows [shell-like wildcards](http://www.linfo.org/wildcard.html), so you
can use `*`, `?`, `[...]`, `[!...]` etc to match parts of the input.
The same description as before can now be set as
@ -115,12 +198,25 @@ The same description as before can now be set as
> setdesc basic cape footwear attitude
```
With the `nick` functionality players can mitigate a lot of syntax dislikes even without the developer changing the underlying Python code.
With the `nick` functionality players can mitigate a lot of syntax dislikes even without the
developer changing the underlying Python code.
## Next steps
If you are a *Developer* and are interested in making a more MUSH-like Evennia game, a good start is to look into the Evennia [Tutorial for a first MUSH-like game](Tutorial-for-basic-MUSH-like-game). That steps through building a simple little game from scratch and helps to acquaint you with the various corners of Evennia. There is also the [Tutorial for running roleplaying sessions](Evennia-for-roleplaying-sessions) that can be of interest.
If you are a *Developer* and are interested in making a more MUSH-like Evennia game, a good start is
to look into the Evennia [Tutorial for a first MUSH-like game](Tutorial-for-basic-MUSH-like-game).
That steps through building a simple little game from scratch and helps to acquaint you with the
various corners of Evennia. There is also the [Tutorial for running roleplaying sessions](Evennia-
for-roleplaying-sessions) that can be of interest.
An important aspect of making things more familiar for *Players* is adding new and tweaking existing commands. How this is done is covered by the [Tutorial on adding new commands](Adding-Command-Tutorial). You may also find it useful to shop through the `evennia/contrib/` folder. The [Tutorial world](Tutorial-World-Introduction) is a small single-player quest you can try (its not very MUSH-like but it does show many Evennia concepts in action). Beyond that there are [many more tutorials](Tutorials) to try out. If you feel you want a more visual overview you can also look at [Evennia in pictures](https://evennia.blogspot.se/2016/05/evennia-in-pictures.html).
An important aspect of making things more familiar for *Players* is adding new and tweaking existing
commands. How this is done is covered by the [Tutorial on adding new commands](Adding-Command-
Tutorial). You may also find it useful to shop through the `evennia/contrib/` folder. The [Tutorial
world](Tutorial-World-Introduction) is a small single-player quest you can try (its not very MUSH-
like but it does show many Evennia concepts in action). Beyond that there are [many more
tutorials](Tutorials) to try out. If you feel you want a more visual overview you can also look at
[Evennia in pictures](https://evennia.blogspot.se/2016/05/evennia-in-pictures.html).
… And of course, if you need further help you can always drop into the [Evennia chatroom](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) or post a question in our [forum/mailing list](https://groups.google.com/forum/#%21forum/evennia)!
… And of course, if you need further help you can always drop into the [Evennia
chatroom](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb)
or post a question in our [forum/mailing list](https://groups.google.com/forum/#%21forum/evennia)!

View file

@ -1,46 +1,72 @@
# Evennia for roleplaying sessions
This tutorial will explain how to set up a realtime or play-by-post tabletop style game using a fresh Evennia server.
This tutorial will explain how to set up a realtime or play-by-post tabletop style game using a
fresh Evennia server.
The scenario is thus: You and a bunch of friends want to play a tabletop role playing game online. One of you will be the game master and you are all okay with playing using written text. You want both the ability to role play in real-time (when people happen to be online at the same time) as well as the ability for people to post when they can and catch up on what happened since they were last online.
The scenario is thus: You and a bunch of friends want to play a tabletop role playing game online.
One of you will be the game master and you are all okay with playing using written text. You want
both the ability to role play in real-time (when people happen to be online at the same time) as
well as the ability for people to post when they can and catch up on what happened since they were
last online.
This is the functionality we will be needing and using:
* The ability to make one of you the *GM* (game master), with special abilities.
* A *Character sheet* that players can create, view and fill in. It can also be locked so only the GM can modify it.
* A *Character sheet* that players can create, view and fill in. It can also be locked so only the
GM can modify it.
* A *dice roller* mechanism, for whatever type of dice the RPG rules require.
* *Rooms*, to give a sense of location and to compartmentalize play going on- This means both Character movements from location to location and GM explicitly moving them around.
* *Rooms*, to give a sense of location and to compartmentalize play going on- This means both
Character movements from location to location and GM explicitly moving them around.
* *Channels*, for easily sending text to all subscribing accounts, regardless of location.
* Account-to-Account *messaging* capability, including sending to multiple recipients simultaneously, regardless of location.
* Account-to-Account *messaging* capability, including sending to multiple recipients
simultaneously, regardless of location.
We will find most of these things are already part of vanilla Evennia, but that we can expand on the defaults for our particular use-case. Below we will flesh out these components from start to finish.
We will find most of these things are already part of vanilla Evennia, but that we can expand on the
defaults for our particular use-case. Below we will flesh out these components from start to finish.
## Starting out
We will assume you start from scratch. You need Evennia installed, as per the [Getting Started](Getting-Started) instructions. Initialize a new game directory with `evennia init <gamedirname>`. In this tutorial we assume your game dir is simply named `mygame`. You can use the default database and keep all other settings to default for now. Familiarize yourself with the `mygame` folder before continuing. You might want to browse the [First Steps Coding](First-Steps-Coding) tutorial, just to see roughly where things are modified.
We will assume you start from scratch. You need Evennia installed, as per the [Getting
Started](Getting-Started) instructions. Initialize a new game directory with `evennia init
<gamedirname>`. In this tutorial we assume your game dir is simply named `mygame`. You can use the
default database and keep all other settings to default for now. Familiarize yourself with the
`mygame` folder before continuing. You might want to browse the [First Steps Coding](First-Steps-
Coding) tutorial, just to see roughly where things are modified.
## The Game Master role
In brief:
* Simplest way: Being an admin, just give one account `Admins` permission using the standard `@perm` command.
* Better but more work: Make a custom command to set/unset the above, while tweaking the Character to show your renewed GM status to the other accounts.
* Simplest way: Being an admin, just give one account `Admins` permission using the standard `@perm`
command.
* Better but more work: Make a custom command to set/unset the above, while tweaking the Character
to show your renewed GM status to the other accounts.
### The permission hierarchy
Evennia has the following [permission hierarchy](Building-Permissions#assigning-permissions) out of the box: *Players, Helpers, Builders, Admins* and finally *Developers*. We could change these but then we'd need to update our Default commands to use the changes. We want to keep this simple, so instead we map our different roles on top of this permission ladder.
Evennia has the following [permission hierarchy](Building-Permissions#assigning-permissions) out of
the box: *Players, Helpers, Builders, Admins* and finally *Developers*. We could change these but
then we'd need to update our Default commands to use the changes. We want to keep this simple, so
instead we map our different roles on top of this permission ladder.
1. `Players` is the permission set on normal players. This is the default for anyone creating a new account on the server.
2. `Helpers` are like `Players` except they also have the ability to create/edit new help entries. This could be granted to players who are willing to help with writing lore or custom logs for everyone.
1. `Players` is the permission set on normal players. This is the default for anyone creating a new
account on the server.
2. `Helpers` are like `Players` except they also have the ability to create/edit new help entries.
This could be granted to players who are willing to help with writing lore or custom logs for
everyone.
3. `Builders` is not used in our case since the GM should be the only world-builder.
4. `Admins` is the permission level the GM should have. Admins can do everything builders can (create/describe rooms etc) but also kick accounts, rename them and things like that.
5. `Developers`-level permission are the server administrators, the ones with the ability to restart/shutdown the server as well as changing the permission levels.
4. `Admins` is the permission level the GM should have. Admins can do everything builders can
(create/describe rooms etc) but also kick accounts, rename them and things like that.
5. `Developers`-level permission are the server administrators, the ones with the ability to
restart/shutdown the server as well as changing the permission levels.
> The [superuser](Building-Permissions#the-super-user) is not part of the hierarchy and actually completely bypasses it. We'll assume server admin(s) will "just" be Developers.
> The [superuser](Building-Permissions#the-super-user) is not part of the hierarchy and actually
completely bypasses it. We'll assume server admin(s) will "just" be Developers.
### How to grant permissions
Only `Developers` can (by default) change permission level. Only they have access to the `@perm` command:
Only `Developers` can (by default) change permission level. Only they have access to the `@perm`
command:
```
> @perm Yvonne
@ -55,21 +81,30 @@ Permissions on Yvonne: accounts, admins
Permissions on Yvonne: accounts
```
There is no need to remove the basic `Players` permission when adding the higher permission: the highest will be used. Permission level names are *not* case sensitive. You can also use both plural and singular, so "Admins" gives the same powers as "Admin".
There is no need to remove the basic `Players` permission when adding the higher permission: the
highest will be used. Permission level names are *not* case sensitive. You can also use both plural
and singular, so "Admins" gives the same powers as "Admin".
### Optional: Making a GM-granting command
Use of `@perm` works out of the box, but it's really the bare minimum. Would it not be nice if other accounts could tell at a glance who the GM is? Also, we shouldn't really need to remember that the permission level is called "Admins". It would be easier if we could just do `@gm <account>` and `@notgm <account>` and at the same time change something make the new GM status apparent.
Use of `@perm` works out of the box, but it's really the bare minimum. Would it not be nice if other
accounts could tell at a glance who the GM is? Also, we shouldn't really need to remember that the
permission level is called "Admins". It would be easier if we could just do `@gm <account>` and
`@notgm <account>` and at the same time change something make the new GM status apparent.
So let's make this possible. This is what we'll do:
1. We'll customize the default Character class. If an object of this class has a particular flag, its name will have the string`(GM)` added to the end.
1. We'll customize the default Character class. If an object of this class has a particular flag,
its name will have the string`(GM)` added to the end.
2. We'll add a new command, for the server admin to assign the GM-flag properly.
#### Character modification
Let's first start by customizing the Character. We recommend you browse the beginning of the [Account](Accounts) page to make sure you know how Evennia differentiates between the OOC "Account objects" (not to be confused with the `Accounts` permission, which is just a string specifying your access) and the IC "Character objects".
Let's first start by customizing the Character. We recommend you browse the beginning of the
[Account](Accounts) page to make sure you know how Evennia differentiates between the OOC "Account
objects" (not to be confused with the `Accounts` permission, which is just a string specifying your
access) and the IC "Character objects".
Open `mygame/typeclasses/characters.py` and modify the default `Character` class:
@ -103,13 +138,23 @@ class Character(DefaultCharacter):
```
Above, we change how the Character's name is displayed: If the account controlling this Character is a GM, we attach the string `(GM)` to the Character's name so everyone can tell who's the boss. If we ourselves are Developers or GM's we will see database ids attached to Characters names, which can help if doing database searches against Characters of exactly the same name. We base the "gm-ingness" on having an flag (an [Attribute](Attributes)) named `is_gm`. We'll make sure new GM's actually get this flag below.
Above, we change how the Character's name is displayed: If the account controlling this Character is
a GM, we attach the string `(GM)` to the Character's name so everyone can tell who's the boss. If we
ourselves are Developers or GM's we will see database ids attached to Characters names, which can
help if doing database searches against Characters of exactly the same name. We base the "gm-
ingness" on having an flag (an [Attribute](Attributes)) named `is_gm`. We'll make sure new GM's
actually get this flag below.
> **Extra exercise:** This will only show the `(GM)` text on *Characters* puppeted by a GM account, that is, it will show only to those in the same location. If we wanted it to also pop up in, say, `who` listings and channels, we'd need to make a similar change to the `Account` typeclass in `mygame/typeclasses/accounts.py`. We leave this as an exercise to the reader.
> **Extra exercise:** This will only show the `(GM)` text on *Characters* puppeted by a GM account,
that is, it will show only to those in the same location. If we wanted it to also pop up in, say,
`who` listings and channels, we'd need to make a similar change to the `Account` typeclass in
`mygame/typeclasses/accounts.py`. We leave this as an exercise to the reader.
#### New @gm/@ungm command
We will describe in some detail how to create and add an Evennia [command](Commands) here with the hope that we don't need to be as detailed when adding commands in the future. We will build on Evennia's default "mux-like" commands here.
We will describe in some detail how to create and add an Evennia [command](Commands) here with the
hope that we don't need to be as detailed when adding commands in the future. We will build on
Evennia's default "mux-like" commands here.
Open `mygame/commands/command.py` and add a new Command class at the bottom:
@ -176,11 +221,17 @@ class CmdMakeGM(default_cmds.MuxCommand):
```
All the command does is to locate the account target and assign it the `Admins` permission if we used `@gm` or revoke it if using the `@ungm` alias. We also set/unset the `is_gm` Attribute that is expected by our new `Character.get_display_name` method from earlier.
All the command does is to locate the account target and assign it the `Admins` permission if we
used `@gm` or revoke it if using the `@ungm` alias. We also set/unset the `is_gm` Attribute that is
expected by our new `Character.get_display_name` method from earlier.
> We could have made this into two separate commands or opted for a syntax like `@gm/revoke <accountname>`. Instead we examine how this command was called (stored in `self.cmdstring`) in order to act accordingly. Either way works, practicality and coding style decides which to go with.
> We could have made this into two separate commands or opted for a syntax like `@gm/revoke
<accountname>`. Instead we examine how this command was called (stored in `self.cmdstring`) in order
to act accordingly. Either way works, practicality and coding style decides which to go with.
To actually make this command available (only to Developers, due to the lock on it), we add it to the default Account command set. Open the file `mygame/commands/default_cmdsets.py` and find the `AccountCmdSet` class:
To actually make this command available (only to Developers, due to the lock on it), we add it to
the default Account command set. Open the file `mygame/commands/default_cmdsets.py` and find the
`AccountCmdSet` class:
```python
# mygame/commands/default_cmdsets.py
@ -196,7 +247,8 @@ class AccountCmdSet(default_cmds.AccountCmdSet):
```
Finally, issue the `@reload` command to update the server to your changes. Developer-level players (or the superuser) should now have the `@gm/@ungm` command available.
Finally, issue the `@reload` command to update the server to your changes. Developer-level players
(or the superuser) should now have the `@gm/@ungm` command available.
## Character sheet
@ -209,13 +261,18 @@ In brief:
### Building a Character sheet
There are many ways to build a Character sheet in text, from manually pasting strings together to more automated ways. Exactly what is the best/easiest way depends on the sheet one tries to create. We will here show two examples using the *EvTable* and *EvForm* utilities.Later we will create Commands to edit and display the output from those utilities.
There are many ways to build a Character sheet in text, from manually pasting strings together to
more automated ways. Exactly what is the best/easiest way depends on the sheet one tries to create.
We will here show two examples using the *EvTable* and *EvForm* utilities.Later we will create
Commands to edit and display the output from those utilities.
> Note that due to the limitations of the wiki, no color is used in any of the examples. See [the text tag documentation](TextTags) for how to add color to the tables and forms.
> Note that due to the limitations of the wiki, no color is used in any of the examples. See [the
text tag documentation](TextTags) for how to add color to the tables and forms.
#### Making a sheet with EvTable
[EvTable](github:evennia.utils.evtable) is a text-table generator. It helps with displaying text in ordered rows and columns. This is an example of using it in code:
[EvTable](github:evennia.utils.evtable) is a text-table generator. It helps with displaying text in
ordered rows and columns. This is an example of using it in code:
````python
# this can be tried out in a Python shell like iPython
@ -232,7 +289,13 @@ table = evtable.EvTable("Attr", "Value",
], align='r', border="incols")
````
Above, we create a two-column table by supplying the two columns directly. We also tell the table to be right-aligned and to use the "incols" border type (borders drawns only in between columns). The `EvTable` class takes a lot of arguments for customizing its look, you can see [some of the possible keyword arguments here](github:evennia.utils.evtable#evtable__init__). Once you have the `table` you could also retroactively add new columns and rows to it with `table.add_row()` and `table.add_column()`: if necessary the table will expand with empty rows/columns to always remain rectangular.
Above, we create a two-column table by supplying the two columns directly. We also tell the table to
be right-aligned and to use the "incols" border type (borders drawns only in between columns). The
`EvTable` class takes a lot of arguments for customizing its look, you can see [some of the possible
keyword arguments here](github:evennia.utils.evtable#evtable__init__). Once you have the `table` you
could also retroactively add new columns and rows to it with `table.add_row()` and
`table.add_column()`: if necessary the table will expand with empty rows/columns to always remain
rectangular.
The result from printing the above table will be
@ -251,17 +314,29 @@ print(table_string)
CHA | 13
```
This is a minimalistic but effective Character sheet. By combining the `table_string` with other strings one could build up a reasonably full graphical representation of a Character. For more advanced layouts we'll look into EvForm next.
This is a minimalistic but effective Character sheet. By combining the `table_string` with other
strings one could build up a reasonably full graphical representation of a Character. For more
advanced layouts we'll look into EvForm next.
#### Making a sheet with EvForm
[EvForm](github:evennia.utils.evform) allows the creation of a two-dimensional "graphic" made by text characters. On this surface, one marks and tags rectangular regions ("cells") to be filled with content. This content can be either normal strings or `EvTable` instances (see the previous section, one such instance would be the `table` variable in that example).
[EvForm](github:evennia.utils.evform) allows the creation of a two-dimensional "graphic" made by
text characters. On this surface, one marks and tags rectangular regions ("cells") to be filled with
content. This content can be either normal strings or `EvTable` instances (see the previous section,
one such instance would be the `table` variable in that example).
In the case of a Character sheet, these cells would be comparable to a line or box where you could enter the name of your character or their strength score. EvMenu also easily allows to update the content of those fields in code (it use EvTables so you rebuild the table first before re-sending it to EvForm).
In the case of a Character sheet, these cells would be comparable to a line or box where you could
enter the name of your character or their strength score. EvMenu also easily allows to update the
content of those fields in code (it use EvTables so you rebuild the table first before re-sending it
to EvForm).
The drawback of EvForm is that its shape is static; if you try to put more text in a region than it was sized for, the text will be cropped. Similarly, if you try to put an EvTable instance in a field too small for it, the EvTable will do its best to try to resize to fit, but will eventually resort to cropping its data or even give an error if too small to fit any data.
The drawback of EvForm is that its shape is static; if you try to put more text in a region than it
was sized for, the text will be cropped. Similarly, if you try to put an EvTable instance in a field
too small for it, the EvTable will do its best to try to resize to fit, but will eventually resort
to cropping its data or even give an error if too small to fit any data.
An EvForm is defined in a Python module. Create a new file `mygame/world/charsheetform.py` and modify it thus:
An EvForm is defined in a Python module. Create a new file `mygame/world/charsheetform.py` and
modify it thus:
````python
#coding=utf-8
@ -291,9 +366,16 @@ FORM = """
+--------------------------------------+
"""
````
The `#coding` statement (which must be put on the very first line to work) tells Python to use the utf-8 encoding for the file. Using the `FORMCHAR` and `TABLECHAR` we define what single-character we want to use to "mark" the regions of the character sheet holding cells and tables respectively. Within each block (which must be separated from one another by at least one non-marking character) we embed identifiers 1-4 to identify each block. The identifier could be any single character except for the `FORMCHAR` and `TABLECHAR`
The `#coding` statement (which must be put on the very first line to work) tells Python to use the
utf-8 encoding for the file. Using the `FORMCHAR` and `TABLECHAR` we define what single-character we
want to use to "mark" the regions of the character sheet holding cells and tables respectively.
Within each block (which must be separated from one another by at least one non-marking character)
we embed identifiers 1-4 to identify each block. The identifier could be any single character except
for the `FORMCHAR` and `TABLECHAR`
> You can still use `FORMCHAR` and `TABLECHAR` elsewhere in your sheet, but not in a way that it would identify a cell/table. The smallest identifiable cell/table area is 3 characters wide including the identifier (for example `x2x`).
> You can still use `FORMCHAR` and `TABLECHAR` elsewhere in your sheet, but not in a way that it
would identify a cell/table. The smallest identifiable cell/table area is 3 characters wide
including the identifier (for example `x2x`).
Now we will map content to this form.
@ -318,9 +400,13 @@ form.map(cells={"1":NAME, "3": ADVANTAGES, "4": DISADVANTAGES},
tables={"2":table})
````
We create some RP-sounding input and re-use the `table` variable from the previous `EvTable` example.
We create some RP-sounding input and re-use the `table` variable from the previous `EvTable`
example.
> Note, that if you didn't want to create the form in a separate module you *could* also load it directly into the `EvForm` call like this: `EvForm(form={"FORMCHAR":"x", "TABLECHAR":"c", "FORM": formstring})` where `FORM` specifies the form as a string in the same way as listed in the module above. Note however that the very first line of the `FORM` string is ignored, so start with a `\n`.
> Note, that if you didn't want to create the form in a separate module you *could* also load it
directly into the `EvForm` call like this: `EvForm(form={"FORMCHAR":"x", "TABLECHAR":"c", "FORM":
formstring})` where `FORM` specifies the form as a string in the same way as listed in the module
above. Note however that the very first line of the `FORM` string is ignored, so start with a `\n`.
We then map those to the cells of the form:
@ -347,11 +433,15 @@ print(form)
+--------------------------------------+
````
As seen, the texts and tables have been slotted into the text areas and line breaks have been added where needed. We chose to just enter the Advantages/Disadvantages as plain strings here, meaning long names ended up split between rows. If we wanted more control over the display we could have inserted `\n` line breaks after each line or used a borderless `EvTable` to display those as well.
As seen, the texts and tables have been slotted into the text areas and line breaks have been added
where needed. We chose to just enter the Advantages/Disadvantages as plain strings here, meaning
long names ended up split between rows. If we wanted more control over the display we could have
inserted `\n` line breaks after each line or used a borderless `EvTable` to display those as well.
### Tie a Character sheet to a Character
We will assume we go with the `EvForm` example above. We now need to attach this to a Character so it can be modified. For this we will modify our `Character` class a little more:
We will assume we go with the `EvForm` example above. We now need to attach this to a Character so
it can be modified. For this we will modify our `Character` class a little more:
```python
# mygame/typeclasses/character.py
@ -397,7 +487,10 @@ class Character(DefaultCharacter):
```
Use `@reload` to make this change available to all *newly created* Characters. *Already existing* Characters will *not* have the charsheet defined, since `at_object_creation` is only called once. The easiest to force an existing Character to re-fire its `at_object_creation` is to use the `@typeclass` command in-game:
Use `@reload` to make this change available to all *newly created* Characters. *Already existing*
Characters will *not* have the charsheet defined, since `at_object_creation` is only called once.
The easiest to force an existing Character to re-fire its `at_object_creation` is to use the
`@typeclass` command in-game:
```
@typeclass/force <Character Name>
@ -405,7 +498,8 @@ Use `@reload` to make this change available to all *newly created* Characters. *
### Command for Account to change Character sheet
We will add a command to edit the sections of our Character sheet. Open `mygame/commands/command.py`.
We will add a command to edit the sections of our Character sheet. Open
`mygame/commands/command.py`.
```python
# at the end of mygame/commands/command.py
@ -480,13 +574,16 @@ class CmdSheet(MuxCommand):
```
Most of this command is error-checking to make sure the right type of data was input. Note how the `sheet_locked` Attribute is checked and will return if not set.
Most of this command is error-checking to make sure the right type of data was input. Note how the
`sheet_locked` Attribute is checked and will return if not set.
This command you import into `mygame/commands/default_cmdsets.py` and add to the `CharacterCmdSet`, in the same way the `@gm` command was added to the `AccountCmdSet` earlier.
This command you import into `mygame/commands/default_cmdsets.py` and add to the `CharacterCmdSet`,
in the same way the `@gm` command was added to the `AccountCmdSet` earlier.
### Commands for GM to change Character sheet
Game masters use basically the same input as Players do to edit a character sheet, except they can do it on other players than themselves. They are also not stopped by any `sheet_locked` flags.
Game masters use basically the same input as Players do to edit a character sheet, except they can
do it on other players than themselves. They are also not stopped by any `sheet_locked` flags.
```python
# continuing in mygame/commands/command.py
@ -564,44 +661,63 @@ class CmdGMsheet(MuxCommand):
caller.msg(character.db.charsheet)
```
The `@gmsheet` command takes an additional argument to specify which Character's character sheet to edit. It also takes `/lock` and `/unlock` switches to block the Player from tweaking their sheet.
The `@gmsheet` command takes an additional argument to specify which Character's character sheet to
edit. It also takes `/lock` and `/unlock` switches to block the Player from tweaking their sheet.
Before this can be used, it should be added to the default `CharacterCmdSet` in the same way as the normal `@sheet`. Due to the lock set on it, this command will only be available to `Admins` (i.e. GMs) or higher permission levels.
Before this can be used, it should be added to the default `CharacterCmdSet` in the same way as the
normal `@sheet`. Due to the lock set on it, this command will only be available to `Admins` (i.e.
GMs) or higher permission levels.
## Dice roller
Evennia's *contrib* folder already comes with a full dice roller. To add it to the game, simply import `contrib.dice.CmdDice` into `mygame/commands/default_cmdsets.py` and add `CmdDice` to the `CharacterCmdset` as done with other commands in this tutorial. After a `@reload` you will be able to roll dice using normal RPG-style format:
Evennia's *contrib* folder already comes with a full dice roller. To add it to the game, simply
import `contrib.dice.CmdDice` into `mygame/commands/default_cmdsets.py` and add `CmdDice` to the
`CharacterCmdset` as done with other commands in this tutorial. After a `@reload` you will be able
to roll dice using normal RPG-style format:
```
roll 2d6 + 3
7
```
Use `help dice` to see what syntax is supported or look at `evennia/contrib/dice.py` to see how it's implemented.
Use `help dice` to see what syntax is supported or look at `evennia/contrib/dice.py` to see how it's
implemented.
## Rooms
Evennia comes with rooms out of the box, so no extra work needed. A GM will automatically have all needed building commands available. A fuller go-through is found in the [Building tutorial](Building-Quickstart). Here are some useful highlights:
Evennia comes with rooms out of the box, so no extra work needed. A GM will automatically have all
needed building commands available. A fuller go-through is found in the [Building
tutorial](Building-Quickstart). Here are some useful highlights:
* `@dig roomname;alias = exit_there;alias, exit_back;alias` - this is the basic command for digging a new room. You can specify any exit-names and just enter the name of that exit to go there.
* `@tunnel direction = roomname` - this is a specialized command that only accepts directions in the cardinal directions (n,ne,e,se,s,sw,w,nw) as well as in/out and up/down. It also automatically builds "matching" exits back in the opposite direction.
* `@dig roomname;alias = exit_there;alias, exit_back;alias` - this is the basic command for digging
a new room. You can specify any exit-names and just enter the name of that exit to go there.
* `@tunnel direction = roomname` - this is a specialized command that only accepts directions in the
cardinal directions (n,ne,e,se,s,sw,w,nw) as well as in/out and up/down. It also automatically
builds "matching" exits back in the opposite direction.
* `@create/drop objectname` - this creates and drops a new simple object in the current location.
* `@desc obj` - change the look-description of the object.
* `@tel object = location` - teleport an object to a named location.
* `@search objectname` - locate an object in the database.
> TODO: Describe how to add a logging room, that logs says and poses to a log file that people can access after the fact.
> TODO: Describe how to add a logging room, that logs says and poses to a log file that people can
access after the fact.
## Channels
Evennia comes with [Channels](Communications#Channels) in-built and they are described fully in the documentation. For brevity, here are the relevant commands for normal use:
Evennia comes with [Channels](Communications#Channels) in-built and they are described fully in the
documentation. For brevity, here are the relevant commands for normal use:
* `@ccreate new_channel;alias;alias = short description` - Creates a new channel.
* `addcom channel` - join an existing channel. Use `addcom alias = channel` to add a new alias you can use to talk to the channel, as many as desired.
* `delcom alias or channel` - remove an alias from a channel or, if the real channel name is given, unsubscribe completely.
* `@channels` lists all available channels, including your subscriptions and any aliases you have set up for them.
* `addcom channel` - join an existing channel. Use `addcom alias = channel` to add a new alias you
can use to talk to the channel, as many as desired.
* `delcom alias or channel` - remove an alias from a channel or, if the real channel name is given,
unsubscribe completely.
* `@channels` lists all available channels, including your subscriptions and any aliases you have
set up for them.
You can read channel history: if you for example are chatting on the `public` channel you can do `public/history` to see the 20 last posts to that channel or `public/history 32` to view twenty posts backwards, starting with the 32nd from the end.
You can read channel history: if you for example are chatting on the `public` channel you can do
`public/history` to see the 20 last posts to that channel or `public/history 32` to view twenty
posts backwards, starting with the 32nd from the end.
## PMs
@ -612,4 +728,5 @@ page recipient = message
page recipient, recipient, ... = message
```
Players can use `page` alone to see the latest messages. This also works if they were not online when the message was sent.
Players can use `page` alone to see the latest messages. This also works if they were not online
when the message was sent.

View file

@ -1,42 +1,62 @@
# Execute Python Code
The `@py` command supplied with the default command set of Evennia allows you to execute Python commands directly from inside the game. An alias to `@py` is simply "`!`". *Access to the `@py` command should be severely restricted*. This is no joke - being able to execute arbitrary Python code on the server is not something you should entrust to just anybody.
The `@py` command supplied with the default command set of Evennia allows you to execute Python
commands directly from inside the game. An alias to `@py` is simply "`!`". *Access to the `@py`
command should be severely restricted*. This is no joke - being able to execute arbitrary Python
code on the server is not something you should entrust to just anybody.
@py 1+2
<<< 3
## Available variables
A few local variables are made available when running `@py`. These offer entry into the running system.
A few local variables are made available when running `@py`. These offer entry into the running
system.
- **self** / **me** - the calling object (i.e. you)
- **here** - the current caller's location
- **obj** - a dummy [Object](Objects) instance
- **evennia** - Evennia's [flat API](Evennia-API) - through this you can access all of Evennia.
For accessing other objects in the same room you need to use `self.search(name)`. For objects in other locations, use one of the `evennia.search_*` methods. See [below](Execute-Python-Code#finding-objects).
For accessing other objects in the same room you need to use `self.search(name)`. For objects in
other locations, use one of the `evennia.search_*` methods. See [below](Execute-Python-Code#finding-
objects).
## Returning output
This is an example where we import and test one of Evennia's utilities found in `src/utils/utils.py`, but also accessible through `ev.utils`:
This is an example where we import and test one of Evennia's utilities found in
`src/utils/utils.py`, but also accessible through `ev.utils`:
@py from ev import utils; utils.time_format(33333)
<<< Done.
Note that we didn't get any return value, all we where told is that the code finished executing without error. This is often the case in more complex pieces of code which has no single obvious return value. To see the output from the `time_format()` function we need to tell the system to echo it to us explicitly with `self.msg()`.
Note that we didn't get any return value, all we where told is that the code finished executing
without error. This is often the case in more complex pieces of code which has no single obvious
return value. To see the output from the `time_format()` function we need to tell the system to
echo it to us explicitly with `self.msg()`.
@py from ev import utils; self.msg(str(utils.time_format(33333)))
09:15
<<< Done.
> Warning: When using the `msg` function wrap our argument in `str()` to convert it into a string above. This is not strictly necessary for most types of data (Evennia will usually convert to a string behind the scenes for you). But for *lists* and *tuples* you will be confused by the output if you don't wrap them in `str()`: only the first item of the iterable will be returned. This is because doing `msg(text)` is actually just a convenience shortcut; the full argument that `msg` accepts is something called an *outputfunc* on the form `(cmdname, (args), {kwargs})` (see [the message path](Messagepath) for more info). Sending a list/tuple confuses Evennia to think you are sending such a structure. Converting it to a string however makes it clear it should just be displayed as-is.
> Warning: When using the `msg` function wrap our argument in `str()` to convert it into a string
above. This is not strictly necessary for most types of data (Evennia will usually convert to a
string behind the scenes for you). But for *lists* and *tuples* you will be confused by the output
if you don't wrap them in `str()`: only the first item of the iterable will be returned. This is
because doing `msg(text)` is actually just a convenience shortcut; the full argument that `msg`
accepts is something called an *outputfunc* on the form `(cmdname, (args), {kwargs})` (see [the
message path](Messagepath) for more info). Sending a list/tuple confuses Evennia to think you are
sending such a structure. Converting it to a string however makes it clear it should just be
displayed as-is.
If you were to use Python's standard `print`, you will see the result in your current `stdout` (your terminal by default, otherwise your log file).
If you were to use Python's standard `print`, you will see the result in your current `stdout` (your
terminal by default, otherwise your log file).
## Finding objects
A common use for `@py` is to explore objects in the database, for debugging and performing specific operations that are not covered by a particular command.
A common use for `@py` is to explore objects in the database, for debugging and performing specific
operations that are not covered by a particular command.
Locating an object is best done using `self.search()`:
@ -49,12 +69,17 @@ Locating an object is best done using `self.search()`:
@py self.search("red_ball").db.color
<<< red
`self.search()` is by far the most used case, but you can also search other database tables for other Evennia entities like scripts or configuration entities. To do this you can use the generic search entries found in `ev.search_*`.
`self.search()` is by far the most used case, but you can also search other database tables for
other Evennia entities like scripts or configuration entities. To do this you can use the generic
search entries found in `ev.search_*`.
@py evennia.search_script("sys_game_time")
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
(Note that since this becomes a simple statement, we don't have to wrap it in `self.msg()` to get the output). You can also use the database model managers directly (accessible through the `objects` properties of database models or as `evennia.managers.*`). This is a bit more flexible since it gives you access to the full range of database search methods defined in each manager.
(Note that since this becomes a simple statement, we don't have to wrap it in `self.msg()` to get
the output). You can also use the database model managers directly (accessible through the `objects`
properties of database models or as `evennia.managers.*`). This is a bit more flexible since it
gives you access to the full range of database search methods defined in each manager.
@py evennia.managers.scripts.script_search("sys_game_time")
<<< [<src.utils.gametime.GameTime object at 0x852be2c>]
@ -66,15 +91,22 @@ The managers are useful for all sorts of database studies.
## Testing code outside the game
`@py` has the advantage of operating inside a running server (sharing the same process), where you can test things in real time. Much of this *can* be done from the outside too though.
`@py` has the advantage of operating inside a running server (sharing the same process), where you
can test things in real time. Much of this *can* be done from the outside too though.
In a terminal, cd to the top of your game directory (this bit is important since we need access to your config file) and run
In a terminal, cd to the top of your game directory (this bit is important since we need access to
your config file) and run
evennia shell
Your default Python interpreter will start up, configured to be able to work with and import all modules of your Evennia installation. From here you can explore the database and test-run individual modules as desired.
Your default Python interpreter will start up, configured to be able to work with and import all
modules of your Evennia installation. From here you can explore the database and test-run individual
modules as desired.
It's recommended that you get a more fully featured Python interpreter like [iPython](http://ipython.scipy.org/moin/). If you use a virtual environment, you can just get it with `pip install ipython`. IPython allows you to better work over several lines, and also has a lot of other editing features, such as tab-completion and `__doc__`-string reading.
It's recommended that you get a more fully featured Python interpreter like
[iPython](http://ipython.scipy.org/moin/). If you use a virtual environment, you can just get it
with `pip install ipython`. IPython allows you to better work over several lines, and also has a lot
of other editing features, such as tab-completion and `__doc__`-string reading.
$ evennia shell
@ -85,4 +117,4 @@ It's recommended that you get a more fully featured Python interpreter like [iPy
In [2]: evennia.managers.objects.all()
Out[3]: [<ObjectDB: Harry>, <ObjectDB: Limbo>, ...]
See the page about the [Evennia-API](Evennia-API) for more things to explore.
See the page about the [Evennia-API](Evennia-API) for more things to explore.

View file

@ -1,24 +1,38 @@
# First Steps Coding
This section gives a brief step-by-step introduction on how to set up Evennia for the first time so you can modify and overload the defaults easily. You should only need to do these steps once. It also walks through you making your first few tweaks.
This section gives a brief step-by-step introduction on how to set up Evennia for the first time so
you can modify and overload the defaults easily. You should only need to do these steps once. It
also walks through you making your first few tweaks.
Before continuing, make sure you have Evennia installed and running by following the [Getting Started](Getting-Started) instructions. You should have initialized a new game folder with the `evennia --init foldername` command. We will in the following assume this folder is called "mygame".
Before continuing, make sure you have Evennia installed and running by following the [Getting
Started](Getting-Started) instructions. You should have initialized a new game folder with the
`evennia --init foldername` command. We will in the following assume this folder is called
"mygame".
It might be a good idea to eye through the brief [Coding Introduction](Coding-Introduction) too (especially the recommendations in the section about the evennia "flat" API and about using `evennia shell` will help you here and in the future).
It might be a good idea to eye through the brief [Coding Introduction](Coding-Introduction) too
(especially the recommendations in the section about the evennia "flat" API and about using `evennia
shell` will help you here and in the future).
To follow this tutorial you also need to know the basics of operating your computer's terminal/command line. You also need to have a text editor to edit and create source text files. There are plenty of online tutorials on how to use the terminal and plenty of good free text editors. We will assume these things are already familiar to you henceforth.
To follow this tutorial you also need to know the basics of operating your computer's
terminal/command line. You also need to have a text editor to edit and create source text files.
There are plenty of online tutorials on how to use the terminal and plenty of good free text
editors. We will assume these things are already familiar to you henceforth.
## Your First Changes
Below are some first things to try with your new custom modules. You can test these to get a feel for the system. See also [Tutorials](Tutorials) for more step-by-step help and special cases.
Below are some first things to try with your new custom modules. You can test these to get a feel
for the system. See also [Tutorials](Tutorials) for more step-by-step help and special cases.
### Tweak Default Character
We will add some simple rpg attributes to our default Character. In the next section we will follow up with a new command to view those attributes.
We will add some simple rpg attributes to our default Character. In the next section we will follow
up with a new command to view those attributes.
1. Edit `mygame/typeclasses/characters.py` and modify the `Character` class. The `at_object_creation` method also exists on the `DefaultCharacter` parent and will overload it. The `get_abilities` method is unique to our version of `Character`.
1. Edit `mygame/typeclasses/characters.py` and modify the `Character` class. The
`at_object_creation` method also exists on the `DefaultCharacter` parent and will overload it. The
`get_abilities` method is unique to our version of `Character`.
```python
class Character(DefaultCharacter):
@ -43,11 +57,18 @@ We will add some simple rpg attributes to our default Character. In the next sec
return self.db.strength, self.db.agility, self.db.magic
```
1. [Reload](Start-Stop-Reload) the server (you will still be connected to the game after doing this). Note that if you examine *yourself* you will *not* see any new Attributes appear yet. Read the next section to understand why.
1. [Reload](Start-Stop-Reload) the server (you will still be connected to the game after doing
this). Note that if you examine *yourself* you will *not* see any new Attributes appear yet. Read
the next section to understand why.
#### Updating Yourself
It's important to note that the new [Attributes](Attributes) we added above will only be stored on *newly* created characters. The reason for this is simple: The `at_object_creation` method, where we added those Attributes, is per definition only called when the object is *first created*, then never again. This is usually a good thing since those Attributes may change over time - calling that hook would reset them back to start values. But it also means that your existing character doesn't have them yet. You can see this by calling the `get_abilities` hook on yourself at this point:
It's important to note that the new [Attributes](Attributes) we added above will only be stored on
*newly* created characters. The reason for this is simple: The `at_object_creation` method, where we
added those Attributes, is per definition only called when the object is *first created*, then never
again. This is usually a good thing since those Attributes may change over time - calling that hook
would reset them back to start values. But it also means that your existing character doesn't have
them yet. You can see this by calling the `get_abilities` hook on yourself at this point:
```
# (you have to be superuser to use @py)
@ -61,14 +82,19 @@ This is easily remedied.
@update self
```
This will (only) re-run `at_object_creation` on yourself. You should henceforth be able to get the abilities successfully:
This will (only) re-run `at_object_creation` on yourself. You should henceforth be able to get the
abilities successfully:
```
@py self.get_abilities()
<<< (5, 4, 2)
```
This is something to keep in mind if you start building your world before your code is stable - startup-hooks will not (and should not) automatically run on *existing* objects - you have to update your existing objects manually. Luckily this is a one-time thing and pretty simple to do. If the typeclass you want to update is in `typeclasses.myclass.MyClass`, you can do the following (e.g. from `evennia shell`):
This is something to keep in mind if you start building your world before your code is stable -
startup-hooks will not (and should not) automatically run on *existing* objects - you have to update
your existing objects manually. Luckily this is a one-time thing and pretty simple to do. If the
typeclass you want to update is in `typeclasses.myclass.MyClass`, you can do the following (e.g.
from `evennia shell`):
```python
from typeclasses.myclass import MyClass
@ -78,17 +104,25 @@ for obj in MyClass.objects.all():
obj.swap_typeclass(MyClass, run_start_hooks="at_object_creation")
```
Using `swap_typeclass` to the same typeclass we already have will re-run the creation hooks (this is what the `@update` command does under the hood). From in-game you can do the same with `@py`:
Using `swap_typeclass` to the same typeclass we already have will re-run the creation hooks (this is
what the `@update` command does under the hood). From in-game you can do the same with `@py`:
```
@py typeclasses.myclass import MyClass;[obj.swap_typeclass(MyClass) for obj in MyClass.objects.all()]
@py typeclasses.myclass import MyClass;[obj.swap_typeclass(MyClass) for obj in
MyClass.objects.all()]
```
See the [Object Typeclass tutorial](Adding-Object-Typeclass-Tutorial) for more help and the [Typeclasses](Typeclasses) and [Attributes](Attributes) page for detailed documentation about Typeclasses and Attributes.
See the [Object Typeclass tutorial](Adding-Object-Typeclass-Tutorial) for more help and the
[Typeclasses](Typeclasses) and [Attributes](Attributes) page for detailed documentation about
Typeclasses and Attributes.
#### Troubleshooting: Updating Yourself
One may experience errors for a number of reasons. Common beginner errors are spelling mistakes, wrong indentations or code omissions leading to a `SyntaxError`. Let's say you leave out a colon from the end of a class function like so: ```def at_object_creation(self)```. The client will reload without issue. *However*, if you look at the terminal/console (i.e. not in-game), you will see Evennia complaining (this is called a *traceback*):
One may experience errors for a number of reasons. Common beginner errors are spelling mistakes,
wrong indentations or code omissions leading to a `SyntaxError`. Let's say you leave out a colon
from the end of a class function like so: ```def at_object_creation(self)```. The client will reload
without issue. *However*, if you look at the terminal/console (i.e. not in-game), you will see
Evennia complaining (this is called a *traceback*):
```
Traceback (most recent call last):
@ -98,21 +132,39 @@ File "C:\mygame\typeclasses\characters.py", line 33
SyntaxError: invalid syntax
```
Evennia will still be restarting and following the tutorial, doing `@py self.get_abilities()` will return the right response `(None, None, None)`. But when attempting to `@typeclass/force self` you will get this response:
Evennia will still be restarting and following the tutorial, doing `@py self.get_abilities()` will
return the right response `(None, None, None)`. But when attempting to `@typeclass/force self` you
will get this response:
```python
AttributeError: 'DefaultObject' object has no attribute 'get_abilities'
```
The full error will show in the terminal/console but this is confusing since you did add `get_abilities` before. Note however what the error says - you (`self`) should be a `Character` but the error talks about `DefaultObject`. What has happened is that due to your unhandled `SyntaxError` earlier, Evennia could not load the `character.py` module at all (it's not valid Python). Rather than crashing, Evennia handles this by temporarily falling back to a safe default - `DefaultObject` - in order to keep your MUD running. Fix the original `SyntaxError` and reload the server. Evennia will then be able to use your modified `Character` class again and things should work.
The full error will show in the terminal/console but this is confusing since you did add
`get_abilities` before. Note however what the error says - you (`self`) should be a `Character` but
the error talks about `DefaultObject`. What has happened is that due to your unhandled `SyntaxError`
earlier, Evennia could not load the `character.py` module at all (it's not valid Python). Rather
than crashing, Evennia handles this by temporarily falling back to a safe default - `DefaultObject`
- in order to keep your MUD running. Fix the original `SyntaxError` and reload the server. Evennia
will then be able to use your modified `Character` class again and things should work.
> Note: Learning how to interpret an error traceback is a critical skill for anyone learning Python. Full tracebacks will appear in the terminal/Console you started Evennia from. The traceback text can sometimes be quite long, but you are usually just looking for the last few lines: The description of the error and the filename + line number for where the error occurred. In the example above, we see it's a `SyntaxError` happening at `line 33` of `mygame\typeclasses\characters.py`. In this case it even points out *where* on the line it encountered the error (the missing colon). Learn to read tracebacks and you'll be able to resolve the vast majority of common errors easily.
> Note: Learning how to interpret an error traceback is a critical skill for anyone learning Python.
Full tracebacks will appear in the terminal/Console you started Evennia from. The traceback text can
sometimes be quite long, but you are usually just looking for the last few lines: The description of
the error and the filename + line number for where the error occurred. In the example above, we see
it's a `SyntaxError` happening at `line 33` of `mygame\typeclasses\characters.py`. In this case it
even points out *where* on the line it encountered the error (the missing colon). Learn to read
tracebacks and you'll be able to resolve the vast majority of common errors easily.
### Add a New Default Command
The `@py` command used above is only available to privileged users. We want any player to be able to see their stats. Let's add a new [command](Commands) to list the abilities we added in the previous section.
The `@py` command used above is only available to privileged users. We want any player to be able to
see their stats. Let's add a new [command](Commands) to list the abilities we added in the previous
section.
1. Open `mygame/commands/command.py`. You could in principle put your command anywhere but this module has all the imports already set up along with some useful documentation. Make a new class at the bottom of this file:
1. Open `mygame/commands/command.py`. You could in principle put your command anywhere but this
module has all the imports already set up along with some useful documentation. Make a new class at
the bottom of this file:
```python
class CmdAbilities(Command):
@ -151,18 +203,21 @@ The `@py` command used above is only available to privileged users. We want any
1. [Reload](Start-Stop-Reload) the server (noone will be disconnected by doing this).
You (and anyone else) should now be able to use `abilities` (or its alias `abi`) as part of your normal commands in-game:
You (and anyone else) should now be able to use `abilities` (or its alias `abi`) as part of your
normal commands in-game:
```
abilities
STR: 5, AGI: 4, MAG: 2
```
See the [Adding a Command tutorial](Adding-Command-Tutorial) for more examples and the [Commands](Commands) section for detailed documentation about the Command system.
See the [Adding a Command tutorial](Adding-Command-Tutorial) for more examples and the
[Commands](Commands) section for detailed documentation about the Command system.
### Make a New Type of Object
Let's test to make a new type of object. This example is an "wise stone" object that returns some random comment when you look at it, like this:
Let's test to make a new type of object. This example is an "wise stone" object that returns some
random comment when you look at it, like this:
> look stone
@ -172,8 +227,10 @@ Let's test to make a new type of object. This example is an "wise stone" object
It grumbles and says: 'The world is like a rock of chocolate.'
1. Create a new module in `mygame/typeclasses/`. Name it `wiseobject.py` for this example.
1. In the module import the base `Object` (`typeclasses.objects.Object`). This is empty by default, meaning it is just a proxy for the default `evennia.DefaultObject`.
1. Make a new class in your module inheriting from `Object`. Overload hooks on it to add new functionality. Here is an example of how the file could look:
1. In the module import the base `Object` (`typeclasses.objects.Object`). This is empty by default,
meaning it is just a proxy for the default `evennia.DefaultObject`.
1. Make a new class in your module inheriting from `Object`. Overload hooks on it to add new
functionality. Here is an example of how the file could look:
```python
from random import choice
@ -204,14 +261,32 @@ Let's test to make a new type of object. This example is an "wise stone" object
return string + wisewords
```
1. Check your code for bugs. Tracebacks will appear on your command line or log. If you have a grave Syntax Error in your code, the source file itself will fail to load which can cause issues with the entire cmdset. If so, fix your bug and [reload the server from the command line](Start-Stop-Reload) (noone will be disconnected by doing this).
1. Use `@create/drop stone:wiseobject.WiseObject` to create a talkative stone. If the `@create` command spits out a warning or cannot find the typeclass (it will tell you which paths it searched), re-check your code for bugs and that you gave the correct path. The `@create` command starts looking for Typeclasses in `mygame/typeclasses/`.
1. Use `look stone` to test. You will see the default description ("You see nothing special") followed by a random message of stony wisdom. Use `@desc stone = This is a wise old stone.` to make it look nicer. See the [Builder Docs](Builder-Docs) for more information.
1. Check your code for bugs. Tracebacks will appear on your command line or log. If you have a grave
Syntax Error in your code, the source file itself will fail to load which can cause issues with the
entire cmdset. If so, fix your bug and [reload the server from the command line](Start-Stop-Reload)
(noone will be disconnected by doing this).
1. Use `@create/drop stone:wiseobject.WiseObject` to create a talkative stone. If the `@create`
command spits out a warning or cannot find the typeclass (it will tell you which paths it searched),
re-check your code for bugs and that you gave the correct path. The `@create` command starts looking
for Typeclasses in `mygame/typeclasses/`.
1. Use `look stone` to test. You will see the default description ("You see nothing special")
followed by a random message of stony wisdom. Use `@desc stone = This is a wise old stone.` to make
it look nicer. See the [Builder Docs](Builder-Docs) for more information.
Note that `at_object_creation` is only called once, when the stone is first created. If you make changes to this method later, already existing stones will not see those changes. As with the `Character` example above you can use `@typeclass/force` to tell the stone to re-run its initialization.
Note that `at_object_creation` is only called once, when the stone is first created. If you make
changes to this method later, already existing stones will not see those changes. As with the
`Character` example above you can use `@typeclass/force` to tell the stone to re-run its
initialization.
The `at_object_creation` is a special case though. Changing most other aspects of the typeclass does *not* require manual updating like this - you just need to `@reload` to have all changes applied automatically to all existing objects.
The `at_object_creation` is a special case though. Changing most other aspects of the typeclass does
*not* require manual updating like this - you just need to `@reload` to have all changes applied
automatically to all existing objects.
## Where to Go From Here?
There are more [Tutorials](Tutorials), including one for building a [whole little MUSH-like game](Tutorial-for-basic-MUSH-like-game) - that is instructive also if you have no interest in MUSHes per se. A good idea is to also get onto the [IRC chat](http://webchat.freenode.net/?channels=evennia) and the [mailing list](https://groups.google.com/forum/#!forum/evennia) to get in touch with the community and other developers.
There are more [Tutorials](Tutorials), including one for building a [whole little MUSH-like
game](Tutorial-for-basic-MUSH-like-game) - that is instructive also if you have no interest in
MUSHes per se. A good idea is to also get onto the [IRC
chat](http://webchat.freenode.net/?channels=evennia) and the [mailing
list](https://groups.google.com/forum/#!forum/evennia) to get in touch with the community and other
developers.

View file

@ -1,29 +1,58 @@
# Game Planning
So you have Evennia up and running. You have a great game idea in mind. Now it's time to start cracking! But where to start? Here are some ideas for a workflow. Note that the suggestions on this page are just that - suggestions. Also, they are primarily aimed at a lone hobby designer or a small team developing a game in their free time. There is an article in the Imaginary Realities e-zine which was written by the Evennia lead dev. It focuses more on you finding out your motivations for making a game - you can [read the article here](http://journal.imaginary-realities.com/volume-07/issue-03/where-do-i-begin/index.html).
So you have Evennia up and running. You have a great game idea in mind. Now it's time to start
cracking! But where to start? Here are some ideas for a workflow. Note that the suggestions on this
page are just that - suggestions. Also, they are primarily aimed at a lone hobby designer or a small
team developing a game in their free time. There is an article in the Imaginary Realities e-zine
which was written by the Evennia lead dev. It focuses more on you finding out your motivations for
making a game - you can [read the article here](http://journal.imaginary-
realities.com/volume-07/issue-03/where-do-i-begin/index.html).
Below are some minimal steps for getting the first version of a new game world going with players. It's worth to at least make the attempt to do these steps in order even if you are itching to jump ahead in the development cycle. On the other hand, you should also make sure to keep your work fun for you, or motivation will falter. Making a full game is a lot of work as it is, you'll need all your motivation to make it a reality.
Below are some minimal steps for getting the first version of a new game world going with players.
It's worth to at least make the attempt to do these steps in order even if you are itching to jump
ahead in the development cycle. On the other hand, you should also make sure to keep your work fun
for you, or motivation will falter. Making a full game is a lot of work as it is, you'll need all
your motivation to make it a reality.
Remember that *99.99999% of all great game ideas never lead to a game*. Especially not to an online game that people can actually play and enjoy. So our first all overshadowing goal is to beat those odds and get *something* out the door! Even if it's a scaled-down version of your dream game, lacking many "must-have" features! It's better to get it out there and expand on it later than to code in isolation forever until you burn out, lose interest or your hard drive crashes.
Remember that *99.99999% of all great game ideas never lead to a game*. Especially not to an online
game that people can actually play and enjoy. So our first all overshadowing goal is to beat those
odds and get *something* out the door! Even if it's a scaled-down version of your dream game,
lacking many "must-have" features! It's better to get it out there and expand on it later than to
code in isolation forever until you burn out, lose interest or your hard drive crashes.
Like is common with online games, getting a game out the door does not mean you are going to be "finished" with the game - most MUDs add features gradually over the course of years - it's often part of the fun!
Like is common with online games, getting a game out the door does not mean you are going to be
"finished" with the game - most MUDs add features gradually over the course of years - it's often
part of the fun!
## Planning (step 1)
This is what you do before having coded a single line or built a single room. Many prospective game developers are very good at *parts* of this process, namely in defining what their world is "about": The theme, the world concept, cool monsters and so on. It is by all means very important to define what is the unique appeal of your game. But it's unfortunately not enough to make your game a reality. To do that you must also have an idea of how to actually map those great ideas onto Evennia.
This is what you do before having coded a single line or built a single room. Many prospective game
developers are very good at *parts* of this process, namely in defining what their world is "about":
The theme, the world concept, cool monsters and so on. It is by all means very important to define
what is the unique appeal of your game. But it's unfortunately not enough to make your game a
reality. To do that you must also have an idea of how to actually map those great ideas onto
Evennia.
A good start is to begin by planning out the basic primitives of the game and what they need to be able to do. Below are a far-from-complete list of examples (and for your first version you should definitely try for a much shorter list):
A good start is to begin by planning out the basic primitives of the game and what they need to be
able to do. Below are a far-from-complete list of examples (and for your first version you should
definitely try for a much shorter list):
### Systems
These are the behind-the-scenes features that exist in your game, often without being represented by a specific in-game object.
These are the behind-the-scenes features that exist in your game, often without being represented by
a specific in-game object.
- Should your game rules be enforced by coded systems or are you planning for human game masters to run and arbitrate rules?
- What are the actual mechanical game rules? How do you decide if an action succeeds or fails? What "rolls" does the game need to be able to do? Do you base your game off an existing system or make up your own?
- Does the flow of time matter in your game - does night and day change? What about seasons? Maybe your magic system is affected by the phase of the moon?
- Do you want changing, global weather? This might need to operate in tandem over a large number of rooms.
- Should your game rules be enforced by coded systems or are you planning for human game masters to
run and arbitrate rules?
- What are the actual mechanical game rules? How do you decide if an action succeeds or fails? What
"rolls" does the game need to be able to do? Do you base your game off an existing system or make up
your own?
- Does the flow of time matter in your game - does night and day change? What about seasons? Maybe
your magic system is affected by the phase of the moon?
- Do you want changing, global weather? This might need to operate in tandem over a large number of
rooms.
- Do you want a game-wide economy or just a simple barter system? Or no formal economy at all?
- Should characters be able to send mail to each other in-game?
- Should players be able to post on Bulletin boards?
@ -35,8 +64,11 @@ These are the behind-the-scenes features that exist in your game, often without
Consider the most basic room in your game.
- Is a simple description enough or should the description be able to change (such as with time, by light conditions, weather or season)?
- Should the room have different statuses? Can it have smells, sounds? Can it be affected by dramatic weather, fire or magical effects? If so, how would this affect things in the room? Or are these things something admins/game masters should handle manually?
- Is a simple description enough or should the description be able to change (such as with time, by
light conditions, weather or season)?
- Should the room have different statuses? Can it have smells, sounds? Can it be affected by
dramatic weather, fire or magical effects? If so, how would this affect things in the room? Or are
these things something admins/game masters should handle manually?
- Can objects be hidden in the room? Can a person hide in the room? How does the room display this?
- etc.
@ -44,15 +76,22 @@ Consider the most basic room in your game.
Consider the most basic (non-player-controlled) object in your game.
- How numerous are your objects? Do you want large loot-lists or are objects just role playing props created on demand?
- Does the game use money? If so, is each coin a separate object or do you just store a bank account value?
- What about multiple identical objects? Do they form stacks and how are those stacks handled in that case?
- How numerous are your objects? Do you want large loot-lists or are objects just role playing props
created on demand?
- Does the game use money? If so, is each coin a separate object or do you just store a bank account
value?
- What about multiple identical objects? Do they form stacks and how are those stacks handled in
that case?
- Does an object have weight or volume (so you cannot carry an infinite amount of them)?
- Can objects be broken? If so, does it have a health value? Is burning it causing the same damage as smashing it? Can it be repaired?
- Is a weapon a specific type of object or are you supposed to be able to fight with a chair too? Can you fight with a flower or piece of paper as well?
- Can objects be broken? If so, does it have a health value? Is burning it causing the same damage
as smashing it? Can it be repaired?
- Is a weapon a specific type of object or are you supposed to be able to fight with a chair too?
Can you fight with a flower or piece of paper as well?
- NPCs/mobs are also objects. Should they just stand around or should they have some sort of AI?
- Are NPCs/mobs differet entities? How is an Orc different from a Kobold, in code - are they the same object with different names or completely different types of objects, with custom code?
- Should there be NPCs giving quests? If so, how would you track quest status and what happens when multiple players try to do the same quest? Do you use instances or some other mechanism?
- Are NPCs/mobs differet entities? How is an Orc different from a Kobold, in code - are they the
same object with different names or completely different types of objects, with custom code?
- Should there be NPCs giving quests? If so, how would you track quest status and what happens when
multiple players try to do the same quest? Do you use instances or some other mechanism?
- etc.
### Characters
@ -60,54 +99,120 @@ Consider the most basic (non-player-controlled) object in your game.
These are the objects controlled directly by Players.
- Can players have more than one Character active at a time or are they allowed to multi-play?
- How does a Player create their Character? A Character-creation screen? Answering questions? Filling in a form?
- How does a Player create their Character? A Character-creation screen? Answering questions?
Filling in a form?
- Do you want to use classes (like "Thief", "Warrior" etc) or some other system, like Skill-based?
- How do you implement different "classes" or "races"? Are they separate types of objects or do you simply load different stats on a basic object depending on what the Player wants?
- How do you implement different "classes" or "races"? Are they separate types of objects or do you
simply load different stats on a basic object depending on what the Player wants?
- If a Character can hide in a room, what skill will decide if they are detected?
- What skill allows a Character to wield a weapon and hit? Do they need a special skill to wield a chair rather than a sword?
- Does a Character need a Strength attribute to tell how much they can carry or which objects they can smash?
- What does the skill tree look like? Can a Character gain experience to improve? By killing enemies? Solving quests? By roleplaying?
- What skill allows a Character to wield a weapon and hit? Do they need a special skill to wield a
chair rather than a sword?
- Does a Character need a Strength attribute to tell how much they can carry or which objects they
can smash?
- What does the skill tree look like? Can a Character gain experience to improve? By killing
enemies? Solving quests? By roleplaying?
- etc.
A MUD's a lot more involved than you would think and these things hang together in a complex web. It can easily become overwhelming and it's tempting to want *all* functionality right out of the door. Try to identify the basic things that "make" your game and focus *only* on them for your first release. Make a list. Keep future expansions in mind but limit yourself.
A MUD's a lot more involved than you would think and these things hang together in a complex web. It
can easily become overwhelming and it's tempting to want *all* functionality right out of the door.
Try to identify the basic things that "make" your game and focus *only* on them for your first
release. Make a list. Keep future expansions in mind but limit yourself.
## Coding (step 2)
This is the actual work of creating the "game" part of your game. Many "game-designer" types tend to gloss over this bit and jump directly to **World Building**. Vice versa, many "game-coder" types tend to jump directly to this part without doing the **Planning** first. Neither way is good and *will* lead to you having to redo all your hard work at least once, probably more.
This is the actual work of creating the "game" part of your game. Many "game-designer" types tend to
gloss over this bit and jump directly to **World Building**. Vice versa, many "game-coder" types
tend to jump directly to this part without doing the **Planning** first. Neither way is good and
*will* lead to you having to redo all your hard work at least once, probably more.
Evennia's [Developer Central](Developer-Central) tries to help you with this bit of development. We also have a slew of [Tutorials](Tutorials) with worked examples. Evennia tries hard to make this part easier for you, but there is no way around the fact that if you want anything but a very basic Talker-type game you *will* have to bite the bullet and code your game (or find a coder willing to do it for you).
Evennia's [Developer Central](Developer-Central) tries to help you with this bit of development. We
also have a slew of [Tutorials](Tutorials) with worked examples. Evennia tries hard to make this
part easier for you, but there is no way around the fact that if you want anything but a very basic
Talker-type game you *will* have to bite the bullet and code your game (or find a coder willing to
do it for you).
Even if you won't code anything yourself, as a designer you need to at least understand the basic paradigms of Evennia, such as [Objects](Objects), [Commands](Commands) and [Scripts](Scripts) and how they hang together. We recommend you go through the [Tutorial World](Tutorial-World-Introduction) in detail (as well as glancing at its code) to get at least a feel for what is involved behind the scenes. You could also look through the tutorial for [building a game from scratch](Tutorial-for-basic-MUSH-like-game).
Even if you won't code anything yourself, as a designer you need to at least understand the basic
paradigms of Evennia, such as [Objects](Objects), [Commands](Commands) and [Scripts](Scripts) and
how they hang together. We recommend you go through the [Tutorial World](Tutorial-World-
Introduction) in detail (as well as glancing at its code) to get at least a feel for what is
involved behind the scenes. You could also look through the tutorial for [building a game from
scratch](Tutorial-for-basic-MUSH-like-game).
During Coding you look back at the things you wanted during the **Planning** phase and try to implement them. Don't be shy to update your plans if you find things easier/harder than you thought. The earlier you revise problems, the easier they will be to fix.
During Coding you look back at the things you wanted during the **Planning** phase and try to
implement them. Don't be shy to update your plans if you find things easier/harder than you thought.
The earlier you revise problems, the easier they will be to fix.
A good idea is to host your code online (publicly or privately) using version control. Not only will this make it easy for multiple coders to collaborate (and have a bug-tracker etc), it also means your work is backed up at all times. The [Version Control](Version-Control) tutorial has instructions for setting up a sane developer environment with proper version control.
A good idea is to host your code online (publicly or privately) using version control. Not only will
this make it easy for multiple coders to collaborate (and have a bug-tracker etc), it also means
your work is backed up at all times. The [Version Control](Version-Control) tutorial has
instructions for setting up a sane developer environment with proper version control.
### "Tech Demo" Building
This is an integral part of your Coding. It might seem obvious to experienced coders, but it cannot be emphasized enough that you should *test things on a small scale* before putting your untested code into a large game-world. The earlier you test, the easier and cheaper it will be to fix bugs and even rework things that didn't work out the way you thought they would. You might even have to go back to the **Planning** phase if your ideas can't handle their meet with reality.
This is an integral part of your Coding. It might seem obvious to experienced coders, but it cannot
be emphasized enough that you should *test things on a small scale* before putting your untested
code into a large game-world. The earlier you test, the easier and cheaper it will be to fix bugs
and even rework things that didn't work out the way you thought they would. You might even have to
go back to the **Planning** phase if your ideas can't handle their meet with reality.
This means building singular in-game examples. Make one room and one object of each important type and test so they work correctly in isolation. Then add more if they are supposed to interact with each other in some way. Build a small series of rooms to test how mobs move around ... and so on. In short, a test-bed for your growing code. It should be done gradually until you have a fully functioning (if not guaranteed bug-free) miniature tech demo that shows *all* the features you want in the first release of your game. There does not need to be any game play or even a theme to your tests, this is only for you and your co-coders to see. The more testing you do on this small scale, the less headaches you will have in the next phase.
This means building singular in-game examples. Make one room and one object of each important type
and test so they work correctly in isolation. Then add more if they are supposed to interact with
each other in some way. Build a small series of rooms to test how mobs move around ... and so on. In
short, a test-bed for your growing code. It should be done gradually until you have a fully
functioning (if not guaranteed bug-free) miniature tech demo that shows *all* the features you want
in the first release of your game. There does not need to be any game play or even a theme to your
tests, this is only for you and your co-coders to see. The more testing you do on this small scale,
the less headaches you will have in the next phase.
## World Building (step 3)
Up until this point we've only had a few tech-demo objects in the database. This step is the act of populating the database with a larger, thematic world. Too many would-be developers jump to this stage too soon (skipping the **Coding** or even **Planning** stages). What if the rooms you build now doesn't include all the nice weather messages the code grows to support? Or the way you store data changes under the hood? Your building work would at best require some rework and at worst you would have to redo the whole thing. And whereas Evennia's typeclass system does allow you to edit the properties of existing objects, some hooks are only called at object creation ... Suffice to say you are in for a *lot* of unnecessary work if you build stuff en masse without having the underlying code systems in some reasonable shape first.
Up until this point we've only had a few tech-demo objects in the database. This step is the act of
populating the database with a larger, thematic world. Too many would-be developers jump to this
stage too soon (skipping the **Coding** or even **Planning** stages). What if the rooms you build
now doesn't include all the nice weather messages the code grows to support? Or the way you store
data changes under the hood? Your building work would at best require some rework and at worst you
would have to redo the whole thing. And whereas Evennia's typeclass system does allow you to edit
the properties of existing objects, some hooks are only called at object creation ... Suffice to
say you are in for a *lot* of unnecessary work if you build stuff en masse without having the
underlying code systems in some reasonable shape first.
So before starting to build, the "game" bit (**Coding** + **Testing**) should be more or less **complete**, *at least to the level of your initial release*.
So before starting to build, the "game" bit (**Coding** + **Testing**) should be more or less
**complete**, *at least to the level of your initial release*.
Before starting to build, you should also plan ahead again. Make sure it is clear to yourself and your eventual builders just which parts of the world you want for your initial release. Establish for everyone which style, quality and level of detail you expect. Your goal should *not* be to complete your entire world in one go. You want just enough to make the game's "feel" come across. You want a minimal but functioning world where the intended game play can be tested and roughly balanced. You can always add new areas later.
Before starting to build, you should also plan ahead again. Make sure it is clear to yourself and
your eventual builders just which parts of the world you want for your initial release. Establish
for everyone which style, quality and level of detail you expect. Your goal should *not* be to
complete your entire world in one go. You want just enough to make the game's "feel" come across.
You want a minimal but functioning world where the intended game play can be tested and roughly
balanced. You can always add new areas later.
During building you get free and extensive testing of whatever custom build commands and systems you have made at this point. Since Building often involves different people than those Coding, you also get a chance to hear if some things are hard to understand or non-intuitive. Make sure to respond to this feedback.
During building you get free and extensive testing of whatever custom build commands and systems you
have made at this point. Since Building often involves different people than those Coding, you also
get a chance to hear if some things are hard to understand or non-intuitive. Make sure to respond
to this feedback.
## Alpha Release
As mentioned, don't hold onto your world more than necessary. *Get it out there* with a huge *Alpha* flag and let people try it! Call upon your alpha-players to try everything - they *will* find ways to break your game in ways that you never could have imagined. In Alpha you might be best off to focus on inviting friends and maybe other MUD developers, people who you can pester to give proper feedback and bug reports (there *will* be bugs, there is no way around it). Follow the quick instructions for [Online Setup](Online-Setup) to make your game visible online. If you hadn't already, make sure to put up your game on the [Evennia game index](http://games.evennia.com/) so people know it's in the works (actually, even pre-alpha games are allowed in the index so don't be shy)!
As mentioned, don't hold onto your world more than necessary. *Get it out there* with a huge *Alpha*
flag and let people try it! Call upon your alpha-players to try everything - they *will* find ways
to break your game in ways that you never could have imagined. In Alpha you might be best off to
focus on inviting friends and maybe other MUD developers, people who you can pester to give proper
feedback and bug reports (there *will* be bugs, there is no way around it). Follow the quick
instructions for [Online Setup](Online-Setup) to make your game visible online. If you hadn't
already, make sure to put up your game on the [Evennia game index](http://games.evennia.com/) so
people know it's in the works (actually, even pre-alpha games are allowed in the index so don't be
shy)!
## Beta Release/Perpetual Beta
Once things stabilize in Alpha you can move to *Beta* and let more people in. Many MUDs are in [perpetual beta](http://en.wikipedia.org/wiki/Perpetual_beta), meaning they are never considered "finished", but just repeat the cycle of Planning, Coding, Testing and Building over and over as new features get implemented or Players come with suggestions. As the game designer it is now up to you to gradually perfect your vision.
Once things stabilize in Alpha you can move to *Beta* and let more people in. Many MUDs are in
[perpetual beta](http://en.wikipedia.org/wiki/Perpetual_beta), meaning they are never considered
"finished", but just repeat the cycle of Planning, Coding, Testing and Building over and over as new
features get implemented or Players come with suggestions. As the game designer it is now up to you
to gradually perfect your vision.
## Congratulate yourself!
You are worthy of a celebration since at this point you have joined the small, exclusive crowd who have made their dream game a reality!
You are worthy of a celebration since at this point you have joined the small, exclusive crowd who
have made their dream game a reality!

View file

@ -1,20 +1,30 @@
# Gametime Tutorial
A lot of games use a separate time system we refer to as *game time*. This runs in parallel to what we usually think of as *real time*. The game time might run at a different speed, use different names for its time units or might even use a completely custom calendar. You don't need to rely on a game time system at all. But if you do, Evennia offers basic tools to handle these various situations. This tutorial will walk you through these features.
A lot of games use a separate time system we refer to as *game time*. This runs in parallel to what
we usually think of as *real time*. The game time might run at a different speed, use different
names for its time units or might even use a completely custom calendar. You don't need to rely on a
game time system at all. But if you do, Evennia offers basic tools to handle these various
situations. This tutorial will walk you through these features.
### A game time with a standard calendar
Many games let their in-game time run faster or slower than real time, but still use our normal real-world calendar. This is common both for games set in present day as well as for games in historical or futuristic settings. Using a standard calendar has some advantages:
Many games let their in-game time run faster or slower than real time, but still use our normal
real-world calendar. This is common both for games set in present day as well as for games in
historical or futuristic settings. Using a standard calendar has some advantages:
- Handling repetitive actions is much easier, since converting from the real time experience to the in-game perceived one is easy.
- The intricacies of the real world calendar, with leap years and months of different length etc are automatically handled by the system.
- Handling repetitive actions is much easier, since converting from the real time experience to the
in-game perceived one is easy.
- The intricacies of the real world calendar, with leap years and months of different length etc are
automatically handled by the system.
Evennia's game time features assume a standard calendar (see the relevant section below for a custom calendar).
Evennia's game time features assume a standard calendar (see the relevant section below for a custom
calendar).
#### Setting up game time for a standard calendar
All is done through the settings. Here are the settings you should use if you want a game time with a standard calendar:
All is done through the settings. Here are the settings you should use if you want a game time with
a standard calendar:
```python
# in a file settings.py in mygame/server/conf
@ -29,9 +39,16 @@ TIME_FACTOR = 2.0
TIME_GAME_EPOCH = None
```
By default, the game time runs twice as fast as the real time. You can set the time factor to be 1 (the game time would run exactly at the same speed than the real time) or lower (the game time will be slower than the real time). Most games choose to have the game time spinning faster (you will find some games that have a time factor of 60, meaning the game time runs sixty times as fast as the real time, a minute in real time would be an hour in game time).
By default, the game time runs twice as fast as the real time. You can set the time factor to be 1
(the game time would run exactly at the same speed than the real time) or lower (the game time will
be slower than the real time). Most games choose to have the game time spinning faster (you will
find some games that have a time factor of 60, meaning the game time runs sixty times as fast as the
real time, a minute in real time would be an hour in game time).
The epoch is a slightly more complex setting. It should contain a number of seconds that would indicate the time your game started. As indicated, an epoch of 0 would mean January 1st, 1970. If you want to set your time in the future, you just need to find the starting point in seconds. There are several ways to do this in Python, this method will show you how to do it in local time:
The epoch is a slightly more complex setting. It should contain a number of seconds that would
indicate the time your game started. As indicated, an epoch of 0 would mean January 1st, 1970. If
you want to set your time in the future, you just need to find the starting point in seconds. There
are several ways to do this in Python, this method will show you how to do it in local time:
```python
# We're looking for the number of seconds representing
@ -42,14 +59,16 @@ start = datetime(2020, 1, 1)
time.mktime(start.timetuple())
```
This should return a huge number - the number of seconds since Jan 1 1970. Copy that directly into your settings (editing `server/conf/settings.py`):
This should return a huge number - the number of seconds since Jan 1 1970. Copy that directly into
your settings (editing `server/conf/settings.py`):
```python
# in a file settings.py in mygame/server/conf
TIME_GAME_EPOCH = 1577865600
```
Reload the game with `@reload`, and then use the `@time` command. You should see something like this:
Reload the game with `@reload`, and then use the `@time` command. You should see something like
this:
```
+----------------------------+-------------------------------------+
@ -68,19 +87,28 @@ Reload the game with `@reload`, and then use the `@time` command. You should se
+----------------------------+-------------------------------------+
```
The line that is most relevant here is the game time epoch. You see it shown at 2020-01-01. From this point forward, the game time keeps increasing. If you keep typing `@time`, you'll see the game time updated correctly... and going (by default) twice as fast as the real time.
The line that is most relevant here is the game time epoch. You see it shown at 2020-01-01. From
this point forward, the game time keeps increasing. If you keep typing `@time`, you'll see the game
time updated correctly... and going (by default) twice as fast as the real time.
#### Time-related events
The `gametime` utility also has a way to schedule game-related events, taking into account your game time, and assuming a standard calendar (see below for the same feature with a custom calendar). For instance, it can be used to have a specific message every (in-game) day at 6:00 AM showing how the sun rises.
The `gametime` utility also has a way to schedule game-related events, taking into account your game
time, and assuming a standard calendar (see below for the same feature with a custom calendar). For
instance, it can be used to have a specific message every (in-game) day at 6:00 AM showing how the
sun rises.
The function `schedule()` should be used here. It will create a [script](Scripts) with some additional features to make sure the script is always executed when the game time matches the given parameters.
The function `schedule()` should be used here. It will create a [script](Scripts) with some
additional features to make sure the script is always executed when the game time matches the given
parameters.
The `schedule` function takes the following arguments:
- The *callback*, a function to be called when time is up.
- The keyword `repeat` (`False` by default) to indicate whether this function should be called repeatedly.
- Additional keyword arguments `sec`, `min`, `hour`, `day`, `month` and `year` to describe the time to schedule. If the parameter isn't given, it assumes the current time value of this specific unit.
- The keyword `repeat` (`False` by default) to indicate whether this function should be called
repeatedly.
- Additional keyword arguments `sec`, `min`, `hour`, `day`, `month` and `year` to describe the time
to schedule. If the parameter isn't given, it assumes the current time value of this specific unit.
Here is a short example for making the sun rise every day:
@ -108,25 +136,46 @@ If you want to test this function, you can easily do something like:
@py from world import ingame_time; ingame_time.start_sunrise_event()
```
The script will be created silently. The `at_sunrise` function will now be called every in-game day at 6 AM. You can use the `@scripts` command to see it. You could stop it using `@scripts/stop`. If we hadn't set `repeat` the sun would only have risen once and then never again.
The script will be created silently. The `at_sunrise` function will now be called every in-game day
at 6 AM. You can use the `@scripts` command to see it. You could stop it using `@scripts/stop`. If
we hadn't set `repeat` the sun would only have risen once and then never again.
We used the `@py` command here: nothing prevents you from adding the system into your game code. Remember to be careful not to add each event at startup, however, otherwise there will be a lot of overlapping events scheduled when the sun rises.
We used the `@py` command here: nothing prevents you from adding the system into your game code.
Remember to be careful not to add each event at startup, however, otherwise there will be a lot of
overlapping events scheduled when the sun rises.
The `schedule` function when using `repeat` set to `True` works with the higher, non-specified unit. In our example, we have specified hour, minute and second. The higher unit we haven't specified is day: `schedule` assumes we mean "run the callback every day at the specified time". Therefore, you can have an event that runs every hour at HH:30, or every month on the 3rd day.
The `schedule` function when using `repeat` set to `True` works with the higher, non-specified unit.
In our example, we have specified hour, minute and second. The higher unit we haven't specified is
day: `schedule` assumes we mean "run the callback every day at the specified time". Therefore, you
can have an event that runs every hour at HH:30, or every month on the 3rd day.
> A word of caution for repeated scripts on a monthly or yearly basis: due to the variations in the real-life calendar you need to be careful when scheduling events for the end of the month or year. For example, if you set a script to run every month on the 31st it will run in January but find no such day in February, April etc. Similarly, leap years may change the number of days in the year.
> A word of caution for repeated scripts on a monthly or yearly basis: due to the variations in the
real-life calendar you need to be careful when scheduling events for the end of the month or year.
For example, if you set a script to run every month on the 31st it will run in January but find no
such day in February, April etc. Similarly, leap years may change the number of days in the year.
### A game time with a custom calendar
Using a custom calendar to handle game time is sometimes needed if you want to place your game in a fictional universe. For instance you may want to create the Shire calendar which Tolkien described having 12 months, each which 30 days. That would give only 360 days per year (presumably hobbits weren't really fond of the hassle of following the astronomical calendar). Another example would be creating a planet in a different solar system with, say, days 29 hours long and months of only 18 days.
Using a custom calendar to handle game time is sometimes needed if you want to place your game in a
fictional universe. For instance you may want to create the Shire calendar which Tolkien described
having 12 months, each which 30 days. That would give only 360 days per year (presumably hobbits
weren't really fond of the hassle of following the astronomical calendar). Another example would be
creating a planet in a different solar system with, say, days 29 hours long and months of only 18
days.
Evennia handles custom calendars through an optional *contrib* module, called `custom_gametime`. Contrary to the normal `gametime` module described above it is not active by default.
Evennia handles custom calendars through an optional *contrib* module, called `custom_gametime`.
Contrary to the normal `gametime` module described above it is not active by default.
#### Setting up the custom calendar
In our first example of the Shire calendar, used by hobbits in books by Tolkien, we don't really need the notion of weeks... but we need the notion of months having 30 days, not 28.
In our first example of the Shire calendar, used by hobbits in books by Tolkien, we don't really
need the notion of weeks... but we need the notion of months having 30 days, not 28.
The custom calendar is defined by adding the `TIME_UNITS` setting to your settings file. It's a dictionary containing as keys the name of the units, and as value the number of seconds (the smallest unit for us) in this unit. Its keys must be picked among the following: "sec", "min", "hour", "day", "week", "month" and "year" but you don't have to include them all. Here is the configuration for the Shire calendar:
The custom calendar is defined by adding the `TIME_UNITS` setting to your settings file. It's a
dictionary containing as keys the name of the units, and as value the number of seconds (the
smallest unit for us) in this unit. Its keys must be picked among the following: "sec", "min",
"hour", "day", "week", "month" and "year" but you don't have to include them all. Here is the
configuration for the Shire calendar:
```python
# in a file settings.py in mygame/server/conf
@ -138,9 +187,12 @@ TIME_UNITS = {"sec": 1,
"year": 60 * 60 * 24 * 30 * 12 }
```
We give each unit we want as keys. Values represent the number of seconds in that unit. Hour is set to 60 * 60 (that is, 3600 seconds per hour). Notice that we don't specify the week unit in this configuration: instead, we skip from days to months directly.
We give each unit we want as keys. Values represent the number of seconds in that unit. Hour is
set to 60 * 60 (that is, 3600 seconds per hour). Notice that we don't specify the week unit in this
configuration: instead, we skip from days to months directly.
In order for this setting to work properly, remember all units have to be multiples of the previous units. If you create "day", it needs to be multiple of hours, for instance.
In order for this setting to work properly, remember all units have to be multiples of the previous
units. If you create "day", it needs to be multiple of hours, for instance.
So for our example, our settings may look like this:
@ -163,13 +215,19 @@ TIME_UNITS = {
}
```
Notice we have set a time epoch of 0. Using a custom calendar, we will come up with a nice display of time on our own. In our case the game time starts at year 0, month 0, day 0, and at midnight.
Notice we have set a time epoch of 0. Using a custom calendar, we will come up with a nice display
of time on our own. In our case the game time starts at year 0, month 0, day 0, and at midnight.
Note that while we use "month", "week" etc in the settings, your game may not use those terms in-game, instead referring to them as "cycles", "moons", "sand falls" etc. This is just a matter of you displaying them differently. See next section.
Note that while we use "month", "week" etc in the settings, your game may not use those terms in-
game, instead referring to them as "cycles", "moons", "sand falls" etc. This is just a matter of you
displaying them differently. See next section.
#### A command to display the current game time
As pointed out earlier, the `@time` command is meant to be used with a standard calendar, not a custom one. We can easily create a new command though. We'll call it `time`, as is often the case on other MU*. Here's an example of how we could write it (for the example, you can create a file `showtime.py` in your `commands` directory and paste this code in it):
As pointed out earlier, the `@time` command is meant to be used with a standard calendar, not a
custom one. We can easily create a new command though. We'll call it `time`, as is often the case
on other MU*. Here's an example of how we could write it (for the example, you can create a file
`showtime.py` in your `commands` directory and paste this code in it):
```python
# in a file mygame/commands/gametime.py
@ -227,13 +285,18 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
self.add(CmdTime()) # <- Add
```
Reload your game with the `@reload` command. You should now see the `time` command. If you enter it, you might see something like:
Reload your game with the `@reload` command. You should now see the `time` command. If you enter
it, you might see something like:
We are in year 0, day 0, month 0.
It's 00:52:17.
You could display it a bit more prettily with names for months and perhaps even days, if you want. And if "months" are called "moons" in your game, this is where you'd add that.
You could display it a bit more prettily with names for months and perhaps even days, if you want.
And if "months" are called "moons" in your game, this is where you'd add that.
#### Time-related events in custom gametime
The `custom_gametime` module also has a way to schedule game-related events, taking into account your game time (and your custom calendar). It can be used to have a specific message every day at 6:00 AM, to show the sun rises, for instance. The `custom_gametime.schedule` function works in the same way as described for the default one above.
The `custom_gametime` module also has a way to schedule game-related events, taking into account
your game time (and your custom calendar). It can be used to have a specific message every day at
6:00 AM, to show the sun rises, for instance. The `custom_gametime.schedule` function works in the
same way as described for the default one above.

View file

@ -33,7 +33,9 @@ more detailed instructions for your platform.
8. `cd mygame`
9. `evennia migrate`
10. `evennia start` (make sure to make a superuser when asked)
Evennia should now be running and you can connect to it by pointing a web browser to `http://localhost:4001` or a MUD telnet client to `localhost:4000` (use `127.0.0.1` if your OS does not recognize `localhost`).
Evennia should now be running and you can connect to it by pointing a web browser to
`http://localhost:4001` or a MUD telnet client to `localhost:4000` (use `127.0.0.1` if your OS does
not recognize `localhost`).
We also release [Docker images](Running-Evennia-in-Docker)
based on `master` and `develop` branches.
@ -55,9 +57,11 @@ updating Evennia itself - Mac users can use the
[git-osx-installer](http://code.google.com/p/git-osx-installer/) or the
[MacPorts version](http://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac).
- [Twisted](http://twistedmatrix.com) (v19.0+)
- [ZopeInterface](http://www.zope.org/Products/ZopeInterface) (v3.0+) - usually included in Twisted packages
- [ZopeInterface](http://www.zope.org/Products/ZopeInterface) (v3.0+) - usually included in
Twisted packages
- Linux/Mac users may need the `gcc` and `python-dev` packages or equivalent.
- Windows users need [MS Visual C++](https://aka.ms/vs/16/release/vs_buildtools.exe) and *maybe* [pypiwin32](https://pypi.python.org/pypi/pypiwin32).
- Windows users need [MS Visual C++](https://aka.ms/vs/16/release/vs_buildtools.exe) and *maybe*
[pypiwin32](https://pypi.python.org/pypi/pypiwin32).
- [Django](http://www.djangoproject.com) (v2.2.x), be warned that latest dev
version is usually untested with Evennia)
@ -71,14 +75,16 @@ install the [dependencies](Getting-Started#requirements):
```
sudo apt-get update
sudo apt-get install python3 python3-pip python3-dev python3-setuptools python3-git python3-virtualenv gcc
sudo apt-get install python3 python3-pip python3-dev python3-setuptools python3-git
python3-virtualenv gcc
# If you are using an Ubuntu version that defaults to Python3, like 18.04+, use this instead:
sudo apt-get update
sudo apt-get install python3.7 python3-pip python3.7-dev python3-setuptools virtualenv gcc
```
Note that, the default Python version for your distribution may still not be Python3.7 after this. This is ok - we'll specify exactly which Python to use later.
Note that, the default Python version for your distribution may still not be Python3.7 after this.
This is ok - we'll specify exactly which Python to use later.
You should make sure to *not* be `root` after this step, running as `root` is a
security risk. Now create a folder where you want to do all your Evennia
development:
@ -175,7 +181,8 @@ created. Check out [where to go next](Getting-Started#where-to-go-next).
## Mac Install
The Evennia server is a terminal program. Open the terminal e.g. from
*Applications->Utilities->Terminal*. [Here is an introduction to the Mac terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line)
*Applications->Utilities->Terminal*. [Here is an introduction to the Mac
terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line)
if you are unsure how it works. If you run into any issues during the
installation, please check out [Mac Troubleshooting](Getting-Started#mac-troubleshooting).
@ -294,11 +301,14 @@ If you run into any issues during the installation, please check out
The Evennia server itself is a command line program. In the Windows launch
menu, start *All Programs -> Accessories -> command prompt* and you will get
the Windows command line interface. Here is [one of many tutorials on using the Windows command line](http://www.bleepingcomputer.com/tutorials/windows-command-prompt-introduction/)
the Windows command line interface. Here is [one of many tutorials on using the Windows command
line](http://www.bleepingcomputer.com/tutorials/windows-command-prompt-introduction/)
if you are unfamiliar with it.
* Install Python [from the Python homepage](https://www.python.org/downloads/windows/). You will need to be a
Windows Administrator to install packages. You want Python version **3.7.0** (latest verified version), usually
* Install Python [from the Python homepage](https://www.python.org/downloads/windows/). You will
need to be a
Windows Administrator to install packages. You want Python version **3.7.0** (latest verified
version), usually
the 64-bit version (although it doesn't matter too much). **When installing, make sure
to check-mark *all* install options, especially the one about making Python
available on the path (you may have to scroll to see it)**. This allows you to
@ -311,8 +321,11 @@ Command Prompt", which gives you more freedom as to where you can use the
program.
* Finally you must install the [Microsoft Visual C++ compiler for
Python](https://aka.ms/vs/16/release/vs_buildtools.exe). Download and run the linked installer and
install the C++ tools. Keep all the defaults. Allow the install of the "Win10 SDK", even if you are on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due to a failure to build the "Twisted wheels", this is where you are missing things.
* You *may* need the [pypiwin32](https://pypi.python.org/pypi/pypiwin32) Python headers. Install these only if you have issues.
install the C++ tools. Keep all the defaults. Allow the install of the "Win10 SDK", even if you are
on Win7 (not tested on older Windows versions). If you later have issues with installing Evennia due
to a failure to build the "Twisted wheels", this is where you are missing things.
* You *may* need the [pypiwin32](https://pypi.python.org/pypi/pypiwin32) Python headers. Install
these only if you have issues.
You can install Evennia wherever you want. `cd` to that location and create a
new folder for all your Evennia development (let's call it `muddev`).
@ -381,8 +394,10 @@ folders when you use the `dir` command) and run
pip install -e evennia
```
For more info about `pip`, see the [Glossary entry on pip](Glossary#pip). If
the install failed with any issues, see [Windows Troubleshooting](Getting-Started#windows-troubleshooting).
Next we'll start our new game, we'll call it "mygame" here. This creates a new folder where you will be
the install failed with any issues, see [Windows Troubleshooting](Getting-Started#windows-
troubleshooting).
Next we'll start our new game, we'll call it "mygame" here. This creates a new folder where you will
be
creating your new game:
```
@ -423,13 +438,29 @@ logged in, stand in the `Limbo` room and run
@batchcommand tutorial_world.build
to build [Evennia's tutorial world](Tutorial-World-Introduction) - it's a small solo quest to explore. Only run the instructed `@batchcommand` once. You'll get a lot of text scrolling by as the tutorial is built. Once done, the `tutorial` exit will have appeared out of Limbo - just write `tutorial` to enter it.
to build [Evennia's tutorial world](Tutorial-World-Introduction) - it's a small solo quest to
explore. Only run the instructed `@batchcommand` once. You'll get a lot of text scrolling by as the
tutorial is built. Once done, the `tutorial` exit will have appeared out of Limbo - just write
`tutorial` to enter it.
Once you get back to `Limbo` from the tutorial (if you get stuck in the tutorial quest you can do `@tel #2` to jump to Limbo), a good idea is to learn how to [start, stop and reload](Start-Stop-Reload) the Evennia server. You may also want to familiarize yourself with some [commonly used terms in our Glossary](Glossary). After that, why not experiment with [creating some new items and build some new rooms](Building-Quickstart) out from Limbo.
Once you get back to `Limbo` from the tutorial (if you get stuck in the tutorial quest you can do
`@tel #2` to jump to Limbo), a good idea is to learn how to [start, stop and reload](Start-Stop-
Reload) the Evennia server. You may also want to familiarize yourself with some [commonly used terms
in our Glossary](Glossary). After that, why not experiment with [creating some new items and build
some new rooms](Building-Quickstart) out from Limbo.
From here on, you could move on to do one of our [introductory tutorials](Tutorials) or simply dive headlong into Evennia's comprehensive [manual](https://github.com/evennia/evennia/wiki). While Evennia has no major game systems out of the box, we do supply a range of optional *contribs* that you can use or borrow from. They range from dice rolling and alternative color schemes to barter and combat systems. You can find the [growing list of contribs here](https://github.com/evennia/evennia/blob/master/evennia/contrib/README.md).
From here on, you could move on to do one of our [introductory tutorials](Tutorials) or simply dive
headlong into Evennia's comprehensive [manual](https://github.com/evennia/evennia/wiki). While
Evennia has no major game systems out of the box, we do supply a range of optional *contribs* that
you can use or borrow from. They range from dice rolling and alternative color schemes to barter and
combat systems. You can find the [growing list of contribs
here](https://github.com/evennia/evennia/blob/master/evennia/contrib/README.md).
If you have any questions, you can always ask in [the developer chat](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) `#evennia` on `irc.freenode.net` or by posting to the [Evennia forums](https://groups.google.com/forum/#%21forum/evennia). You can also join the [Discord Server](https://discord.gg/NecFePw).
If you have any questions, you can always ask in [the developer
chat](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb)
`#evennia` on `irc.freenode.net` or by posting to the [Evennia
forums](https://groups.google.com/forum/#%21forum/evennia). You can also join the [Discord
Server](https://discord.gg/NecFePw).
Finally, if you are itching to help out or support Evennia (awesome!) have an
issue to report or a feature to request, [see here](How-To-Get-And-Give-Help).
@ -455,20 +486,51 @@ you can run `evennia -l`, or (in the future) start the server with `evennia star
- Under some not-updated Linux distributions you may run into errors with a
too-old `setuptools` or missing `functools`. If so, update your environment
with `pip install --upgrade pip wheel setuptools`. Then try `pip install -e evennia` again.
- One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; `Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/` with errors like `distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('incremental>=16.10.1')`. This appears possible to solve by simply updating Ubuntu with `sudo apt-get update && sudo apt-get dist-upgrade`.
- Users of Fedora (notably Fedora 24) has reported a `gcc` error saying the directory `/usr/lib/rpm/redhat/redhat-hardened-cc1` is missing, despite `gcc` itself being installed. [The confirmed work-around](https://gist.github.com/yograterol/99c8e123afecc828cb8c) seems to be to install the `redhat-rpm-config` package with e.g. `sudo dnf install redhat-rpm-config`.
- Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to yourself?)
- One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; `Command
"python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/` with errors
like `distutils.errors.DistutilsError: Could not find suitable distribution for
Requirement.parse('incremental>=16.10.1')`. This appears possible to solve by simply updating Ubuntu
with `sudo apt-get update && sudo apt-get dist-upgrade`.
- Users of Fedora (notably Fedora 24) has reported a `gcc` error saying the directory
`/usr/lib/rpm/redhat/redhat-hardened-cc1` is missing, despite `gcc` itself being installed. [The
confirmed work-around](https://gist.github.com/yograterol/99c8e123afecc828cb8c) seems to be to
install the `redhat-rpm-config` package with e.g. `sudo dnf install redhat-rpm-config`.
- Some users trying to set up a virtualenv on an NTFS filesystem find that it fails due to issues
with symlinks not being supported. Answer is to not use NTFS (seriously, why would you do that to
yourself?)
### Mac Troubleshooting
- Mac users have reported a critical `MemoryError` when trying to start Evennia on Mac with a Python version below `2.7.12`. If you get this error, update to the latest XCode and Python2 version.
- Some Mac users have reported not being able to connect to `localhost` (i.e. your own computer). If so, try to connect to `127.0.0.1` instead, which is the same thing. Use port 4000 from mud clients and port 4001 from the web browser as usual.
- Mac users have reported a critical `MemoryError` when trying to start Evennia on Mac with a Python
version below `2.7.12`. If you get this error, update to the latest XCode and Python2 version.
- Some Mac users have reported not being able to connect to `localhost` (i.e. your own computer). If
so, try to connect to `127.0.0.1` instead, which is the same thing. Use port 4000 from mud clients
and port 4001 from the web browser as usual.
### Windows Troubleshooting
- If you installed Python but the `python` command is not available (even in a new console), then you might have missed installing Python on the path. In the Windows Python installer you get a list of options for what to install. Most or all options are pre-checked except this one, and you may even have to scroll down to see it. Reinstall Python and make sure it's checked.
- If your MUD client cannot connect to `localhost:4000`, try the equivalent `127.0.0.1:4000` instead. Some MUD clients on Windows does not appear to understand the alias `localhost`.
- If you run `virtualenv evenv` and get a `'virtualenv' is not recognized as an internal or external command,
operable program or batch file.` error, you can `mkdir evenv`, `cd evenv` and then `python -m virtualenv .` as a workaround.
- Some Windows users get an error installing the Twisted 'wheel'. A wheel is a pre-compiled binary package for Python. A common reason for this error is that you are using a 32-bit version of Python, but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a slightly older Twisted version. So if, say, version `18.1` failed, install `18.0` manually with `pip install twisted==18.0`. Alternatively you could try to get a 64-bit version of Python (uninstall the 32bit one). If so, you must then `deactivate` the virtualenv, delete the `evenv` folder and recreate it anew (it will then use the new Python executable).
- If your server won't start, with no error messages (and no log files at all when starting from scratch), try to start with `evennia ipstart` instead. If you then see an error about `system cannot find the path specified`, it may be that the file `evennia/evennia/server/twistd.bat` has the wrong path to the `twistd` executable. This file is auto-generated, so try to delete it and then run `evennia start` to rebuild it and see if it works. If it still doesn't work you need to open it in a text editor like Notepad. It's just one line containing the path to the `twistd.exe` executable as determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and you should update the line to the real location.
- If you installed Python but the `python` command is not available (even in a new console), then
you might have missed installing Python on the path. In the Windows Python installer you get a list
of options for what to install. Most or all options are pre-checked except this one, and you may
even have to scroll down to see it. Reinstall Python and make sure it's checked.
- If your MUD client cannot connect to `localhost:4000`, try the equivalent `127.0.0.1:4000`
instead. Some MUD clients on Windows does not appear to understand the alias `localhost`.
- If you run `virtualenv evenv` and get a `'virtualenv' is not recognized as an internal or external
command,
operable program or batch file.` error, you can `mkdir evenv`, `cd evenv` and then `python -m
virtualenv .` as a workaround.
- Some Windows users get an error installing the Twisted 'wheel'. A wheel is a pre-compiled binary
package for Python. A common reason for this error is that you are using a 32-bit version of Python,
but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a
slightly older Twisted version. So if, say, version `18.1` failed, install `18.0` manually with `pip
install twisted==18.0`. Alternatively you could try to get a 64-bit version of Python (uninstall the
32bit one). If so, you must then `deactivate` the virtualenv, delete the `evenv` folder and recreate
it anew (it will then use the new Python executable).
- If your server won't start, with no error messages (and no log files at all when starting from
scratch), try to start with `evennia ipstart` instead. If you then see an error about `system cannot
find the path specified`, it may be that the file `evennia/evennia/server/twistd.bat` has the wrong
path to the `twistd` executable. This file is auto-generated, so try to delete it and then run
`evennia start` to rebuild it and see if it works. If it still doesn't work you need to open it in a
text editor like Notepad. It's just one line containing the path to the `twistd.exe` executable as
determined by Evennia. If you installed Twisted in a non-standard location this might be wrong and
you should update the line to the real location.

View file

@ -7,25 +7,30 @@ This explains common recurring terms used in the Evennia docs. It will be expand
- _[admin-site](Glossary#admin-site)_ - the Django web page for manipulating the database
- _[attribute](Glossary#attribute)_ - persistent, custom data stored on typeclasses
- _[channel](Glossary#channel)_ - game communication channels
- _[character](Glossary#character)_ - the player's avatar in the game, controlled from _[account](Glossary#account)_
- _[character](Glossary#character)_ - the player's avatar in the game, controlled from
_[account](Glossary#account)_
- _[core](Glossary#core)_ - a term used for the code distributed with Evennia proper
- _[django](Glossary#django)_ - web framework Evennia uses for database access and web integration
- _[field](Glossary#field)_ - a _[typeclass](Glossary#typeclass)_ property representing a database column
- _[field](Glossary#field)_ - a _[typeclass](Glossary#typeclass)_ property representing a database
column
- _[git](Glossary#git)_ - the version-control system we use
- _[github](Glossary#github)_ - the online hosting of our source code
- _[migrate](Glossary#migrate)_ - updating the database schema
- _[multisession mode`](#multisession-mode)_ - a setting defining how users connect to Evennia
- _[object](Glossary#object)_ - Python instance, general term or in-game _[typeclass](Glossary#typeclass)_
- _[object](Glossary#object)_ - Python instance, general term or in-game
_[typeclass](Glossary#typeclass)_
- _[pip](Glossary#pip)_ - the Python installer
- _player_ - the human connecting to the game with their client
- _[puppet](Glossary#puppet)_ - when an [account](Glossary#account) controls an in-game [object](Glossary#object)
- _[puppet](Glossary#puppet)_ - when an [account](Glossary#account) controls an in-game
[object](Glossary#object)
- _[property](Glossary#property)_ - a python property
- _evenv_ - see _[virtualenv](Glossary#virtualenv)_
- _[repository](Glossary#repository)_ - a store of source code + source history
- _[script](Glossary#script)_ - a building block for custom storage, systems and time-keepint
- _[session](Glossary#session)_ - represents one client connection
- _[ticker](Glossary#ticker)_ - Allows to run events on a steady 'tick'
- _[twisted](Glossary#twisted)_ - networking engine responsible for Evennia's event loop and communications
- _[twisted](Glossary#twisted)_ - networking engine responsible for Evennia's event loop and
communications
- _[typeclass](Glossary#typeclass)_ - Evennia's database-connected Python class
- _upstream_ - see _[github](Glossary#github)_
- _[virtualenv](Glossary#virtualenv)_ - a Python program and way to make an isolated Python install
@ -35,168 +40,321 @@ This explains common recurring terms used in the Evennia docs. It will be expand
### _account_
The term 'account' refers to the [player's](Glossary#player) unique account on the game. It is represented by the `Account` [typeclass](Glossary#typeclass) and holds things like email, password, configuration etc.
The term 'account' refers to the [player's](Glossary#player) unique account on the game. It is
represented by the `Account` [typeclass](Glossary#typeclass) and holds things like email, password,
configuration etc.
When a player connects to the game, they connect to their account. The account has *no* representation in the game world. Through their Account they can instead choose to [puppet](Glossary#puppet) one (or more, depending on game mode) [Characters](Glossary#character) in the game.
When a player connects to the game, they connect to their account. The account has *no*
representation in the game world. Through their Account they can instead choose to
[puppet](Glossary#puppet) one (or more, depending on game mode) [Characters](Glossary#character) in
the game.
In the default [multisession mode](Sessions#multisession-mode) of Evennia, you immediately start puppeting a Character with the same name as your Account when you log in - mimicking how older servers used to work.
In the default [multisession mode](Sessions#multisession-mode) of Evennia, you immediately start
puppeting a Character with the same name as your Account when you log in - mimicking how older
servers used to work.
### _admin-site_
This usually refers to [Django's](Glossary#django) *Admin site* or database-administration web page ([link to Django docs](https://docs.djangoproject.com/en/2.1/ref/contrib/admin/)). The admin site is an automatically generated web interface to the database (it can be customized extensively). It's reachable from the `admin` link on the default Evennia website you get with your server.
This usually refers to [Django's](Glossary#django) *Admin site* or database-administration web page
([link to Django docs](https://docs.djangoproject.com/en/2.1/ref/contrib/admin/)). The admin site is
an automatically generated web interface to the database (it can be customized extensively). It's
reachable from the `admin` link on the default Evennia website you get with your server.
### _attribute_
The term _Attribute_ should not be confused with ([properties](Glossary#property) or [fields](Glossary#field). The `Attribute` represents arbitrary pieces of data that can be attached to any [typeclassed](Glossary#typeclass) entity in Evennia. Attributes allows storing new persistent data on typeclasses without changing their underlying database schemas. [Read more about Attributes here](Attributes).
The term _Attribute_ should not be confused with ([properties](Glossary#property) or
[fields](Glossary#field). The `Attribute` represents arbitrary pieces of data that can be attached
to any [typeclassed](Glossary#typeclass) entity in Evennia. Attributes allows storing new persistent
data on typeclasses without changing their underlying database schemas. [Read more about Attributes
here](Attributes).
### _channel_
A _Channel_ refers to an in-game communication channel. It's an entity that people subscribe to and which re-distributes messages between all subscribers. Such subscribers default to being [Accounts](Glossary#account), for out-of-game communication but could also be [Objects (usually Characters)](Glossary#character) if one wanted to adopt Channels for things like in-game walkie-talkies or phone systems. It is represented by the `Channel` typeclass. [You can read more about the comm system here](Communications#channels).
A _Channel_ refers to an in-game communication channel. It's an entity that people subscribe to and
which re-distributes messages between all subscribers. Such subscribers default to being
[Accounts](Glossary#account), for out-of-game communication but could also be [Objects (usually
Characters)](Glossary#character) if one wanted to adopt Channels for things like in-game walkie-
talkies or phone systems. It is represented by the `Channel` typeclass. [You can read more about the
comm system here](Communications#channels).
### _character_
The _Character_ is the term we use for the default avatar being [puppeted](Glossary#puppet) by the [account](Glossary#account) in the game world. It is represented by the `Character` typeclass (which is a child of [Object](Glossary#object)). Many developers use children of this class to represent monsters and other NPCs. You can [read more about it here](Objects#subclasses-of-object).
The _Character_ is the term we use for the default avatar being [puppeted](Glossary#puppet) by the
[account](Glossary#account) in the game world. It is represented by the `Character` typeclass (which
is a child of [Object](Glossary#object)). Many developers use children of this class to represent
monsters and other NPCs. You can [read more about it here](Objects#subclasses-of-object).
### _django_
[Django](https://www.djangoproject.com/) is a professional and very popular Python web framework, similar to Rails for the Ruby language. It is one of Evennia's central library dependencies (the other one is [Twisted](Glossary#twisted)). Evennia uses Django for two main things - to map all database operations to Python and for structuring our web site.
[Django](https://www.djangoproject.com/) is a professional and very popular Python web framework,
similar to Rails for the Ruby language. It is one of Evennia's central library dependencies (the
other one is [Twisted](Glossary#twisted)). Evennia uses Django for two main things - to map all
database operations to Python and for structuring our web site.
Through Django, we can work with any supported database (SQlite3, Postgres, MySQL ...) using generic Python instead of database-specific SQL: A database table is represented in Django as a Python class (called a *model*). An Python instance of such a class represents a row in that table.
Through Django, we can work with any supported database (SQlite3, Postgres, MySQL ...) using generic
Python instead of database-specific SQL: A database table is represented in Django as a Python class
(called a *model*). An Python instance of such a class represents a row in that table.
There is usually no need to know the details of Django's database handling in order to use Evennia - it will handle most of the complexity for you under the hood using what we call [typeclasses](Glossary#typeclass). But should you need the power of Django you can always get it. Most commonly people want to use "raw" Django when doing more advanced/custom database queries than offered by Evennia's [default search functions](Tutorial-Searching-For-Objects). One will then need to read about Django's _querysets_. Querysets are Python method calls on a special form that lets you build complex queries. They get converted into optimized SQL queries under the hood, suitable for your current database. [Here is our tutorial/explanation of Django queries](Tutorial-Searching-For-Objects#queries-in-django).
There is usually no need to know the details of Django's database handling in order to use Evennia -
it will handle most of the complexity for you under the hood using what we call
[typeclasses](Glossary#typeclass). But should you need the power of Django you can always get it.
Most commonly people want to use "raw" Django when doing more advanced/custom database queries than
offered by Evennia's [default search functions](Tutorial-Searching-For-Objects). One will then need
to read about Django's _querysets_. Querysets are Python method calls on a special form that lets
you build complex queries. They get converted into optimized SQL queries under the hood, suitable
for your current database. [Here is our tutorial/explanation of Django queries](Tutorial-Searching-
For-Objects#queries-in-django).
> By the way, Django (and Evennia) does allow you to fall through and send raw SQL if you really want to. It's highly unlikely to be needed though; the Django database abstraction is very, very powerful.
> By the way, Django (and Evennia) does allow you to fall through and send raw SQL if you really
want to. It's highly unlikely to be needed though; the Django database abstraction is very, very
powerful.
The other aspect where Evennia uses Django is for web integration. On one end Django gives an infrastructure for wiring Python functions (called *views*) to URLs: the view/function is called when a user goes that URL in their browser, enters data into a form etc. The return is the web page to show. Django also offers templating with features such as being able to add special markers in HTML where it will insert the values of Python variables on the fly (like showing the current player count on the web page). [Here is one of our tutorials on wiring up such a web page](Add-a-simple-new-web-page). Django also comes with the [admin site](Glossary#admin-site), which automatically maps the database into a form accessible from a web browser.
The other aspect where Evennia uses Django is for web integration. On one end Django gives an
infrastructure for wiring Python functions (called *views*) to URLs: the view/function is called
when a user goes that URL in their browser, enters data into a form etc. The return is the web page
to show. Django also offers templating with features such as being able to add special markers in
HTML where it will insert the values of Python variables on the fly (like showing the current player
count on the web page). [Here is one of our tutorials on wiring up such a web page](Add-a-simple-
new-web-page). Django also comes with the [admin site](Glossary#admin-site), which automatically
maps the database into a form accessible from a web browser.
### _core_
This term is sometimes used to represent the main Evennia library code suite, *excluding* its [contrib](Glossary#contrib) directory. It can sometimes come up in code reviews, such as
This term is sometimes used to represent the main Evennia library code suite, *excluding* its
[contrib](Glossary#contrib) directory. It can sometimes come up in code reviews, such as
> Evennia is game-agnostic but this feature is for a particular game genre. So it does not belong in core. Better make it a contrib.
> Evennia is game-agnostic but this feature is for a particular game genre. So it does not belong in
core. Better make it a contrib.
### _field_
A _field_ or _database field_ in Evennia refers to a [property](Glossary#property) on a [typeclass](Glossary#typeclass) directly linked to an underlying database column. Only a few fixed properties per typeclass are database fields but they are often tied to the core functionality of that base typeclass (for example [Objects](Glossary#object) store its location as a field). In all other cases, [attributes](Glossary#attribute) are used to add new persistent data to the typeclass. [Read more about typeclass properties here](Typeclasses#about-typeclass-properties).
A _field_ or _database field_ in Evennia refers to a [property](Glossary#property) on a
[typeclass](Glossary#typeclass) directly linked to an underlying database column. Only a few fixed
properties per typeclass are database fields but they are often tied to the core functionality of
that base typeclass (for example [Objects](Glossary#object) store its location as a field). In all
other cases, [attributes](Glossary#attribute) are used to add new persistent data to the typeclass.
[Read more about typeclass properties here](Typeclasses#about-typeclass-properties).
### _git_
[Git](https://git-scm.com/) is a [version control](https://en.wikipedia.org/wiki/Version_control) tool. It allows us to track the development of the Evennia code by dividing it into units called *commits*. A 'commit' is sort of a save-spot - you save the current state of your code and can then come back to it later if later changes caused problems. By tracking commits we know what 'version' of the code we are currently using.
[Git](https://git-scm.com/) is a [version control](https://en.wikipedia.org/wiki/Version_control)
tool. It allows us to track the development of the Evennia code by dividing it into units called
*commits*. A 'commit' is sort of a save-spot - you save the current state of your code and can then
come back to it later if later changes caused problems. By tracking commits we know what 'version'
of the code we are currently using.
Evennia's source code + its source history is jointly called a [repository](Glossary#repository). This is centrally stored at our online home on [GitHub](Glossary#github). Everyone using or developing Evennia makes a 'clone' of this repository to their own computer - everyone automatically gets everything that is online, including all the code history.
Evennia's source code + its source history is jointly called a [repository](Glossary#repository).
This is centrally stored at our online home on [GitHub](Glossary#github). Everyone using or
developing Evennia makes a 'clone' of this repository to their own computer - everyone
automatically gets everything that is online, including all the code history.
> Don't confuse Git and [GitHub](Glossary#github). The former is the version control system. The latter is a website (run by a company) that allows you to upload source code controlled by Git for others to see (among other things).
> Don't confuse Git and [GitHub](Glossary#github). The former is the version control system. The
latter is a website (run by a company) that allows you to upload source code controlled by Git for
others to see (among other things).
Git allows multiple users from around the world to efficiently collaborate on Evennia's code: People can make local commits on their cloned code. The commits they do can then be uploaded to GitHub and reviewed by the Evennia lead devs - and if the changes look ok they can be safely *merged* into the central Evennia code - and everyone can *pull* those changes to update their local copies.
Git allows multiple users from around the world to efficiently collaborate on Evennia's code: People
can make local commits on their cloned code. The commits they do can then be uploaded to GitHub and
reviewed by the Evennia lead devs - and if the changes look ok they can be safely *merged* into the
central Evennia code - and everyone can *pull* those changes to update their local copies.
Developers using Evennia often uses Git on their own games in the same way - to track their changes and to help collaboration with team mates. This is done completely independently of Evennia's Git usage.
Developers using Evennia often uses Git on their own games in the same way - to track their changes
and to help collaboration with team mates. This is done completely independently of Evennia's Git
usage.
Common usage (for non-Evennia developers):
- `git clone <github-url>` - clone an online repository to your computer. This is what you do when you 'download' Evennia. You only need to do this once.
- `git clone <github-url>` - clone an online repository to your computer. This is what you do when
you 'download' Evennia. You only need to do this once.
- `git pull` (inside local copy of repository) - sync your local repository with what is online.
> Full usage of Git is way beyond the scope of this glossary. See [Tutorial - version control](Version-Control) for more info and links to the Git documentation.
> Full usage of Git is way beyond the scope of this glossary. See [Tutorial - version
control](Version-Control) for more info and links to the Git documentation.
### _migrate_
This term is used for upgrading the database structure (it's _schema_ )to a new version. Most often this is due to Evennia's [upstream](Glossary#github) schema changing. When that happens you need to migrate that schema to the new version as well. Once you have used [git](Glossary#git) to pull the latest changes, just `cd` into your game dir and run
This term is used for upgrading the database structure (it's _schema_ )to a new version. Most often
this is due to Evennia's [upstream](Glossary#github) schema changing. When that happens you need to
migrate that schema to the new version as well. Once you have used [git](Glossary#git) to pull the
latest changes, just `cd` into your game dir and run
evennia migrate
That should be it (see [virtualenv](Glossary#virtualenv) if you get a warning that the `evennia` command is not available). See also [Updating your game](Updating-Your-Game) for more details.
That should be it (see [virtualenv](Glossary#virtualenv) if you get a warning that the `evennia`
command is not available). See also [Updating your game](Updating-Your-Game) for more details.
> Technically, migrations are shipped as little Python snippets of code that explains which database actions must be taken to upgrade from one version of the schema to the next. When you run the command above, those snippets are run in sequence.
> Technically, migrations are shipped as little Python snippets of code that explains which database
actions must be taken to upgrade from one version of the schema to the next. When you run the
command above, those snippets are run in sequence.
### _multisession mode_
This term refers to the `MULTISESSION_MODE` setting, which has a value of 0 to 3. The mode alters how players can connect to the game, such as how many Sessions a player can start with one account and how many Characters they can control at the same time. It is [described in detail here](Sessions#multisession-mode).
This term refers to the `MULTISESSION_MODE` setting, which has a value of 0 to 3. The mode alters
how players can connect to the game, such as how many Sessions a player can start with one account
and how many Characters they can control at the same time. It is [described in detail
here](Sessions#multisession-mode).
### _github_
[Github](https://github.com/evennia) is where Evennia's source code and documentation is hosted. This online [repository](Glossary#repository) of code we also sometimes refer to as _upstream_.
[Github](https://github.com/evennia) is where Evennia's source code and documentation is hosted.
This online [repository](Glossary#repository) of code we also sometimes refer to as _upstream_.
GitHub is a business, offering free hosting to Open-source projects like Evennia. Despite the similarity in name, don't confuse GitHub the website with [Git](Glossary#git), the versioning system. Github hosts Git [repositories](Glossary#repository) online and helps with collaboration and infrastructure. Git itself is a separate project.
GitHub is a business, offering free hosting to Open-source projects like Evennia. Despite the
similarity in name, don't confuse GitHub the website with [Git](Glossary#git), the versioning
system. Github hosts Git [repositories](Glossary#repository) online and helps with collaboration and
infrastructure. Git itself is a separate project.
### _object_
In general Python (and other [object-oriented languages](https://en.wikipedia.org/wiki/Object-oriented_programming)), an `object` is what we call the instance of a *class*. But one of Evennia's core [typeclasses](Glossary#typeclasss) is also called "Object". To separate these in the docs we try to use `object` to refer to the general term and capitalized `Object` when we refer to the typeclass.
In general Python (and other [object-oriented languages](https://en.wikipedia.org/wiki/Object-
oriented_programming)), an `object` is what we call the instance of a *class*. But one of Evennia's
core [typeclasses](Glossary#typeclasss) is also called "Object". To separate these in the docs we
try to use `object` to refer to the general term and capitalized `Object` when we refer to the
typeclass.
The `Object` is a typeclass that represents all *in-game* entities, including [Characters](Glossary#character), rooms, trees, weapons etc. [Read more about Objects here](Objects).
The `Object` is a typeclass that represents all *in-game* entities, including
[Characters](Glossary#character), rooms, trees, weapons etc. [Read more about Objects
here](Objects).
### _pip_
_[pip](https://pypi.org/project/pip/)_ comes with Python and is the main tool for installing third-party Python packages from the web. Once a python package is installed you can do `import <packagename>` in your Python code.
_[pip](https://pypi.org/project/pip/)_ comes with Python and is the main tool for installing third-
party Python packages from the web. Once a python package is installed you can do `import
<packagename>` in your Python code.
Common usage:
- `pip install <package-name>` - install the given package along with all its dependencies.
- `pip search <name>` - search Python's central package repository [PyPi](https://pypi.org/) for a package of that name.
- `pip search <name>` - search Python's central package repository [PyPi](https://pypi.org/) for a
package of that name.
- `pip install --upgrade <package_name>` - upgrade a package you already have to the latest version.
- `pip install <packagename>==1.5` - install exactly a specific package version.
- `pip install <folder>` - install a Python package you have downloaded earlier (or cloned using git).
- `pip install -e <folder>` - install a local package by just making a soft link to the folder. This means that if the code in `<folder>` changes, the installed Python package is immediately updated. If not using `-e`, one would need to run `pip install --upgrade <folder>` every time to make the changes available when you import this package into your code. Evennia is installed this way.
- `pip install <folder>` - install a Python package you have downloaded earlier (or cloned using
git).
- `pip install -e <folder>` - install a local package by just making a soft link to the folder. This
means that if the code in `<folder>` changes, the installed Python package is immediately updated.
If not using `-e`, one would need to run `pip install --upgrade <folder>` every time to make the
changes available when you import this package into your code. Evennia is installed this way.
For development, `pip` is usually used together with a [virtualenv](Glossary#virtualenv) to install all packages and dependencies needed for a project in one, isolated location on the hard drive.
For development, `pip` is usually used together with a [virtualenv](Glossary#virtualenv) to install
all packages and dependencies needed for a project in one, isolated location on the hard drive.
### _puppet_
An [account](Glossary#account) can take control and "play as" any [Object](Glossary#object). When doing so, we call this _puppeting_, (like [puppeteering](https://en.wikipedia.org/wiki/Puppeteer)). Normally the entity being puppeted is of the [Character](Glossary#character) subclass but it does not have to be.
An [account](Glossary#account) can take control and "play as" any [Object](Glossary#object). When
doing so, we call this _puppeting_, (like [puppeteering](https://en.wikipedia.org/wiki/Puppeteer)).
Normally the entity being puppeted is of the [Character](Glossary#character) subclass but it does
not have to be.
### _property_
A _property_ is a general term used for properties on any Python object. The term also sometimes refers to the `property` built-in function of Python ([read more here](https://www.python-course.eu/python3_properties.php)). Note the distinction between properties, [fields](Glossary#field) and [Attributes](Glossary#attribute).
A _property_ is a general term used for properties on any Python object. The term also sometimes
refers to the `property` built-in function of Python ([read more here](https://www.python-
course.eu/python3_properties.php)). Note the distinction between properties,
[fields](Glossary#field) and [Attributes](Glossary#attribute).
### _repository_
A _repository_ is a version control/[git](Glossary#git) term. It represents a folder containing source code plus its versioning history.
A _repository_ is a version control/[git](Glossary#git) term. It represents a folder containing
source code plus its versioning history.
> In Git's case, that history is stored in a hidden folder `.git`. If you ever feel the need to look into this folder you probably already know enough Git to know why.
> In Git's case, that history is stored in a hidden folder `.git`. If you ever feel the need to look
into this folder you probably already know enough Git to know why.
The `evennia` folder you download from us with `git clone` is a repository. The code on [GitHub](Glossary#github) is often referred to as the 'online repository' (or the _upstream_ repository). If you put your game dir under version control, that of course becomes a repository as well.
The `evennia` folder you download from us with `git clone` is a repository. The code on
[GitHub](Glossary#github) is often referred to as the 'online repository' (or the _upstream_
repository). If you put your game dir under version control, that of course becomes a repository as
well.
### _script_
When we refer to _Scripts_, we generally refer to the `Script` [typeclass](Typeclasses). Scripts are the mavericks of Evennia - they are like [Objects](Glossary#object) but without any in-game existence. They are useful as custom places to store data but also as building blocks in persistent game systems. Since the can be initialized with timing capabilities they can also be used for long-time persistent time keeping (for fast updates other types of timers may be better though). [Read more about Scripts here](Scripts)
When we refer to _Scripts_, we generally refer to the `Script` [typeclass](Typeclasses). Scripts are
the mavericks of Evennia - they are like [Objects](Glossary#object) but without any in-game
existence. They are useful as custom places to store data but also as building blocks in persistent
game systems. Since the can be initialized with timing capabilities they can also be used for long-
time persistent time keeping (for fast updates other types of timers may be better though). [Read
more about Scripts here](Scripts)
### _session_
A [Session](Sessions) is a Python object representing a single client connection to the server. A given human player could connect to the game from different clients and each would get a Session (even if you did not allow them to actually log in and get access to an [account](Glossary#account)).
A [Session](Sessions) is a Python object representing a single client connection to the server. A
given human player could connect to the game from different clients and each would get a Session
(even if you did not allow them to actually log in and get access to an
[account](Glossary#account)).
Sessions are _not_ [typeclassed](Glossary#typeclass) and has no database persistence. But since they always exist (also when not logged in), they share some common functionality with typeclasses that can be useful for certain game states.
Sessions are _not_ [typeclassed](Glossary#typeclass) and has no database persistence. But since they
always exist (also when not logged in), they share some common functionality with typeclasses that
can be useful for certain game states.
### _ticker_
The [Ticker handler](TickerHandler) runs Evennia's optional 'ticker' system. In other engines, such as [DIKU](https://en.wikipedia.org/wiki/DikuMUD), all game events are processed only at specific intervals called 'ticks'. Evennia has no such technical limitation (events are processed whenever needed) but using a fixed tick can still be useful for certain types of game systems, like combat. Ticker Handler allows you to emulate any number of tick rates (not just one) and subscribe actions to be called when those ticks come around.
The [Ticker handler](TickerHandler) runs Evennia's optional 'ticker' system. In other engines, such
as [DIKU](https://en.wikipedia.org/wiki/DikuMUD), all game events are processed only at specific
intervals called 'ticks'. Evennia has no such technical limitation (events are processed whenever
needed) but using a fixed tick can still be useful for certain types of game systems, like combat.
Ticker Handler allows you to emulate any number of tick rates (not just one) and subscribe actions
to be called when those ticks come around.
### _typeclass_
The [typeclass](Typeclasses) is an Evennia-specific term. A typeclass allows developers to work with database-persistent objects as if they were normal Python objects. It makes use of specific [Django](Glossary#django) features to link a Python class to a database table. Sometimes we refer to such code entities as _being typeclassed_.
The [typeclass](Typeclasses) is an Evennia-specific term. A typeclass allows developers to work with
database-persistent objects as if they were normal Python objects. It makes use of specific
[Django](Glossary#django) features to link a Python class to a database table. Sometimes we refer to
such code entities as _being typeclassed_.
Evennia's main typeclasses are [Account](Glossary#account), [Object](Glossary#object), [Script](Glossary#script) and [Channel](Glossary#channel). Children of the base class (such as [Character](Glossary#character)) will use the same database table as the parent, but can have vastly different Python capabilities (and persistent features through [Attributes](Glossary#attributes) and [Tags](Glossary#tags). A typeclass can be coded and treated pretty much like any other Python class except it must inherit (at any distance) from one of the base typeclasses. Also, creating a new instance of a typeclass will add a new row to the database table to which it is linked.
Evennia's main typeclasses are [Account](Glossary#account), [Object](Glossary#object),
[Script](Glossary#script) and [Channel](Glossary#channel). Children of the base class (such as
[Character](Glossary#character)) will use the same database table as the parent, but can have vastly
different Python capabilities (and persistent features through [Attributes](Glossary#attributes) and
[Tags](Glossary#tags). A typeclass can be coded and treated pretty much like any other Python class
except it must inherit (at any distance) from one of the base typeclasses. Also, creating a new
instance of a typeclass will add a new row to the database table to which it is linked.
The [core](Glossary#core) typeclasses in the Evennia library are all named `DefaultAccount`, `DefaultObject` etc. When you initialize your [game dir] you automatically get empty children of these, called `Account`, `Object` etc that you can start working with.
The [core](Glossary#core) typeclasses in the Evennia library are all named `DefaultAccount`,
`DefaultObject` etc. When you initialize your [game dir] you automatically get empty children of
these, called `Account`, `Object` etc that you can start working with.
### _twisted_
[Twisted](https://twistedmatrix.com/trac/) is a heavy-duty asynchronous networking engine. It is one of Evennia's two major library dependencies (the other one is [Django](Glossary#django)). Twisted is what "runs" Evennia - it handles Evennia's event loop. Twisted also has the building blocks we need to construct network protocols and communicate with the outside world; such as our MUD-custom version of Telnet, Telnet+SSL, SSH, webclient-websockets etc. Twisted also runs our integrated web server, serving the Django-based website for your game.
[Twisted](https://twistedmatrix.com/trac/) is a heavy-duty asynchronous networking engine. It is one
of Evennia's two major library dependencies (the other one is [Django](Glossary#django)). Twisted is
what "runs" Evennia - it handles Evennia's event loop. Twisted also has the building blocks we need
to construct network protocols and communicate with the outside world; such as our MUD-custom
version of Telnet, Telnet+SSL, SSH, webclient-websockets etc. Twisted also runs our integrated web
server, serving the Django-based website for your game.
### _virtualenv_
The standard [virtualenv](https://virtualenv.pypa.io/en/stable/) program comes with Python. It is used to isolate all Python packages needed by a given Python project into one folder (we call that folder `evenv` but it could be called anything). A package environment created this way is usually referred to as "a virtualenv". If you ever try to run the `evennia` program and get an error saying something like "the command 'evennia' is not available" - it's probably because your virtualenv is not 'active' yet (see below).
The standard [virtualenv](https://virtualenv.pypa.io/en/stable/) program comes with Python. It is
used to isolate all Python packages needed by a given Python project into one folder (we call that
folder `evenv` but it could be called anything). A package environment created this way is usually
referred to as "a virtualenv". If you ever try to run the `evennia` program and get an error saying
something like "the command 'evennia' is not available" - it's probably because your virtualenv is
not 'active' yet (see below).
Usage:
- `virtualenv <name>` - initialize a new virtualenv `<name>` in a new folder `<name>` in the current location. Called `evenv` in these docs.
- `virtualenv -p path/to/alternate/python_executable <name>` - create a virtualenv using another Python version than default.
- `virtualenv <name>` - initialize a new virtualenv `<name>` in a new folder `<name>` in the current
location. Called `evenv` in these docs.
- `virtualenv -p path/to/alternate/python_executable <name>` - create a virtualenv using another
Python version than default.
- `source <folder_name>/bin/activate`(linux/mac) - activate the virtualenv in `<folder_name>`.
- `<folder_name>\Scripts\activate` (windows)
- `deactivate` - turn off the currently activated virtualenv.
A virtualenv is 'activated' only for the console/terminal it was started in, but it's safe to activate the same virtualenv many times in different windows if you want. Once activated, all Python packages now installed with [pip](Glossary#pip) will install to `evenv` rather than to a global location like `/usr/local/bin` or `C:\Program Files`.
A virtualenv is 'activated' only for the console/terminal it was started in, but it's safe to
activate the same virtualenv many times in different windows if you want. Once activated, all Python
packages now installed with [pip](Glossary#pip) will install to `evenv` rather than to a global
location like `/usr/local/bin` or `C:\Program Files`.
> Note that if you have root/admin access you *could* install Evennia globally just fine, without using a virtualenv. It's strongly discouraged and considered bad practice though. Experienced Python developers tend to rather create one new virtualenv per project they are working on, to keep the varying installs cleanly separated from one another.
> Note that if you have root/admin access you *could* install Evennia globally just fine, without
using a virtualenv. It's strongly discouraged and considered bad practice though. Experienced Python
developers tend to rather create one new virtualenv per project they are working on, to keep the
varying installs cleanly separated from one another.
When you execute Python code within this activated virtualenv, *only* those packages installed within will be possible to `import` into your code. So if you installed a Python package globally on your computer, you'll need to install it again in your virtualenv.
When you execute Python code within this activated virtualenv, *only* those packages installed
within will be possible to `import` into your code. So if you installed a Python package globally on
your computer, you'll need to install it again in your virtualenv.
> Virtualenvs *only* deal with Python programs/packages. Other programs on your computer couldn't care less if your virtualenv is active or not. So you could use `git` without the virtualenv being active, for example.
> Virtualenvs *only* deal with Python programs/packages. Other programs on your computer couldn't
care less if your virtualenv is active or not. So you could use `git` without the virtualenv being
active, for example.
When your virtualenv is active you should see your console/terminal prompt change to
@ -204,6 +362,11 @@ When your virtualenv is active you should see your console/terminal prompt chang
... or whatever name you gave the virtualenv when you initialized it.
> We sometimes say that we are "in" the virtualenv when it's active. But just to be clear - you never have to actually `cd` into the `evenv` folder. You can activate it from anywhere and will still be considered "in" the virtualenv wherever you go until you `deactivate` or close the console/terminal.
> We sometimes say that we are "in" the virtualenv when it's active. But just to be clear - you
never have to actually `cd` into the `evenv` folder. You can activate it from anywhere and will
still be considered "in" the virtualenv wherever you go until you `deactivate` or close the
console/terminal.
So, when do I *need* to activate my virtualenv? If the virtualenv is not active, none of the Python packages/programs you installed in it will be available to you. So at a minimum, *it needs to be activated whenever you want to use the `evennia` command* for any reason.
So, when do I *need* to activate my virtualenv? If the virtualenv is not active, none of the Python
packages/programs you installed in it will be available to you. So at a minimum, *it needs to be
activated whenever you want to use the `evennia` command* for any reason.

View file

@ -68,4 +68,4 @@ Write something in the Evennia channel *gw* and check so a message appears in
the Grapevine chat. Write a reply in the chat and the grapevine bot should echo
it to your channel in-game.
Your Evennia gamers can now chat with users on external Grapevine channels!
Your Evennia gamers can now chat with users on external Grapevine channels!

View file

@ -1,18 +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.
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](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.
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](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](Typeclasses) for guests. Defaults to `"typeclasses.accounts.Guest"`.
- `PERMISSION_GUEST_DEFAULT` - [permission level](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).
- `BASE_GUEST_TYPECLASS` - the python-path to the default [typeclass](Typeclasses) for guests.
Defaults to `"typeclasses.accounts.Guest"`.
- `PERMISSION_GUEST_DEFAULT` - [permission level](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"`.
- `GUEST_LIST` - this is a list holding the possible guest names to use when entering the game. The
length of this list also sets how many guests may log in at the same time. By default this is a list
of nine names from `"Guest1"` to `"Guest9"`.

View file

@ -34,11 +34,13 @@ defaults
# Evennia Specifics
listen evennia-https-website
bind <public-ip-address>:<public-SSL-port--probably-443> ssl no-sslv3 no-tlsv10 crt /path/to/your-cert.pem
bind <public-ip-address>:<public-SSL-port--probably-443> ssl no-sslv3 no-tlsv10 crt
/path/to/your-cert.pem
server localhost 127.0.0.1:<evennia-web-port-probably-4001>
listen evennia-secure-websocket
bind <public-ip-address>:<WEBSOCKET_CLIENT_URL 4002> ssl no-sslv3 no-tlsv10 crt /path/to/your-cert.pem
bind <public-ip-address>:<WEBSOCKET_CLIENT_URL 4002> ssl no-sslv3 no-tlsv10 crt /path/to/your-
cert.pem
server localhost 127.0.0.1:<WEBSOCKET_CLIENT_URL 4002>
timeout client 10m
timeout server 10m

View file

@ -1,9 +1,12 @@
# Help System Tutorial
**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](Web-Tutorial).** Reading the three first parts of the [Django tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/) might help as well.
**Before doing this tutorial you will probably want to read the intro in [Basic Web tutorial](Web-
Tutorial).** Reading the three first parts of the [Django
tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/) might help as well.
This tutorial will show you how to access the help system through your website. Both help commands and regular help entries will be visible, depending on the logged-in user or an anonymous character.
This tutorial will show you how to access the help system through your website. Both help commands
and regular help entries will be visible, depending on the logged-in user or an anonymous character.
This tutorial will show you how to:
@ -14,15 +17,23 @@ This tutorial will show you how to:
## Creating our app
The first step is to create our new Django *app*. An app in Django can contain pages and mechanisms: your website may contain different apps. Actually, the website provided out-of-the-box by Evennia has already three apps: a "webclient" app, to handle the entire webclient, a "website" app to contain your basic pages, and a third app provided by Django to create a simple admin interface. So we'll create another app in parallel, giving it a clear name to represent our help system.
The first step is to create our new Django *app*. An app in Django can contain pages and
mechanisms: your website may contain different apps. Actually, the website provided out-of-the-box
by Evennia has already three apps: a "webclient" app, to handle the entire webclient, a "website"
app to contain your basic pages, and a third app provided by Django to create a simple admin
interface. So we'll create another app in parallel, giving it a clear name to represent our help
system.
From your game directory, use the following command:
evennia startapp help_system
> Note: calling the app "help" would have been more explicit, but this name is already used by Django.
> Note: calling the app "help" would have been more explicit, but this name is already used by
Django.
This will create a directory named `help_system` at the root of your game directory. It's a good idea to keep things organized and move this directory in the "web" directory of your game. Your game directory should look like:
This will create a directory named `help_system` at the root of your game directory. It's a good
idea to keep things organized and move this directory in the "web" directory of your game. Your
game directory should look like:
mygame/
...
@ -30,9 +41,13 @@ This will create a directory named `help_system` at the root of your game direct
help_system/
...
The "web/help_system" directory contains files created by Django. We'll use some of them, but if you want to learn more about them all, you should read [the Django tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/).
The "web/help_system" directory contains files created by Django. We'll use some of them, but if
you want to learn more about them all, you should read [the Django
tutorial](https://docs.djangoproject.com/en/1.9/intro/tutorial01/).
There is a last thing to be done: your folder has been added, but Django doesn't know about it, it doesn't know it's a new app. We need to tell it, and we do so by editing a simple setting. Open your "server/conf/settings.py" file and add, or edit, these lines:
There is a last thing to be done: your folder has been added, but Django doesn't know about it, it
doesn't know it's a new app. We need to tell it, and we do so by editing a simple setting. Open
your "server/conf/settings.py" file and add, or edit, these lines:
```python
# Web configuration
@ -41,23 +56,30 @@ INSTALLED_APPS += (
)
```
You can start Evennia if you want, and go to your website, probably at [http://localhost:4001](http://localhost:4001) . You won't see anything different though: we added the app but it's fairly empty.
You can start Evennia if you want, and go to your website, probably at
[http://localhost:4001](http://localhost:4001) . You won't see anything different though: we added
the app but it's fairly empty.
## Our new page
At this point, our new *app* contains mostly empty files that you can explore. In order to create a page for our help system, we need to add:
At this point, our new *app* contains mostly empty files that you can explore. In order to create
a page for our help system, we need to add:
- A *view*, dealing with the logic of our page.
- A *template* to display our new page.
- A new *URL* pointing to our page.
> We could get away by creating just a view and a new URL, but that's not a recommended way to work with your website. Building on templates is so much more convenient.
> We could get away by creating just a view and a new URL, but that's not a recommended way to work
with your website. Building on templates is so much more convenient.
### Create a view
A *view* in Django is a simple Python function placed in the "views.py" file in your app. It will handle the behavior that is triggered when a user asks for this information by entering a *URL* (the connection between *views* and *URLs* will be discussed later).
A *view* in Django is a simple Python function placed in the "views.py" file in your app. It will
handle the behavior that is triggered when a user asks for this information by entering a *URL* (the
connection between *views* and *URLs* will be discussed later).
So let's create our view. You can open the "web/help_system/view.py" file and paste the following lines:
So let's create our view. You can open the "web/help_system/view.py" file and paste the following
lines:
```python
from django.shortcuts import render
@ -67,11 +89,16 @@ def index(request):
return render(request, "help_system/index.html")
```
Our view handles all code logic. This time, there's not much: when this function is called, it will render the template we will now create. But that's where we will do most of our work afterward.
Our view handles all code logic. This time, there's not much: when this function is called, it will
render the template we will now create. But that's where we will do most of our work afterward.
### Create a template
The `render` function called into our *view* asks the *template* `help_system/index.html`. The *templates* of our apps are stored in the app directory, "templates" sub-directory. Django may have created the "templates" folder already. If not, create it yourself. In it, create another folder "help_system", and inside of this folder, create a file named "index.html". Wow, that's some hierarchy. Your directory structure (starting from `web`) should look like this:
The `render` function called into our *view* asks the *template* `help_system/index.html`. The
*templates* of our apps are stored in the app directory, "templates" sub-directory. Django may have
created the "templates" folder already. If not, create it yourself. In it, create another folder
"help_system", and inside of this folder, create a file named "index.html". Wow, that's some
hierarchy. Your directory structure (starting from `web`) should look like this:
web/
help_system/
@ -92,15 +119,22 @@ Open the "index.html" file and paste in the following lines:
Here's a little explanation line by line of what this template does:
1. It loads the "base.html" *template*. This describes the basic structure of all your pages, with a menu at the top and a footer, and perhaps other information like images and things to be present on each page. You can create templates that do not inherit from "base.html", but you should have a good reason for doing so.
2. The "base.html" *template* defines all the structure of the page. What is left is to override some sections of our pages. These sections are called *blocks*. On line 2, we override the block named "blocktitle", which contains the title of our page.
3. Same thing here, we override the *block* named "content", which contains the main content of our web page. This block is bigger, so we define it on several lines.
1. It loads the "base.html" *template*. This describes the basic structure of all your pages, with
a menu at the top and a footer, and perhaps other information like images and things to be present
on each page. You can create templates that do not inherit from "base.html", but you should have a
good reason for doing so.
2. The "base.html" *template* defines all the structure of the page. What is left is to override
some sections of our pages. These sections are called *blocks*. On line 2, we override the block
named "blocktitle", which contains the title of our page.
3. Same thing here, we override the *block* named "content", which contains the main content of our
web page. This block is bigger, so we define it on several lines.
4. This is perfectly normal HTML code to display a level-2 heading.
5. And finally we close the *block* named "content".
### Create a new URL
Last step to add our page: we need to add a *URL* leading to it... otherwise users won't be able to access it. The URLs of our apps are stored in the app's directory "urls.py" file.
Last step to add our page: we need to add a *URL* leading to it... otherwise users won't be able to
access it. The URLs of our apps are stored in the app's directory "urls.py" file.
Open the "web/help_system/urls.py" file (you might have to create it) and write in it:
@ -115,7 +149,8 @@ urlpatterns = [
]
```
We also need to add our app as a namespace holder for URLS. Edit the file "web/urls.py". In it you will find the `custom_patterns` variable. Replace it with:
We also need to add our app as a namespace holder for URLS. Edit the file "web/urls.py". In it you
will find the `custom_patterns` variable. Replace it with:
```python
custom_patterns = [
@ -126,14 +161,21 @@ custom_patterns = [
When a user will ask for a specific *URL* on your site, Django will:
1. Read the list of custom patterns defined in "web/urls.py". There's one pattern here, which describes to Django that all URLs beginning by 'help/' should be sent to the 'help_system' app. The 'help/' part is removed.
2. Then Django will check the "web.help_system/urls.py" file. It contains only one URL, which is empty (`^$`).
1. Read the list of custom patterns defined in "web/urls.py". There's one pattern here, which
describes to Django that all URLs beginning by 'help/' should be sent to the 'help_system' app. The
'help/' part is removed.
2. Then Django will check the "web.help_system/urls.py" file. It contains only one URL, which is
empty (`^$`).
In other words, if the URL is '/help/', then Django will execute our defined view.
### Let's see it work
You can now reload or start Evennia. Open a tab in your browser and go to [http://localhost:4001/help/](http://localhost:4001/help/) . If everything goes well, you should see your new page... which isn't empty since Evennia uses our "base.html" *template*. In the content of our page, there's only a heading that reads "help index". Notice that the title of our page is "mygame - Help index" ("mygame" is replaced by the name of your game).
You can now reload or start Evennia. Open a tab in your browser and go to
[http://localhost:4001/help/](http://localhost:4001/help/) . If everything goes well, you should
see your new page... which isn't empty since Evennia uses our "base.html" *template*. In the
content of our page, there's only a heading that reads "help index". Notice that the title of our
page is "mygame - Help index" ("mygame" is replaced by the name of your game).
From now on, it will be easier to move forward and add features.
@ -153,17 +195,30 @@ The first one would link to the second.
> Should we create two URLs?
The answer is... maybe. It depends on what you want to do. We have our help index accessible through the "/help/" URL. We could have the detail of a help entry accessible through "/help/desc" (to see the detail of the "desc" command). The problem is that our commands or help topics may contain special characters that aren't to be present in URLs. There are different ways around this problem. I have decided to use a *GET variable* here, which would create URLs like this:
The answer is... maybe. It depends on what you want to do. We have our help index accessible
through the "/help/" URL. We could have the detail of a help entry accessible through "/help/desc"
(to see the detail of the "desc" command). The problem is that our commands or help topics may
contain special characters that aren't to be present in URLs. There are different ways around this
problem. I have decided to use a *GET variable* here, which would create URLs like this:
/help?name=desc
If you use this system, you don't have to add a new URL: GET and POST variables are accessible through our requests and we'll see how soon enough.
If you use this system, you don't have to add a new URL: GET and POST variables are accessible
through our requests and we'll see how soon enough.
## Handling logged-in users
One of our requirements is to have a help system tailored to our accounts. If an account with admin access logs in, the page should display a lot of commands that aren't accessible to common users. And perhaps even some additional help topics.
One of our requirements is to have a help system tailored to our accounts. If an account with admin
access logs in, the page should display a lot of commands that aren't accessible to common users.
And perhaps even some additional help topics.
Fortunately, it's fairly easy to get the logged in account in our view (remember that we'll do most of our coding there). The *request* object, passed to our function, contains a `user` attribute. This attribute will always be there: we cannot test whether it's `None` or not, for instance. But when the request comes from a user that isn't logged in, the `user` attribute will contain an anonymous Django user. We then can use the `is_anonymous` method to see whether the user is logged-in or not. Last gift by Evennia, if the user is logged in, `request.user` contains a reference to an account object, which will help us a lot in coupling the game and online system.
Fortunately, it's fairly easy to get the logged in account in our view (remember that we'll do most
of our coding there). The *request* object, passed to our function, contains a `user` attribute.
This attribute will always be there: we cannot test whether it's `None` or not, for instance. But
when the request comes from a user that isn't logged in, the `user` attribute will contain an
anonymous Django user. We then can use the `is_anonymous` method to see whether the user is logged-
in or not. Last gift by Evennia, if the user is logged in, `request.user` contains a reference to
an account object, which will help us a lot in coupling the game and online system.
So we might end up with something like:
@ -175,7 +230,8 @@ def index(request):
character = user.character
```
> Note: this code works when your MULTISESSION_MODE is set to 0 or 1. When it's above, you would have something like:
> Note: this code works when your MULTISESSION_MODE is set to 0 or 1. When it's above, you would
have something like:
```python
def index(request):
@ -187,7 +243,9 @@ def index(request):
In this second case, it will select the first character of the account.
But what if the user's not logged in? Again, we have different solutions. One of the most simple is to create a character that will behave as our default character for the help system. You can create it through your game: connect to it and enter:
But what if the user's not logged in? Again, we have different solutions. One of the most simple
is to create a character that will behave as our default character for the help system. You can
create it through your game: connect to it and enter:
@charcreate anonymous
@ -209,13 +267,16 @@ def index(request):
character = Character.objects.get(db_key="anonymous")
```
This time, we have a valid character no matter what: remember to adapt this code if you're running in multisession mode above 1.
This time, we have a valid character no matter what: remember to adapt this code if you're running
in multisession mode above 1.
## The full system
What we're going to do is to browse through all commands and help entries, and list all the commands that can be seen by this character (either our 'anonymous' character, or our logged-in character).
What we're going to do is to browse through all commands and help entries, and list all the commands
that can be seen by this character (either our 'anonymous' character, or our logged-in character).
The code is longer, but it presents the entire concept in our view. Edit the "web/help_system/views.py" file and paste into it:
The code is longer, but it presents the entire concept in our view. Edit the
"web/help_system/views.py" file and paste into it:
```python
from django.http import Http404
@ -310,16 +371,25 @@ That's a bit more complicated here, but all in all, it can be divided in small c
- The `index` function is our view:
- It begins by getting the character as we saw in the previous section.
- It gets the help topics (commands and help entries) accessible to this character. It's another function that handles that part.
- If there's a *GET variable* "name" in our URL (like "/help?name=drop"), it will retrieve it. If it's not a valid topic's name, it returns a *404*. Otherwise, it renders the template called "detail.html", to display the detail of our topic.
- It gets the help topics (commands and help entries) accessible to this character. It's another
function that handles that part.
- If there's a *GET variable* "name" in our URL (like "/help?name=drop"), it will retrieve it. If
it's not a valid topic's name, it returns a *404*. Otherwise, it renders the template called
"detail.html", to display the detail of our topic.
- If there's no *GET variable* "name", render "index.html", to display the list of topics.
- The `_get_topics` is a private function. Its sole mission is to retrieve the commands a character can execute, and the help entries this same character can see. This code is more Evennia-specific than Django-specific, it will not be detailed in this tutorial. Just notice that all help topics are stored in a dictionary. This is to simplify our job when displaying them in our templates.
- The `_get_topics` is a private function. Its sole mission is to retrieve the commands a character
can execute, and the help entries this same character can see. This code is more Evennia-specific
than Django-specific, it will not be detailed in this tutorial. Just notice that all help topics
are stored in a dictionary. This is to simplify our job when displaying them in our templates.
Notice that, in both cases when we asked to render a *template*, we passed to `render` a third argument which is the dictionary of variables used in our templates. We can pass variables this way, and we will use them in our templates.
Notice that, in both cases when we asked to render a *template*, we passed to `render` a third
argument which is the dictionary of variables used in our templates. We can pass variables this
way, and we will use them in our templates.
### The index template
Let's look at our full "index" *template*. You can open the "web/help_system/templates/help_sstem/index.html" file and paste the following into it:
Let's look at our full "index" *template*. You can open the
"web/help_system/templates/help_sstem/index.html" file and paste the following into it:
```
{% extends "base.html" %}
@ -350,12 +420,17 @@ This template is definitely more detailed. What it does is:
1. Browse through all categories.
2. For all categories, display a level-2 heading with the name of the category.
3. All topics in a category (remember, they can be either commands or help entries) are displayed in a table. The trickier part may be that, when the loop is above 5, it will create a new line. The table will have 5 columns at the most per row.
4. For every cell in the table, we create a link redirecting to the detail page (see below). The URL would look something like "help?name=say". We use `urlencode` to ensure special characters are properly escaped.
3. All topics in a category (remember, they can be either commands or help entries) are displayed in
a table. The trickier part may be that, when the loop is above 5, it will create a new line. The
table will have 5 columns at the most per row.
4. For every cell in the table, we create a link redirecting to the detail page (see below). The
URL would look something like "help?name=say". We use `urlencode` to ensure special characters are
properly escaped.
### The detail template
It's now time to show the detail of a topic (command or help entry). You can create the file "web/help_system/templates/help_system/detail.html". You can paste into it the following code:
It's now time to show the detail of a topic (command or help entry). You can create the file
"web/help_system/templates/help_system/detail.html". You can paste into it the following code:
```
{% extends "base.html" %}
@ -367,17 +442,26 @@ It's now time to show the detail of a topic (command or help entry). You can cr
{% endblock %}
```
This template is much easier to read. Some *filters* might be unknown to you, but they are just used to format here.
This template is much easier to read. Some *filters* might be unknown to you, but they are just
used to format here.
### Put it all together
Remember to reload or start Evennia, and then go to [http://localhost:4001/help](http://localhost:4001/help/). You should see the list of commands and topics accessible by all characters. Try to login (click the "login" link in the menu of your website) and go to the same page again. You should now see a more detailed list of commands and help entries. Click on one to see its detail.
Remember to reload or start Evennia, and then go to
[http://localhost:4001/help](http://localhost:4001/help/). You should see the list of commands and
topics accessible by all characters. Try to login (click the "login" link in the menu of your
website) and go to the same page again. You should now see a more detailed list of commands and
help entries. Click on one to see its detail.
## To improve this feature
As always, a tutorial is here to help you feel comfortable adding new features and code by yourself. Here are some ideas of things to improve this little feature:
As always, a tutorial is here to help you feel comfortable adding new features and code by yourself.
Here are some ideas of things to improve this little feature:
- Links at the bottom of the detail template to go back to the index might be useful.
- A link in the main menu to link to this page would be great... for the time being you have to enter the URL, users won't guess it's there.
- A link in the main menu to link to this page would be great... for the time being you have to
enter the URL, users won't guess it's there.
- Colors aren't handled at this point, which isn't exactly surprising. You could add it though.
- Linking help entries between one another won't be simple, but it would be great. For instance, if you see a help entry about how to use several commands, it would be great if these commands were themselves links to display their details.
- Linking help entries between one another won't be simple, but it would be great. For instance, if
you see a help entry about how to use several commands, it would be great if these commands were
themselves links to display their details.

View file

@ -1,7 +1,10 @@
# Help System
An important part of Evennia is the online help system. This allows the players and staff alike to learn how to use the game's commands as well as other information pertinent to the game. The help system has many different aspects, from the normal editing of help entries from inside the game, to auto-generated help entries during code development using the *auto-help system*.
An important part of Evennia is the online help system. This allows the players and staff alike to
learn how to use the game's commands as well as other information pertinent to the game. The help
system has many different aspects, from the normal editing of help entries from inside the game, to
auto-generated help entries during code development using the *auto-help system*.
## Viewing the help database
@ -9,14 +12,26 @@ The main command is `help`:
help [searchstring]
This will show a list of help entries, ordered after categories. You will find two sections, *Command help entries* and *Other help entries* (initially you will only have the first one). You can use help to get more info about an entry; you can also give partial matches to get suggestions. If you give category names you will only be shown the topics in that category.
This will show a list of help entries, ordered after categories. You will find two sections,
*Command help entries* and *Other help entries* (initially you will only have the first one). You
can use help to get more info about an entry; you can also give partial matches to get suggestions.
If you give category names you will only be shown the topics in that category.
## Command Auto-help system
A common item that requires help entries are in-game commands. Keeping these entries up-to-date with the actual source code functionality can be a chore. Evennia's commands are therefore auto-documenting straight from the sources through its *auto-help system*. Only commands that you and your character can actually currently use are picked up by the auto-help system. That means an admin will see a considerably larger amount of help topics than a normal player when using the default `help` command.
A common item that requires help entries are in-game commands. Keeping these entries up-to-date with
the actual source code functionality can be a chore. Evennia's commands are therefore auto-
documenting straight from the sources through its *auto-help system*. Only commands that you and
your character can actually currently use are picked up by the auto-help system. That means an admin
will see a considerably larger amount of help topics than a normal player when using the default
`help` command.
The auto-help system uses the `__doc__` strings of your command classes and formats this to a nice-looking help entry. This makes for a very easy way to keep the help updated - just document your commands well and updating the help file is just a `@reload` away. There is no need to manually create and maintain help database entries for commands; as long as you keep the docstrings updated your help will be dynamically updated for you as well.
The auto-help system uses the `__doc__` strings of your command classes and formats this to a nice-
looking help entry. This makes for a very easy way to keep the help updated - just document your
commands well and updating the help file is just a `@reload` away. There is no need to manually
create and maintain help database entries for commands; as long as you keep the docstrings updated
your help will be dynamically updated for you as well.
Example (from a module with command definitions):
@ -43,24 +58,40 @@ Example (from a module with command definitions):
# [...]
```
The text at the very top of the command class definition is the class' `__doc__`-string and will be shown to users looking for help. Try to use a consistent format - all default commands are using the structure shown above.
The text at the very top of the command class definition is the class' `__doc__`-string and will be
shown to users looking for help. Try to use a consistent format - all default commands are using the
structure shown above.
You should also supply the `help_category` class property if you can; this helps to group help entries together for people to more easily find them. See the `help` command in-game to see the default categories. If you don't specify the category, "General" is assumed.
You should also supply the `help_category` class property if you can; this helps to group help
entries together for people to more easily find them. See the `help` command in-game to see the
default categories. If you don't specify the category, "General" is assumed.
If you don't want your command to be picked up by the auto-help system at all (like if you want to write its docs manually using the info in the next section or you use a [cmdset](Command-Sets) that has its own help functionality) you can explicitly set `auto_help` class property to `False` in your command definition.
If you don't want your command to be picked up by the auto-help system at all (like if you want to
write its docs manually using the info in the next section or you use a [cmdset](Command-Sets) that
has its own help functionality) you can explicitly set `auto_help` class property to `False` in your
command definition.
Alternatively, you can keep the advantages of *auto-help* in commands, but control the display of command helps. You can do so by overriding the command's `get_help()` method. By default, this method will return the class docstring. You could modify it to add custom behavior: the text returned by this method will be displayed to the character asking for help in this command.
Alternatively, you can keep the advantages of *auto-help* in commands, but control the display of
command helps. You can do so by overriding the command's `get_help()` method. By default, this
method will return the class docstring. You could modify it to add custom behavior: the text
returned by this method will be displayed to the character asking for help in this command.
## Database help entries
These are all help entries not involving commands (this is handled automatically by the [Command Auto-help system](Help-System#command-auto-help-system)). Non-automatic help entries describe how your particular game is played - its rules, world descriptions and so on.
These are all help entries not involving commands (this is handled automatically by the [Command
Auto-help system](Help-System#command-auto-help-system)). Non-automatic help entries describe how
your particular game is played - its rules, world descriptions and so on.
A help entry consists of four parts:
- The *topic*. This is the name of the help entry. This is what players search for when they are looking for help. The topic can contain spaces and also partial matches will be found.
- The *help category*. Examples are *Administration*, *Building*, *Comms* or *General*. This is an overall grouping of similar help topics, used by the engine to give a better overview.
- The *topic*. This is the name of the help entry. This is what players search for when they are
looking for help. The topic can contain spaces and also partial matches will be found.
- The *help category*. Examples are *Administration*, *Building*, *Comms* or *General*. This is an
overall grouping of similar help topics, used by the engine to give a better overview.
- The *text* - the help text itself, of any length.
- locks - a [lock definition](Locks). This can be used to limit access to this help entry, maybe because it's staff-only or otherwise meant to be restricted. Help commands check for `access_type`s `view` and `edit`. An example of a lock string would be `view:perm(Builders)`.
- locks - a [lock definition](Locks). This can be used to limit access to this help entry, maybe
because it's staff-only or otherwise meant to be restricted. Help commands check for `access_type`s
`view` and `edit`. An example of a lock string would be `view:perm(Builders)`.
You can create new help entries in code by using `evennia.create_help_entry()`.
@ -71,16 +102,21 @@ entry = create_help_entry("emote",
category="Roleplaying", locks="view:all()")
```
From inside the game those with the right permissions can use the `@sethelp` command to add and modify help entries.
From inside the game those with the right permissions can use the `@sethelp` command to add and
modify help entries.
> @sethelp/add emote = The emote command is ...
Using `@sethelp` you can add, delete and append text to existing entries. By default new entries will go in the *General* help category. You can change this using a different form of the `@sethelp` command:
Using `@sethelp` you can add, delete and append text to existing entries. By default new entries
will go in the *General* help category. You can change this using a different form of the `@sethelp`
command:
> @sethelp/add emote, Roleplaying = Emoting is important because ...
If the category *Roleplaying* did not already exist, it is created and will appear in the help index.
If the category *Roleplaying* did not already exist, it is created and will appear in the help
index.
You can, finally, define a lock for the help entry by following the category with a [lock definition](Locks):
You can, finally, define a lock for the help entry by following the category with a [lock
definition](Locks):
> @sethelp/add emote, Roleplaying, view:all() = Emoting is ...
> @sethelp/add emote, Roleplaying, view:all() = Emoting is ...

View file

@ -3,39 +3,73 @@
### How to *get* Help
If you cannot find what you are looking for in the [online documentation]([online documentation](index)), here's what to do:
If you cannot find what you are looking for in the [online documentation]([online
documentation](index)), here's what to do:
- If you think the documentation is not clear enough and are short on time, fill in our quick little [online form][form] and let us know - no login required. Maybe the docs need to be improved or a new tutorial added! Note that while this form is useful as a suggestion box we cannot answer questions or reply to you. Use the discussion group or chat (linked below) if you want feedback.
- If you have trouble with a missing feature or a problem you think is a bug, go to the [issue tracker][issues] and search to see if has been reported/suggested already. If you can't find an existing entry create a new one.
- If you need help, want to start a discussion or get some input on something you are working on, make a post to the [discussions group][group] This is technically a 'mailing list', but you don't need to use e-mail; you can post and read all messages just as easily from your browser via the online interface.
- If you want more direct discussions with developers and other users, consider dropping into our IRC chat channel [#evennia][chat] on the *Freenode* network. Please note however that you have to be patient if you don't get any response immediately; we are all in very different time zones and many have busy personal lives. So you might have to hang around for a while - you'll get noticed eventually!
- If you think the documentation is not clear enough and are short on time, fill in our quick little
[online form][form] and let us know - no login required. Maybe the docs need to be improved or a new
tutorial added! Note that while this form is useful as a suggestion box we cannot answer questions
or reply to you. Use the discussion group or chat (linked below) if you want feedback.
- If you have trouble with a missing feature or a problem you think is a bug, go to the [issue
tracker][issues] and search to see if has been reported/suggested already. If you can't find an
existing entry create a new one.
- If you need help, want to start a discussion or get some input on something you are working on,
make a post to the [discussions group][group] This is technically a 'mailing list', but you don't
need to use e-mail; you can post and read all messages just as easily from your browser via the
online interface.
- If you want more direct discussions with developers and other users, consider dropping into our
IRC chat channel [#evennia][chat] on the *Freenode* network. Please note however that you have to be
patient if you don't get any response immediately; we are all in very different time zones and many
have busy personal lives. So you might have to hang around for a while - you'll get noticed
eventually!
### How to *give* Help
Evennia is a completely non-funded project. It relies on the time donated by its users and developers in order to progress.
Evennia is a completely non-funded project. It relies on the time donated by its users and
developers in order to progress.
The first and easiest way you as a user can help us out is by taking part in [community discussions][group] and by giving feedback on what is good or bad. Report bugs you find and features you lack to our [issue tracker][issues]. Just the simple act of letting developers know you are out there using their program is worth a lot. Generally mentioning and reviewing Evennia elsewhere is also a nice way to spread the word.
The first and easiest way you as a user can help us out is by taking part in [community
discussions][group] and by giving feedback on what is good or bad. Report bugs you find and features
you lack to our [issue tracker][issues]. Just the simple act of letting developers know you are out
there using their program is worth a lot. Generally mentioning and reviewing Evennia elsewhere is
also a nice way to spread the word.
If you'd like to help develop Evennia more hands-on, here are some ways to get going:
- Look through our [online documentation wiki]([online documentation wiki](index)) and see if you can help improve or expand the documentation (even small things like fixing typos!). You don't need any particular permissions to edit the wiki.
- Send a message to our [discussion group][group] and/or our [IRC chat][chat] asking about what needs doing, along with what your interests and skills are.
- Take a look at our [issue tracker][issues] and see if there's something you feel like taking on. [here are bugs][issues-master] that need fixes. At any given time there may also be some [bounties][issues-bounties] open - these are issues members of the community has put up money to see fixed (if you want to put up a bounty yourself you can do so via our page on [bountysource][bountysource]).
- Check out the [Contributing](Contributing) page on how to practically contribute with code using github.
- Look through our [online documentation wiki]([online documentation wiki](index)) and see if you
can help improve or expand the documentation (even small things like fixing typos!). You don't need
any particular permissions to edit the wiki.
- Send a message to our [discussion group][group] and/or our [IRC chat][chat] asking about what
needs doing, along with what your interests and skills are.
- Take a look at our [issue tracker][issues] and see if there's something you feel like taking on.
[here are bugs][issues-master] that need fixes. At any given time there may also be some
[bounties][issues-bounties] open - these are issues members of the community has put up money to see
fixed (if you want to put up a bounty yourself you can do so via our page on
[bountysource][bountysource]).
- Check out the [Contributing](Contributing) page on how to practically contribute with code using
github.
... And finally, if you want to help motivate and support development you can also drop some coins in the developer's cup. You can [make a donation via PayPal][paypal] or, even better, [become an Evennia patron on Patreon][patreon]! This is a great way to tip your hat and show that you appreciate the work done with the server! Finally, if you want to encourage the community to resolve a particular
... And finally, if you want to help motivate and support development you can also drop some coins
in the developer's cup. You can [make a donation via PayPal][paypal] or, even better, [become an
Evennia patron on Patreon][patreon]! This is a great way to tip your hat and show that you
appreciate the work done with the server! Finally, if you want to encourage the community to resolve
a particular
[form]: https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0
[form]:
https://docs.google.com/spreadsheet/viewform?hl=en_US&formkey=dGN0VlJXMWpCT3VHaHpscDEzY1RoZGc6MQ#gid=0
[group]: http://groups.google.com/group/evennia/
[issues]: https://github.com/evennia/evennia/issues
[issues-master]: https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Abug%20label%3Amaster-branch
[issues-master]:
https://github.com/evennia/evennia/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Abug%20label%3Amaster-
branch
[chat]: http://webchat.freenode.net/?channels=evennia
[paypal]: https://www.paypal.com/se/cgi-bin/webscr?cmd=_flow&SESSION=Z-VlOvfGjYq2qvCDOUGpb6C8Due7skT0qOklQEy5EbaD1f0eyEQaYlmCc8O&dispatch=5885d80a13c0db1f8e263663d3faee8d64ad11bbf4d2a5a1a0d303a50933f9b2
[donate-img]: http://images-focus-opensocial.googleusercontent.com/gadgets/proxy?url=https://www.paypalobjects.com/en%255fUS/SE/i/btn/btn%255fdonateCC%255fLG.gif&container=focus&gadget=a&rewriteMime=image/*
[paypal]: https://www.paypal.com/se/cgi-
bin/webscr?cmd=_flow&SESSION=Z-VlOvfGjYq2qvCDOUGpb6C8Due7skT0qOklQEy5EbaD1f0eyEQaYlmCc8O&dispatch=5885d80a13c0db1f8e263663d3faee8d64ad11bbf4d2a5a1a0d303a50933f9b2
[donate-img]: http://images-focus-
opensocial.googleusercontent.com/gadgets/proxy?url=https://www.paypalobjects.com/en%255fUS/SE/i/btn/btn%255fdonateCC%255fLG.gif&container=focus&gadget=a&rewriteMime=image/*
[patreon]: https://www.patreon.com/griatch
[patreon-img]: http://www.evennia.com/_/rsrc/1424724909023/home/evennia_patreon_100x100.png
[issues-bounties]: https://github.com/evennia/evennia/labels/bounty
[bountysource]: https://www.bountysource.com/teams/evennia

View file

@ -1,15 +1,22 @@
# How to connect Evennia to Twitter
[Twitter](http://en.wikipedia.org/wiki/twitter) is an online social networking service that enables users to send and read short 280-character messages called "tweets". Following is a short tutorial explaining how to enable users to send tweets from inside Evennia.
[Twitter](http://en.wikipedia.org/wiki/twitter) is an online social networking service that enables
users to send and read short 280-character messages called "tweets". Following is a short tutorial
explaining how to enable users to send tweets from inside Evennia.
## Configuring Twitter
You must first have a Twitter account. Log in and register an App at the [Twitter Dev Site](https://apps.twitter.com/). Make sure you enable access to "write" tweets!
You must first have a Twitter account. Log in and register an App at the [Twitter Dev
Site](https://apps.twitter.com/). Make sure you enable access to "write" tweets!
To tweet from Evennia you will need both the "API Token" and the "API secret" strings as well as the "Access Token" and "Access Secret" strings.
To tweet from Evennia you will need both the "API Token" and the "API secret" strings as well as the
"Access Token" and "Access Secret" strings.
Twitter changed their requirements to require a Mobile number on the Twitter account to register new apps with write access. If you're unable to do this, please see [this Dev post](https://dev.twitter.com/notifications/new-apps-registration) which describes how to get around it.
Twitter changed their requirements to require a Mobile number on the Twitter account to register new
apps with write access. If you're unable to do this, please see [this Dev
post](https://dev.twitter.com/notifications/new-apps-registration) which describes how to get around
it.
## Install the twitter python module
@ -21,9 +28,13 @@ pip install python-twitter
## A basic tweet command
Evennia doesn't have a `tweet` command out of the box so you need to write your own little [Command](Commands) in order to tweet. If you are unsure about how commands work and how to add them, it can be an idea to go through the [Adding a Command Tutorial](Adding-Command-Tutorial) before continuing.
Evennia doesn't have a `tweet` command out of the box so you need to write your own little
[Command](Commands) in order to tweet. If you are unsure about how commands work and how to add
them, it can be an idea to go through the [Adding a Command Tutorial](Adding-Command-Tutorial)
before continuing.
You can create the command in a separate command module (something like `mygame/commands/tweet.py`) or together with your other custom commands, as you prefer.
You can create the command in a separate command module (something like `mygame/commands/tweet.py`)
or together with your other custom commands, as you prefer.
This is how it can look:
@ -76,9 +87,14 @@ class CmdTweet(Command):
Be sure to substitute your own actual API/Access keys and secrets in the appropriate places.
We default to limiting tweet access to players with `Developers`-level access *or* to those players that have the permission "tweet" (allow individual characters to tweet with `@perm/player playername = tweet`). You may change the [lock](Locks) as you feel is appropriate. Change the overall permission to `Players` if you want everyone to be able to tweet.
We default to limiting tweet access to players with `Developers`-level access *or* to those players
that have the permission "tweet" (allow individual characters to tweet with `@perm/player playername
= tweet`). You may change the [lock](Locks) as you feel is appropriate. Change the overall
permission to `Players` if you want everyone to be able to tweet.
Now add this command to your default command set (e.g in `mygame/commands/defalt_cmdsets.py`") and reload the server. From now on those with access can simply use `tweet <message>` to see the tweet posted from the game's Twitter account.
Now add this command to your default command set (e.g in `mygame/commands/defalt_cmdsets.py`") and
reload the server. From now on those with access can simply use `tweet <message>` to see the tweet
posted from the game's Twitter account.
## Next Steps
@ -89,4 +105,6 @@ This shows only a basic tweet setup, other things to do could be:
* Changing locks to make tweeting open to more people
* Echo your tweets to an in-game channel
Rather than using an explicit command you can set up a Script to send automatic tweets, for example to post updated game stats. See the [Tweeting Game Stats tutorial](Tutorial-Tweeting-Game-Stats) for help.
Rather than using an explicit command you can set up a Script to send automatic tweets, for example
to post updated game stats. See the [Tweeting Game Stats tutorial](Tutorial-Tweeting-Game-Stats) for
help.

View file

@ -1,9 +1,18 @@
# IRC
_Disambiguation: This page is related to using IRC inside an Evennia game. To join the official Evennia IRC chat, connect to irc.freenode.net and join #evennia. Alternatively, you can [join our Discord](https://discord.gg/NecFePw), which is mirrored to IRC._
_Disambiguation: This page is related to using IRC inside an Evennia game. To join the official
Evennia IRC chat, connect to irc.freenode.net and join #evennia. Alternatively, you can [join our
Discord](https://discord.gg/NecFePw), which is mirrored to IRC._
[IRC (Internet Relay Chat)](http://en.wikipedia.org/wiki/Internet_Relay_Chat) is a long standing chat protocol used by many open-source projects for communicating in real time. By connecting one of Evennia's [Channels](Communications) to an IRC channel you can communicate also with people not on an mud themselves. You can also use IRC if you are only running your Evennia MUD locally on your computer (your game doesn't need to be open to the public)! All you need is an internet connection. For IRC operation you also need [twisted.words](http://twistedmatrix.com/trac/wiki/TwistedWords). This is available simply as a package *python-twisted-words* in many Linux distros, or directly downloadable from the link.
[IRC (Internet Relay Chat)](http://en.wikipedia.org/wiki/Internet_Relay_Chat) is a long standing
chat protocol used by many open-source projects for communicating in real time. By connecting one of
Evennia's [Channels](Communications) to an IRC channel you can communicate also with people not on
an mud themselves. You can also use IRC if you are only running your Evennia MUD locally on your
computer (your game doesn't need to be open to the public)! All you need is an internet connection.
For IRC operation you also need [twisted.words](http://twistedmatrix.com/trac/wiki/TwistedWords).
This is available simply as a package *python-twisted-words* in many Linux distros, or directly
downloadable from the link.
## Configuring IRC
@ -13,49 +22,69 @@ To configure IRC, you'll need to activate it in your settings file.
IRC_ENABLED = True
```
Start Evennia and log in as a privileged user. You should now have a new command available: `@irc2chan`. This command is called like this:
Start Evennia and log in as a privileged user. You should now have a new command available:
`@irc2chan`. This command is called like this:
@irc2chan[/switches] <evennia_channel> = <ircnetwork> <port> <#irchannel> <botname>
If you already know how IRC works, this should be pretty self-evident to use. Read the help entry for more features.
If you already know how IRC works, this should be pretty self-evident to use. Read the help entry
for more features.
## Setting up IRC, step by step
You can connect IRC to any Evennia channel (so you could connect it to the default *public* channel if you like), but for testing, let's set up a new channel `irc`.
You can connect IRC to any Evennia channel (so you could connect it to the default *public* channel
if you like), but for testing, let's set up a new channel `irc`.
@ccreate irc = This is connected to an irc channel!
You will automatically join the new channel.
Next we will create a connection to an external IRC network and channel. There are many, many IRC nets. [Here is a list](http://www.irchelp.org/irchelp/networks/popular.html) of some of the biggest ones, the one you choose is not really very important unless you want to connect to a particular channel (also make sure that the network allows for "bots" to connect).
Next we will create a connection to an external IRC network and channel. There are many, many IRC
nets. [Here is a list](http://www.irchelp.org/irchelp/networks/popular.html) of some of the biggest
ones, the one you choose is not really very important unless you want to connect to a particular
channel (also make sure that the network allows for "bots" to connect).
For testing, we choose the *Freenode* network, `irc.freenode.net`. We will connect to a test channel, let's call it *#myevennia-test* (an IRC channel always begins with `#`). It's best if you pick an obscure channel name that didn't exist previously - if it didn't exist it will be created for you.
For testing, we choose the *Freenode* network, `irc.freenode.net`. We will connect to a test
channel, let's call it *#myevennia-test* (an IRC channel always begins with `#`). It's best if you
pick an obscure channel name that didn't exist previously - if it didn't exist it will be created
for you.
> *Don't* connect to `#evennia` for testing and debugging, that is Evennia's official chat channel! You *are* welcome to connect your game to `#evennia` once you have everything working though - it can be a good way to get help and ideas. But if you do, please do so with an in-game channel open only to your game admins and developers).
> *Don't* connect to `#evennia` for testing and debugging, that is Evennia's official chat channel!
You *are* welcome to connect your game to `#evennia` once you have everything working though - it
can be a good way to get help and ideas. But if you do, please do so with an in-game channel open
only to your game admins and developers).
The *port* needed depends on the network. For Freenode this is `6667`.
What will happen is that your Evennia server will connect to this IRC channel as a normal user. This "user" (or "bot") needs a name, which you must also supply. Let's call it "mud-bot".
What will happen is that your Evennia server will connect to this IRC channel as a normal user. This
"user" (or "bot") needs a name, which you must also supply. Let's call it "mud-bot".
To test that the bot connects correctly you also want to log onto this channel with a separate, third-party IRC client. There are hundreds of such clients available. If you use Firefox, the *Chatzilla* plugin is good and easy. Freenode also offers its own web-based chat page. Once you have connected to a network, the command to join is usually `/join #channelname` (don't forget the #).
To test that the bot connects correctly you also want to log onto this channel with a separate,
third-party IRC client. There are hundreds of such clients available. If you use Firefox, the
*Chatzilla* plugin is good and easy. Freenode also offers its own web-based chat page. Once you
have connected to a network, the command to join is usually `/join #channelname` (don't forget the
#).
Next we connect Evennia with the IRC channel.
@irc2chan irc = irc.freenode.net 6667 #myevennia-test mud-bot
Evennia will now create a new IRC bot `mud-bot` and connect it to the IRC network and the channel #myevennia. If you are connected to the IRC channel you will soon see the user *mud-bot* connect.
Evennia will now create a new IRC bot `mud-bot` and connect it to the IRC network and the channel
#myevennia. If you are connected to the IRC channel you will soon see the user *mud-bot* connect.
Write something in the Evennia channel *irc*.
irc Hello, World!
[irc] Anna: Hello, World!
If you are viewing your IRC channel with a separate IRC client you should see your text appearing there, spoken by the bot:
If you are viewing your IRC channel with a separate IRC client you should see your text appearing
there, spoken by the bot:
mud-bot> [irc] Anna: Hello, World!
Write `Hello!` in your IRC client window and it will appear in your normal channel, marked with the name of the IRC channel you used (#evennia here).
Write `Hello!` in your IRC client window and it will appear in your normal channel, marked with the
name of the IRC channel you used (#evennia here).
[irc] Anna@#myevennia-test: Hello!
Your Evennia gamers can now chat with users on external IRC channels!
Your Evennia gamers can now chat with users on external IRC channels!

View file

@ -36,10 +36,25 @@ We strongly recommend that you code your rule system as stand-alone as possible.
spread your skill check code, race bonus calculation, die modifiers or what have you all over your
game.
- Put everything you would need to look up in a rule book into a module in `mygame/world`. Hide away as much as you can. Think of it as a black box (or maybe the code representation of an all-knowing game master). The rest of your game will ask this black box questions and get answers back. Exactly how it arrives at those results should not need to be known outside the box. Doing it this way makes it easier to change and update things in one place later.
- Store only the minimum stuff you need with each game object. That is, if your Characters need values for Health, a list of skills etc, store those things on the Character - don't store how to roll or change them.
- Next is to determine just how you want to store things on your Objects and Characters. You can choose to either store things as individual [Attributes](Attributes), like `character.db.STR=34` and `character.db.Hunting_skill=20`. But you could also use some custom storage method, like a dictionary `character.db.skills = {"Hunting":34, "Fishing":20, ...}`. A much more fancy solution is to look at the Ainneve [Trait handler](https://github.com/evennia/ainneve/blob/master/world/traits.py). Finally you could even go with a [custom django model](New-Models). Which is the better depends on your game and the complexity of your system.
- Make a clear [API](http://en.wikipedia.org/wiki/Application_programming_interface) into your rules. That is, make methods/functions that you feed with, say, your Character and which skill you want to check. That is, you want something similar to this:
- Put everything you would need to look up in a rule book into a module in `mygame/world`. Hide away
as much as you can. Think of it as a black box (or maybe the code representation of an all-knowing
game master). The rest of your game will ask this black box questions and get answers back. Exactly
how it arrives at those results should not need to be known outside the box. Doing it this way
makes it easier to change and update things in one place later.
- Store only the minimum stuff you need with each game object. That is, if your Characters need
values for Health, a list of skills etc, store those things on the Character - don't store how to
roll or change them.
- Next is to determine just how you want to store things on your Objects and Characters. You can
choose to either store things as individual [Attributes](Attributes), like `character.db.STR=34` and
`character.db.Hunting_skill=20`. But you could also use some custom storage method, like a
dictionary `character.db.skills = {"Hunting":34, "Fishing":20, ...}`. A much more fancy solution is
to look at the Ainneve [Trait
handler](https://github.com/evennia/ainneve/blob/master/world/traits.py). Finally you could even go
with a [custom django model](New-Models). Which is the better depends on your game and the
complexity of your system.
- Make a clear [API](http://en.wikipedia.org/wiki/Application_programming_interface) into your
rules. That is, make methods/functions that you feed with, say, your Character and which skill you
want to check. That is, you want something similar to this:
```python
from world import rules
@ -47,19 +62,33 @@ game.
result = rules.roll_challenge(character1, character2, "swords")
```
You might need to make these functions more or less complex depending on your game. For example the properties of the room might matter to the outcome of a roll (if the room is dark, burning etc). Establishing just what you need to send into your game mechanic module is a great way to also get a feel for what you need to add to your engine.
You might need to make these functions more or less complex depending on your game. For example the
properties of the room might matter to the outcome of a roll (if the room is dark, burning etc).
Establishing just what you need to send into your game mechanic module is a great way to also get a
feel for what you need to add to your engine.
## Coded systems
Inspired by tabletop role playing games, most game systems mimic some sort of die mechanic. To this end Evennia offers a full [dice roller](https://github.com/evennia/evennia/blob/master/evennia/contrib/dice.py) in its `contrib` folder. For custom implementations, Python offers many ways to randomize a result using its in-built `random` module. No matter how it's implemented, we will in this text refer to the action of determining an outcome as a "roll".
Inspired by tabletop role playing games, most game systems mimic some sort of die mechanic. To this
end Evennia offers a full [dice
roller](https://github.com/evennia/evennia/blob/master/evennia/contrib/dice.py) in its `contrib`
folder. For custom implementations, Python offers many ways to randomize a result using its in-built
`random` module. No matter how it's implemented, we will in this text refer to the action of
determining an outcome as a "roll".
In a freeform system, the result of the roll is just compared with values and people (or the game master) just agree on what it means. In a coded system the result now needs to be processed somehow. There are many things that may happen as a result of rule enforcement:
In a freeform system, the result of the roll is just compared with values and people (or the game
master) just agree on what it means. In a coded system the result now needs to be processed somehow.
There are many things that may happen as a result of rule enforcement:
- Health may be added or deducted. This can effect the character in various ways.
- Experience may need to be added, and if a level-based system is used, the player might need to be informed they have increased a level.
- Experience may need to be added, and if a level-based system is used, the player might need to be
informed they have increased a level.
- Room-wide effects need to be reported to the room, possibly affecting everyone in the room.
There are also a slew of other things that fall under "Coded systems", including things like weather, NPC artificial intelligence and game economy. Basically everything about the world that a Game master would control in a tabletop role playing game can be mimicked to some level by coded systems.
There are also a slew of other things that fall under "Coded systems", including things like
weather, NPC artificial intelligence and game economy. Basically everything about the world that a
Game master would control in a tabletop role playing game can be mimicked to some level by coded
systems.
## Example of Rule module
@ -67,17 +96,24 @@ There are also a slew of other things that fall under "Coded systems", including
Here is a simple example of a rule module. This is what we assume about our simple example game:
- Characters have only four numerical values:
- Their `level`, which starts at 1.
- A skill `combat`, which determines how good they are at hitting things. Starts between 5 and 10.
- A skill `combat`, which determines how good they are at hitting things. Starts between 5 and
10.
- Their Strength, `STR`, which determine how much damage they do. Starts between 1 and 10.
- Their Health points, `HP`, which starts at 100.
- When a Character reaches `HP = 0`, they are presumed "defeated". Their HP is reset and they get a failure message (as a stand-in for death code).
- When a Character reaches `HP = 0`, they are presumed "defeated". Their HP is reset and they get a
failure message (as a stand-in for death code).
- Abilities are stored as simple Attributes on the Character.
- "Rolls" are done by rolling a 100-sided die. If the result is below the `combat` value, it's a success and damage is rolled. Damage is rolled as a six-sided die + the value of `STR` (for this example we ignore weapons and assume `STR` is all that matters).
- Every successful `attack` roll gives 1-3 experience points (`XP`). Every time the number of `XP` reaches `(level + 1) ** 2`, the Character levels up. When leveling up, the Character's `combat` value goes up by 2 points and `STR` by one (this is a stand-in for a real progression system).
- "Rolls" are done by rolling a 100-sided die. If the result is below the `combat` value, it's a
success and damage is rolled. Damage is rolled as a six-sided die + the value of `STR` (for this
example we ignore weapons and assume `STR` is all that matters).
- Every successful `attack` roll gives 1-3 experience points (`XP`). Every time the number of `XP`
reaches `(level + 1) ** 2`, the Character levels up. When leveling up, the Character's `combat`
value goes up by 2 points and `STR` by one (this is a stand-in for a real progression system).
### Character
The Character typeclass is simple. It goes in `mygame/typeclasses/characters.py`. There is already an empty `Character` class there that Evennia will look to and use.
The Character typeclass is simple. It goes in `mygame/typeclasses/characters.py`. There is already
an empty `Character` class there that Evennia will look to and use.
```python
from random import randint
@ -97,7 +133,10 @@ class Character(DefaultCharacter):
self.db.combat = randint(5, 10)
```
`@reload` the server to load up the new code. Doing `examine self` will however *not* show the new Attributes on yourself. This is because the `at_object_creation` hook is only called on *new* Characters. Your Character was already created and will thus not have them. To force a reload, use the following command:
`@reload` the server to load up the new code. Doing `examine self` will however *not* show the new
Attributes on yourself. This is because the `at_object_creation` hook is only called on *new*
Characters. Your Character was already created and will thus not have them. To force a reload, use
the following command:
```
@typeclass/force/reset self
@ -181,9 +220,14 @@ def roll_challenge(character1, character2, skillname):
raise RunTimeError("Skillname %s not found." % skillname)
```
These few functions implement the entirety of our simple rule system. We have a function to check the "defeat" condition and reset the `HP` back to 100 again. We define a generic "skill" function. Multiple skills could all be added with the same signature; our `SKILLS` dictionary makes it easy to look up the skills regardless of what their actual functions are called. Finally, the access function `roll_challenge` just picks the skill and gets the result.
These few functions implement the entirety of our simple rule system. We have a function to check
the "defeat" condition and reset the `HP` back to 100 again. We define a generic "skill" function.
Multiple skills could all be added with the same signature; our `SKILLS` dictionary makes it easy to
look up the skills regardless of what their actual functions are called. Finally, the access
function `roll_challenge` just picks the skill and gets the result.
In this example, the skill function actually does a lot - it not only rolls results, it also informs everyone of their results via `character.msg()` calls.
In this example, the skill function actually does a lot - it not only rolls results, it also informs
everyone of their results via `character.msg()` calls.
Here is an example of usage in a game command:
@ -218,4 +262,4 @@ Note how simple the command becomes and how generic you can make it. It becomes
number of Combat commands by just extending this functionality - you can easily roll challenges and
pick different skills to check. And if you ever decided to, say, change how to determine hit chance,
you don't have to change every command, but need only change the single `roll_hit` function inside
your `rules` module.
your `rules` module.

View file

@ -1,7 +1,10 @@
# Inputfuncs
An *inputfunc* is an Evennia function that handles a particular input (an [inputcommand](OOB)) from the client. The inputfunc is the last destination for the inputcommand along the [ingoing message path](Messagepath#the-ingoing-message-path). The inputcommand always has the form `(commandname, (args), {kwargs})` and Evennia will use this to try to find and call an inputfunc on the form
An *inputfunc* is an Evennia function that handles a particular input (an [inputcommand](OOB)) from
the client. The inputfunc is the last destination for the inputcommand along the [ingoing message
path](Messagepath#the-ingoing-message-path). The inputcommand always has the form `(commandname,
(args), {kwargs})` and Evennia will use this to try to find and call an inputfunc on the form
```python
def commandname(session, *args, **kwargs):
@ -18,31 +21,42 @@ Or, if no match was found, it will call an inputfunc named "default" on this for
## Adding your own inputfuncs
This is simple. Add a function on the above form to `mygame/server/conf/inputfuncs.py`. Your function must be in the global, outermost scope of that module and not start with an underscore (`_`) to be recognized as an inputfunc. Reload the server. That's it. To overload a default inputfunc (see below), just add a function with the same name.
This is simple. Add a function on the above form to `mygame/server/conf/inputfuncs.py`. Your
function must be in the global, outermost scope of that module and not start with an underscore
(`_`) to be recognized as an inputfunc. Reload the server. That's it. To overload a default
inputfunc (see below), just add a function with the same name.
The modules Evennia looks into for inputfuncs are defined in the list `settings.INPUT_FUNC_MODULES`. This list will be imported from left to right and later imported functions will replace earlier ones.
The modules Evennia looks into for inputfuncs are defined in the list `settings.INPUT_FUNC_MODULES`.
This list will be imported from left to right and later imported functions will replace earlier
ones.
## Default inputfuncs
Evennia defines a few default inputfuncs to handle the common cases. These are defined in `evennia/server/inputfuncs.py`.
Evennia defines a few default inputfuncs to handle the common cases. These are defined in
`evennia/server/inputfuncs.py`.
### text
- Input: `("text", (textstring,), {})`
- Output: Depends on Command triggered
This is the most common of inputcommands, and the only one supported by every traditional mud. The argument is usually what the user sent from their command line. Since all text input from the user like this is considered a [Command](Commands), this inputfunc will do things like nick-replacement and then pass on the input to the central Commandhandler.
This is the most common of inputcommands, and the only one supported by every traditional mud. The
argument is usually what the user sent from their command line. Since all text input from the user
like this is considered a [Command](Commands), this inputfunc will do things like nick-replacement
and then pass on the input to the central Commandhandler.
### echo
- Input: `("echo", (args), {})`
- Output: `("text", ("Echo returns: %s" % args), {})`
This is a test input, which just echoes the argument back to the session as text. Can be used for testing custom client input.
This is a test input, which just echoes the argument back to the session as text. Can be used for
testing custom client input.
### default
The default function, as mentioned above, absorbs all non-recognized inputcommands. The default one will just log an error.
The default function, as mentioned above, absorbs all non-recognized inputcommands. The default one
will just log an error.
### client_options
@ -51,9 +65,12 @@ The default function, as mentioned above, absorbs all non-recognized inputcomman
- normal: None
- get: `("client_options", (), {key:value, ...})`
This is a direct command for setting protocol options. These are settable with the `@option` command, but this offers a client-side way to set them. Not all connection protocols makes use of all flags, but here are the possible keywords:
This is a direct command for setting protocol options. These are settable with the `@option`
command, but this offers a client-side way to set them. Not all connection protocols makes use of
all flags, but here are the possible keywords:
- get (bool): If this is true, ignore all other kwargs and immediately return the current settings as an outputcommand `("client_options", (), {key=value, ...})`-
- get (bool): If this is true, ignore all other kwargs and immediately return the current settings
as an outputcommand `("client_options", (), {key=value, ...})`-
- client (str): A client identifier, like "mushclient".
- version (str): A client version
- ansi (bool): Supports ansi colors
@ -68,21 +85,24 @@ This is a direct command for setting protocol options. These are settable with t
- nomarkup (bool): Strip all text tags
- raw (bool): Leave text tags unparsed
> Note that there are two GMCP aliases to this inputfunc - `hello` and `supports_set`, which means it will be accessed via the GMCP `Hello` and `Supports.Set` instructions assumed by some clients.
> Note that there are two GMCP aliases to this inputfunc - `hello` and `supports_set`, which means
it will be accessed via the GMCP `Hello` and `Supports.Set` instructions assumed by some clients.
### get_client_options
- Input: `("get_client_options, (), {key:value, ...})`
- Output: `("client_options, (), {key:value, ...})`
This is a convenience wrapper that retrieves the current options by sending "get" to `client_options` above.
This is a convenience wrapper that retrieves the current options by sending "get" to
`client_options` above.
### get_inputfuncs
- Input: `("get_inputfuncs", (), {})`
- Output: `("get_inputfuncs", (), {funcname:docstring, ...})`
Returns an outputcommand on the form `("get_inputfuncs", (), {funcname:docstring, ...})` - a list of all the available inputfunctions along with their docstrings.
Returns an outputcommand on the form `("get_inputfuncs", (), {funcname:docstring, ...})` - a list of
all the available inputfunctions along with their docstrings.
### login
@ -98,7 +118,9 @@ This performs the inputfunc version of a login operation on the current Session.
Input: `("get_value", (name, ), {})`
Output: `("get_value", (value, ), {})`
Retrieves a value from the Character or Account currently controlled by this Session. Takes one argument, This will only accept particular white-listed names, you'll need to overload the function to expand. By default the following values can be retrieved:
Retrieves a value from the Character or Account currently controlled by this Session. Takes one
argument, This will only accept particular white-listed names, you'll need to overload the function
to expand. By default the following values can be retrieved:
- "name" or "key": The key of the Account or puppeted Character.
- "location": Name of the current location, or "None".
@ -108,9 +130,15 @@ Retrieves a value from the Character or Account currently controlled by this Ses
- Input: `("repeat", (), {"callback":funcname,
"interval": secs, "stop": False})`
- Output: Depends on the repeated function. Will return `("text", (repeatlist),{}` with a list of accepted names if given an unfamiliar callback name.
- Output: Depends on the repeated function. Will return `("text", (repeatlist),{}` with a list of
accepted names if given an unfamiliar callback name.
This will tell evennia to repeatedly call a named function at a given interval. Behind the scenes this will set up a [Ticker](TickerHandler). Only previously acceptable functions are possible to repeat-call in this way, you'll need to overload this inputfunc to add the ones you want to offer. By default only two example functions are allowed, "test1" and "test2", which will just echo a text back at the given interval. Stop the repeat by sending `"stop": True` (note that you must include both the callback name and interval for Evennia to know what to stop).
This will tell evennia to repeatedly call a named function at a given interval. Behind the scenes
this will set up a [Ticker](TickerHandler). Only previously acceptable functions are possible to
repeat-call in this way, you'll need to overload this inputfunc to add the ones you want to offer.
By default only two example functions are allowed, "test1" and "test2", which will just echo a text
back at the given interval. Stop the repeat by sending `"stop": True` (note that you must include
both the callback name and interval for Evennia to know what to stop).
### unrepeat
@ -125,9 +153,14 @@ This is a convenience wrapper for sending "stop" to the `repeat` inputfunc.
- Input: `("monitor", (), ("name":field_or_argname, stop=False)`
- Output (on change): `("monitor", (), {"name":name, "value":value})`
This sets up on-object monitoring of Attributes or database fields. Whenever the field or Attribute changes in any way, the outputcommand will be sent. This is using the [MonitorHandler](MonitorHandler) behind the scenes. Pass the "stop" key to stop monitoring. Note that you must supply the name also when stopping to let the system know which monitor should be cancelled.
This sets up on-object monitoring of Attributes or database fields. Whenever the field or Attribute
changes in any way, the outputcommand will be sent. This is using the
[MonitorHandler](MonitorHandler) behind the scenes. Pass the "stop" key to stop monitoring. Note
that you must supply the name also when stopping to let the system know which monitor should be
cancelled.
Only fields/attributes in a whitelist are allowed to be used, you have to overload this function to add more. By default the following fields/attributes can be monitored:
Only fields/attributes in a whitelist are allowed to be used, you have to overload this function to
add more. By default the following fields/attributes can be monitored:
- "name": The current character name
- "location": The current location

View file

@ -7,12 +7,19 @@ this before starting.
## Install Termux
The first thing to do is install a terminal emulator that allows a "full" version of linux to be run. Note that Android is essentially running on top of linux so if you have a rooted phone, you may be able to skip this step. You *don't* require a rooted phone to install Evennia though.
The first thing to do is install a terminal emulator that allows a "full" version of linux to be
run. Note that Android is essentially running on top of linux so if you have a rooted phone, you may
be able to skip this step. You *don't* require a rooted phone to install Evennia though.
Assuming we do not have root, we will install [Termux](https://play.google.com/store/apps/details?id=com.termux&hl=en).
Termux provides a base installation of Linux essentials, including apt and Python, and makes them available under a writeable directory. It also gives us a terminal where we can enter commands. By default, Android doesn't give you permissions to the root folder, so Termux pretends that its own installation directory is the root directory.
Assuming we do not have root, we will install
[Termux](https://play.google.com/store/apps/details?id=com.termux&hl=en).
Termux provides a base installation of Linux essentials, including apt and Python, and makes them
available under a writeable directory. It also gives us a terminal where we can enter commands. By
default, Android doesn't give you permissions to the root folder, so Termux pretends that its own
installation directory is the root directory.
Termux will set up a base system for us on first launch, but we will need to install some prerequisites for Evennia. Commands you should run in Termux will look like this:
Termux will set up a base system for us on first launch, but we will need to install some
prerequisites for Evennia. Commands you should run in Termux will look like this:
```
$ cat file.txt
@ -21,14 +28,17 @@ The `$` symbol is your prompt - do not include it when running commands.
## Prerequisites
To install some of the libraries Evennia requires, namely Pillow and Twisted, we have to first install some packages they depend on. In Termux, run the following
To install some of the libraries Evennia requires, namely Pillow and Twisted, we have to first
install some packages they depend on. In Termux, run the following
```
$ pkg install -y clang git zlib ndk-sysroot libjpeg-turbo libcrypt python
```
Termux ships with Python 3, perfect. Python 3 has venv (virtualenv) and pip (Python's module installer) built-in.
Termux ships with Python 3, perfect. Python 3 has venv (virtualenv) and pip (Python's module
installer) built-in.
So, let's set up our virtualenv. This keeps the Python packages we install separate from the system versions.
So, let's set up our virtualenv. This keeps the Python packages we install separate from the system
versions.
```
$ cd
@ -36,7 +46,8 @@ $ python3 -m venv evenv
```
This will create a new folder, called `evenv`, containing the new python executable.
Next, let's activate our new virtualenv. Every time you want to work on Evennia, you need to run the following command:
Next, let's activate our new virtualenv. Every time you want to work on Evennia, you need to run the
following command:
```
$ source evenv/bin/activate
@ -67,13 +78,17 @@ Install the latest Evennia in a way that lets you edit the source
(evenv) $ pip install --upgrade -e 'git+https://github.com/evennia/evennia#egg=evennia'
```
This step will possibly take quite a while - we are downloading Evennia and are then installing it, building all of the requirements for Evennia to run. If you run into trouble on this step, please see [Troubleshooting](Installing-on-Android#troubleshooting).
This step will possibly take quite a while - we are downloading Evennia and are then installing it,
building all of the requirements for Evennia to run. If you run into trouble on this step, please
see [Troubleshooting](Installing-on-Android#troubleshooting).
You can go to the dir where Evennia is installed with `cd $VIRTUAL_ENV/src/evennia`. `git grep (something)` can be handy, as can `git diff`
You can go to the dir where Evennia is installed with `cd $VIRTUAL_ENV/src/evennia`. `git grep
(something)` can be handy, as can `git diff`
### Final steps
At this point, Evennia is installed on your phone! You can now continue with the original [Getting Started](Getting-Started) instruction, we repeat them here for clarity.
At this point, Evennia is installed on your phone! You can now continue with the original [Getting
Started](Getting-Started) instruction, we repeat them here for clarity.
To start a new game:
@ -91,11 +106,13 @@ To start the game for the first time:
(evenv) $ evennia start
```
Your game should now be running! Open a web browser at http://localhost:4001 or point a telnet client to localhost:4000 and log in with the user you created.
Your game should now be running! Open a web browser at http://localhost:4001 or point a telnet
client to localhost:4000 and log in with the user you created.
## Running Evennia
When you wish to run Evennia, get into your Termux console and make sure you have activated your virtualenv as well as are in your game's directory. You can then run evennia start as normal.
When you wish to run Evennia, get into your Termux console and make sure you have activated your
virtualenv as well as are in your game's directory. You can then run evennia start as normal.
```
$ cd ~ && source evenv/bin/activate
@ -107,9 +124,11 @@ You may wish to look at the [Linux Instructions](Getting-Started#linux-install)
## Caveats
- Android's os module doesn't support certain functions - in particular getloadavg. Thusly, running the command @server in-game will throw an exception. So far, there is no fix for this problem.
- Android's os module doesn't support certain functions - in particular getloadavg. Thusly, running
the command @server in-game will throw an exception. So far, there is no fix for this problem.
- As you might expect, performance is not amazing.
- Android is fairly aggressive about memory handling, and you may find that your server process is killed if your phone is heavily taxed. Termux seems to keep a notification up to discourage this.
- Android is fairly aggressive about memory handling, and you may find that your server process is
killed if your phone is heavily taxed. Termux seems to keep a notification up to discourage this.
## Troubleshooting

View file

@ -15,31 +15,59 @@ Change language by adding the following to your `mygame/server/conf/settings.py`
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).
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).
> 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).
> **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.
> 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!
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 [Getting Started](Getting-Started) 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:
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 [Getting Started](Getting-Started) 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:
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.
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.
> 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).
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
@ -47,11 +75,14 @@ supporting editors) allow you to mark translations as "fuzzy". This tells the sy
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
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.
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!
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!

View file

@ -2,19 +2,48 @@
# WORK IN PROGRESS - DO NOT USE
Evennia provides a great foundation to build your very own MU* whether you have programming experience or none at all. Whilst Evennia has a number of in-game building commands and tutorials available to get you started, when approaching game systems of any complexity it is advisable to have the basics of Python under your belt before jumping into the code. There are many Python tutorials freely available online however this page focuses on Learn Python the Hard Way (LPTHW) by Zed Shaw. This tutorial takes you through the basics of Python and progresses you to creating your very own online text based game. Whilst completing the course feel free to install Evennia and try out some of our beginner tutorials. On completion you can return to this page, which will act as an overview to the concepts separating your online text based game and the inner-workings of Evennia.
-The latter portion of the tutorial focuses working your engine into a webpage and is not strictly required for development in Evennia.
Evennia provides a great foundation to build your very own MU* whether you have programming
experience or none at all. Whilst Evennia has a number of in-game building commands and tutorials
available to get you started, when approaching game systems of any complexity it is advisable to
have the basics of Python under your belt before jumping into the code. There are many Python
tutorials freely available online however this page focuses on Learn Python the Hard Way (LPTHW) by
Zed Shaw. This tutorial takes you through the basics of Python and progresses you to creating your
very own online text based game. Whilst completing the course feel free to install Evennia and try
out some of our beginner tutorials. On completion you can return to this page, which will act as an
overview to the concepts separating your online text based game and the inner-workings of Evennia.
-The latter portion of the tutorial focuses working your engine into a webpage and is not strictly
required for development in Evennia.
## Exercise 23
You may have returned here when you were invited to read some code. If you havent already, you should now have the knowledge necessary to install Evennia. Head over to the Getting Started page for install instructions. You can also try some of our tutorials to get you started on working with Evennia.
You may have returned here when you were invited to read some code. If you havent already, you
should now have the knowledge necessary to install Evennia. Head over to the Getting Started page
for install instructions. You can also try some of our tutorials to get you started on working with
Evennia.
## Bridging the gap.
If you have successfully completed the Learn Python the Hard Way tutorial you should now have a simple browser based Interactive Fiction engine which looks similar to this.
This engine is built using a single interactive object type, the Room class. The Room class holds a description of itself that is presented to the user and a list of hardcoded commands which if selected correctly will present you with the next rooms description and commands. Whilst your game only has one interactive object, MU* have many more: Swords and shields, potions and scrolls or even laser guns and robots. Even the player has an in-game representation in the form of your character. Each of these examples are represented by their own object with their own description that can be presented to the user.
If you have successfully completed the Learn Python the Hard Way tutorial you should now have a
simple browser based Interactive Fiction engine which looks similar to this.
This engine is built using a single interactive object type, the Room class. The Room class holds a
description of itself that is presented to the user and a list of hardcoded commands which if
selected correctly will present you with the next rooms description and commands. Whilst your game
only has one interactive object, MU* have many more: Swords and shields, potions and scrolls or even
laser guns and robots. Even the player has an in-game representation in the form of your character.
Each of these examples are represented by their own object with their own description that can be
presented to the user.
A basic object in Evennia has a number of default functions but perhaps most important is the idea of location. In your text engine you receive a description of a room but you are not really in the room because you have no in-game representation. However, in Evennia when you enter a Dungeon you ARE in the dungeon. That is to say your character.location = Dungeon whilst the Dungeon.contents now has a spunky young adventurer added to it. In turn, your character.contents may have amongst it a number of swords and potions to help you on your adventure and their location would be you.
A basic object in Evennia has a number of default functions but perhaps most important is the idea
of location. In your text engine you receive a description of a room but you are not really in the
room because you have no in-game representation. However, in Evennia when you enter a Dungeon you
ARE in the dungeon. That is to say your character.location = Dungeon whilst the Dungeon.contents now
has a spunky young adventurer added to it. In turn, your character.contents may have amongst it a
number of swords and potions to help you on your adventure and their location would be you.
In reality each of these “objects” are just an entry in your Evennia projects database which keeps track of all these attributes, such as location and contents. Making changes to those attributes and the rules in which they are changed is the most fundamental perspective of how your game works. We define those rules in the objects Typeclass. The Typeclass is a Python class with a special connection to the games database which changes values for us through various class methods. Lets look at your characters Typeclass rules for changing location.
In reality each of these “objects” are just an entry in your Evennia projects database which keeps
track of all these attributes, such as location and contents. Making changes to those attributes and
the rules in which they are changed is the most fundamental perspective of how your game works. We
define those rules in the objects Typeclass. The Typeclass is a Python class with a special
connection to the games database which changes values for us through various class methods. Lets
look at your characters Typeclass rules for changing location.
1. `self.at_before_move(destination)` (if this returns False, move is aborted)
2. `self.announce_move_from(destination)`
@ -22,6 +51,20 @@ In reality each of these “objects” are just an entry in your Evennia project
4. `self.announce_move_to(source_location)`
5. `self.at_after_move(source_location)`
First we check if its okay to leave our current location, then we tell everyone there that were leaving. We move locations and tell everyone at our new location that weve arrived before checking were okay to be there. By default stages 1 and 5 are empty ready for us to add some rules. Well leave an explanation as to how to make those changes for the tutorial section, but imagine if you were an astronaut. A smart astronaut might stop at step 1 to remember to put his helmet on whilst a slower astronaut might realise hes forgotten in step 5 before shortly after ceasing to be an astronaut.
First we check if its okay to leave our current location, then we tell everyone there that were
leaving. We move locations and tell everyone at our new location that weve arrived before checking
were okay to be there. By default stages 1 and 5 are empty ready for us to add some rules. Well
leave an explanation as to how to make those changes for the tutorial section, but imagine if you
were an astronaut. A smart astronaut might stop at step 1 to remember to put his helmet on whilst a
slower astronaut might realise hes forgotten in step 5 before shortly after ceasing to be an
astronaut.
With all these objects and all this moving around it raises another problem. In your text engine the commands available to the player were hard-coded to the room. That means if we have commands we always want available to the player well need to have those commands hard-coded on every single room. What about an armoury? When all the swords are gone the command to take a sword would still remain causing confusion. Evennia solves this problem by giving each object the ability to hold commands. Rooms can have commands attached to them specific to that location, like climbing a tree; Players can have commands which are always available to them, like look, get and say; and objects can have commands attached to them which unlock when taking possession of it, like attack commands when obtaining a weapon.
With all these objects and all this moving around it raises another problem. In your text engine the
commands available to the player were hard-coded to the room. That means if we have commands we
always want available to the player well need to have those commands hard-coded on every single
room. What about an armoury? When all the swords are gone the command to take a sword would still
remain causing confusion. Evennia solves this problem by giving each object the ability to hold
commands. Rooms can have commands attached to them specific to that location, like climbing a tree;
Players can have commands which are always available to them, like look, get and say; and
objects can have commands attached to them which unlock when taking possession of it, like attack
commands when obtaining a weapon.

View file

@ -29,4 +29,4 @@ if you do!
**Q: What about Contributions?**
The contributions in `evennia/evennia/contrib` are considered to be released under the same license
as Evennia itself, unless the individual contributor has specifically defined otherwise.
as Evennia itself, unless the individual contributor has specifically defined otherwise.

View file

@ -6,10 +6,14 @@
- [evennia.com](http://www.evennia.com) - Main Evennia portal page. Links to all corners of Evennia.
- [Evennia github page](https://github.com/evennia/evennia) - Download code and read documentation.
- [Evennia official chat channel](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb) - Our official IRC chat #evennia at irc.freenode.net:6667.
- [Evennia forums/mailing list](http://groups.google.com/group/evennia) - Web interface to our google group.
- [Evennia official chat
channel](http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb)
- Our official IRC chat #evennia at irc.freenode.net:6667.
- [Evennia forums/mailing list](http://groups.google.com/group/evennia) - Web interface to our
google group.
- [Evennia development blog](http://evennia.blogspot.se/) - Musings from the lead developer.
- [Evennia's manual on ReadTheDocs](http://readthedocs.org/projects/evennia/) - Read and download offline in html, PDF or epub formats.
- [Evennia's manual on ReadTheDocs](http://readthedocs.org/projects/evennia/) - Read and download
offline in html, PDF or epub formats.
- [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games.
----
- [Evennia on Open Hub](https://www.openhub.net/p/6906)
@ -19,66 +23,130 @@
### Third-party Evennia utilities and resources
*For publicly available games running on Evennia, add and find those in the [Evennia game index](http://games.evennia.com) instead!*
*For publicly available games running on Evennia, add and find those in the [Evennia game
index](http://games.evennia.com) instead!*
- [Discord Evennia channel](https://discord.gg/NecFePw) - This is a fan-driven Discord channel with a bridge to the official Evennia IRC channel.
- [Discord Evennia channel](https://discord.gg/NecFePw) - This is a fan-driven Discord channel with
a bridge to the official Evennia IRC channel.
---
- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the _Blackbirds_ Evennia game project.
- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin) - an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine.
- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON 27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and challenges [used during the conference](https://dcdark.net/home#).
- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular [Arx](http://play.arxmush.org/) Evennia game. [Here are instructions for installing](Arxcode-installing-help)
- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your website.
- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional coloration for Evennia unit-test output.
- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for telnet/web).
- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for Evennia with things like races, combat etc. [Summary here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/).
- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source turn-based battle system for Evennia. It also has a [live demo](http://wcb.battlestudio.com/).
- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people to make [RPI](http://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games.
- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an older fork of Evennia. It has some specific design goals for building and extending the game based on input files.
- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`) files in the [vim](http://www.vim.org/) text editor (Emacs users can use [evennia-mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)).
- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the
_Blackbirds_ Evennia game project.
- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin)
- an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine.
- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON
27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an
Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and
challenges [used during the conference](https://dcdark.net/home#).
- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular
[Arx](http://play.arxmush.org/) Evennia game. [Here are instructions for installing](Arxcode-
installing-help)
- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your
website.
- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional
coloration for Evennia unit-test output.
- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for
telnet/web).
- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for
Evennia with things like races, combat etc. [Summary
here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/).
- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source
turn-based battle system for Evennia. It also has a [live demo](http://wcb.battlestudio.com/).
- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people
to make [RPI](http://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games.
- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an
older fork of Evennia. It has some specific design goals for building and extending the game based
on input files.
- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`)
files in the [vim](http://www.vim.org/) text editor (Emacs users can use [evennia-
mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)).
- [Other Evennia-related repos on github](https://github.com/search?p=1&q=evennia)
----
- [EvCast video series](https://www.youtube.com/playlist?list=PLyYMNttpc-SX1hvaqlUNmcxrhmM64pQXl) - Tutorial videos explaining installing Evennia, basic Python etc.
- [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker container](https://www.docker.com/) for quick install and deployment in just a few commands.
- [Evennia's docs in Chinese](http://www.evenniacn.com/) - A translated mirror of a slightly older Evennia version. Announcement [here](https://groups.google.com/forum/#!topic/evennia/3AXS8ZTzJaA).
- [Evennia for MUSHers](http://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing Evennia for those used to the MUSH way of doing things.
- *[Language Understanding for Text games using Deep reinforcement learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)* ([PDF](http://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia to train AIs.
- [EvCast video series](https://www.youtube.com/playlist?list=PLyYMNttpc-SX1hvaqlUNmcxrhmM64pQXl) -
Tutorial videos explaining installing Evennia, basic Python etc.
- [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker
container](https://www.docker.com/) for quick install and deployment in just a few commands.
- [Evennia's docs in Chinese](http://www.evenniacn.com/) - A translated mirror of a slightly older
Evennia version. Announcement [here](https://groups.google.com/forum/#!topic/evennia/3AXS8ZTzJaA).
- [Evennia for MUSHers](http://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing
Evennia for those used to the MUSH way of doing things.
- *[Language Understanding for Text games using Deep reinforcement
learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)*
([PDF](http://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia
to train AIs.
### Other useful mud development resources
- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to Python objects.
- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to
Python objects.
- [Gossip MUD chat network](https://gossip.haus/)
### General MUD forums and discussions
- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack channel](https://slack.mudcoders.com/) with discussions on MUD development.
- [MuSoapbox](http://www.musoapbox.net/) - Very active Mu* game community mainly focused on MUSH-type gaming.
- [Imaginary Realities](http://journal.imaginary-realities.com/) - An e-magazine on game and MUD design that has several articles about Evennia. There is also an [archive of older issues](http://disinterest.org/resource/imaginary-realities/) from 1998-2001 that are still very relevant.
- [Optional Realities](http://optionalrealities.com/) - Mud development discussion forums that has regular articles on MUD development focused on roleplay-intensive games. After a HD crash it's not as content-rich as it once was.
- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack
channel](https://slack.mudcoders.com/) with discussions on MUD development.
- [MuSoapbox](http://www.musoapbox.net/) - Very active Mu* game community mainly focused on MUSH-
type gaming.
- [Imaginary Realities](http://journal.imaginary-realities.com/) - An e-magazine on game and MUD
design that has several articles about Evennia. There is also an [archive of older
issues](http://disinterest.org/resource/imaginary-realities/) from 1998-2001 that are still very
relevant.
- [Optional Realities](http://optionalrealities.com/) - Mud development discussion forums that has
regular articles on MUD development focused on roleplay-intensive games. After a HD crash it's not
as content-rich as it once was.
- [MudLab](http://mudlab.org/) - Mud design discussion forum
- [MudConnector](http://www.mudconnect.com/) - Mud listing and forums
- [MudBytes](http://www.mudbytes.net/) - Mud listing and forums
- [Top Mud Sites](http://www.topmudsites.com/) - Mud listing and forums
- [Planet Mud-Dev](http://planet-muddev.disinterest.org/) - A blog aggregator following blogs of current MUD development (including Evennia) around the 'net. Worth to put among your RSS subscriptions.
- Mud Dev mailing list archive ([mirror](http://www.disinterest.org/resource/MUD-Dev/)) - Influential mailing list active 1996-2004. Advanced game design discussions.
- [Planet Mud-Dev](http://planet-muddev.disinterest.org/) - A blog aggregator following blogs of
current MUD development (including Evennia) around the 'net. Worth to put among your RSS
subscriptions.
- Mud Dev mailing list archive ([mirror](http://www.disinterest.org/resource/MUD-Dev/)) -
Influential mailing list active 1996-2004. Advanced game design discussions.
- [Mud-dev wiki](http://mud-dev.wikidot.com/) - A (very) slowly growing resource on MUD creation.
- [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD telnet protocols.
- [Mud Tech's fun/cool but ...](http://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-ship-damned-thing/) - Greg Taylor gives good advice on mud design.
- [Lost Library of MOO](http://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in particular moo).
- [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) - Contains a very useful list of things to think about when starting your new MUD.
- [Lost Garden](http://www.lostgarden.com/) - A game development blog with long and interesting articles (not MUD-specific)
- [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD
telnet protocols.
- [Mud Tech's fun/cool but ...](http://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-
ship-damned-thing/) - Greg Taylor gives good advice on mud design.
- [Lost Library of MOO](http://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in
particular moo).
- [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) -
Contains a very useful list of things to think about when starting your new MUD.
- [Lost Garden](http://www.lostgarden.com/) - A game development blog with long and interesting
articles (not MUD-specific)
- [What Games Are](http://whatgamesare.com/) - A blog about general game design (not MUD-specific)
- [The Alexandrian](http://thealexandrian.net/) - A blog about tabletop roleplaying and board games, but with lots of general discussion about rule systems and game balance that could be applicable also for MUDs.
- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world-design/the-laws-of-online-world-design/) - thought-provoking guidelines and things to think about when designing a virtual multiplayer world (Raph is known for *Ultima Online* among other things).
- [The Alexandrian](http://thealexandrian.net/) - A blog about tabletop roleplaying and board games,
but with lots of general discussion about rule systems and game balance that could be applicable
also for MUDs.
- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world-
design/the-laws-of-online-world-design/) - thought-provoking guidelines and things to think about
when designing a virtual multiplayer world (Raph is known for *Ultima Online* among other things).
### Literature
- Richard Bartle *Designing Virtual Worlds* ([amazon page](http://www.amazon.com/Designing-Virtual-Worlds-Richard-Bartle/dp/0131018167)) - Essential reading for the design of any persistent game world, written by the co-creator of the original game *MUD*. Published in 2003 but it's still as relevant now as when it came out. Covers everything you need to know and then some.
- Zed A. Shaw *Learn Python the Hard way* ([homepage](https://learnpythonthehardway.org/)) - Despite the imposing name this book is for the absolute Python/programming beginner. One learns the language by gradually creating a small text game! It has been used by multiple users before moving on to Evennia. *Update: This used to be free to read online, this is no longer the case.*
- David M. Beazley *Python Essential Reference (4th ed)* ([amazon page](http://www.amazon.com/Python-Essential-Reference-David-Beazley/dp/0672329786/)) - Our recommended book on Python; it not only efficiently summarizes the language but is also an excellent reference to the standard library for more experienced Python coders.
- Luciano Ramalho, *Fluent Python* ([o'reilly page](http://shop.oreilly.com/product/0636920032519.do)) - This is an excellent book for experienced Python coders willing to take their code to the next level. A great read with a lot of useful info also for veteran Pythonistas.
- Richard Cantillon *An Essay on Economic Theory* ([free pdf](http://mises.org/books/essay_on_economic_theory_cantillon.pdf)) - A very good English translation of *Essai sur la Nature du Commerce en Général*, one of the foundations of modern economic theory. Written in 1730 but the translation is annotated and the essay is actually very easy to follow also for a modern reader. Required reading if you think of implementing a sane game economic system.
- Richard Bartle *Designing Virtual Worlds* ([amazon page](http://www.amazon.com/Designing-Virtual-
Worlds-Richard-Bartle/dp/0131018167)) - Essential reading for the design of any persistent game
world, written by the co-creator of the original game *MUD*. Published in 2003 but it's still as
relevant now as when it came out. Covers everything you need to know and then some.
- Zed A. Shaw *Learn Python the Hard way* ([homepage](https://learnpythonthehardway.org/)) - Despite
the imposing name this book is for the absolute Python/programming beginner. One learns the language
by gradually creating a small text game! It has been used by multiple users before moving on to
Evennia. *Update: This used to be free to read online, this is no longer the case.*
- David M. Beazley *Python Essential Reference (4th ed)* ([amazon
page](http://www.amazon.com/Python-Essential-Reference-David-Beazley/dp/0672329786/)) - Our
recommended book on Python; it not only efficiently summarizes the language but is also an excellent
reference to the standard library for more experienced Python coders.
- Luciano Ramalho, *Fluent Python* ([o'reilly
page](http://shop.oreilly.com/product/0636920032519.do)) - This is an excellent book for experienced
Python coders willing to take their code to the next level. A great read with a lot of useful info
also for veteran Pythonistas.
- Richard Cantillon *An Essay on Economic Theory* ([free
pdf](http://mises.org/books/essay_on_economic_theory_cantillon.pdf)) - A very good English
translation of *Essai sur la Nature du Commerce en Général*, one of the foundations of modern
economic theory. Written in 1730 but the translation is annotated and the essay is actually very
easy to follow also for a modern reader. Required reading if you think of implementing a sane game
economic system.
### Frameworks
@ -103,8 +171,11 @@
- [Library Reference](http://docs.python.org/lib/lib.html)
- [Language Reference](http://docs.python.org/ref/ref.html)
- [Python tips and tricks](http://www.siafoo.net/article/52)
- [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) - free online programming curriculum for different skill levels
- [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) - free online
programming curriculum for different skill levels
### Credits
- Wiki [Home](index) Icons made by [Freepik](http://www.freepik.com"-title="Freepik">Freepik) from [flaticon.com](http://www.flaticon.com), licensed under [Creative Commons BY 3.0](http://creativecommons.org/licenses/by/3.0).
- Wiki [Home](index) Icons made by [Freepik](http://www.freepik.com"-title="Freepik">Freepik) from
[flaticon.com](http://www.flaticon.com), licensed under [Creative Commons BY
3.0](http://creativecommons.org/licenses/by/3.0).

View file

@ -1,11 +1,22 @@
# Locks
For most games it is a good idea to restrict what people can do. In Evennia such restrictions are applied and checked by something called *locks*. All Evennia entities ([Commands](Commands), [Objects](Objects), [Scripts](Scripts), [Accounts](Accounts), [Help System](Help-System), [messages](Communications#Msg) and [channels](Communications#Channels)) are accessed through locks.
For most games it is a good idea to restrict what people can do. In Evennia such restrictions are
applied and checked by something called *locks*. All Evennia entities ([Commands](Commands),
[Objects](Objects), [Scripts](Scripts), [Accounts](Accounts), [Help System](Help-System),
[messages](Communications#Msg) and [channels](Communications#Channels)) are accessed through locks.
A lock can be thought of as an "access rule" restricting a particular use of an Evennia entity. Whenever another entity wants that kind of access the lock will analyze that entity in different ways to determine if access should be granted or not. Evennia implements a "lockdown" philosophy - all entities are inaccessible unless you explicitly define a lock that allows some or full access.
A lock can be thought of as an "access rule" restricting a particular use of an Evennia entity.
Whenever another entity wants that kind of access the lock will analyze that entity in different
ways to determine if access should be granted or not. Evennia implements a "lockdown" philosophy -
all entities are inaccessible unless you explicitly define a lock that allows some or full access.
Let's take an example: An object has a lock on itself that restricts how people may "delete" that object. Apart from knowing that it restricts deletion, the lock also knows that only players with the specific ID of, say, `34` are allowed to delete it. So whenever a player tries to run `delete` on the object, the `delete` command makes sure to check if this player is really allowed to do so. It calls the lock, which in turn checks if the player's id is `34`. Only then will it allow `delete` to go on with its job.
Let's take an example: An object has a lock on itself that restricts how people may "delete" that
object. Apart from knowing that it restricts deletion, the lock also knows that only players with
the specific ID of, say, `34` are allowed to delete it. So whenever a player tries to run `delete`
on the object, the `delete` command makes sure to check if this player is really allowed to do so.
It calls the lock, which in turn checks if the player's id is `34`. Only then will it allow `delete`
to go on with its job.
## Setting and checking a lock
@ -13,15 +24,21 @@ The in-game command for setting locks on objects is `lock`:
> lock obj = <lockstring>
The `<lockstring>` is a string of a certain form that defines the behaviour of the lock. We will go into more detail on how `<lockstring>` should look in the next section.
The `<lockstring>` is a string of a certain form that defines the behaviour of the lock. We will go
into more detail on how `<lockstring>` should look in the next section.
Code-wise, Evennia handles locks through what is usually called `locks` on all relevant entities. This is a handler that allows you to add, delete and check locks.
Code-wise, Evennia handles locks through what is usually called `locks` on all relevant entities.
This is a handler that allows you to add, delete and check locks.
```python
myobj.locks.add(<lockstring>)
```
One can call `locks.check()` to perform a lock check, but to hide the underlying implementation all objects also have a convenience function called `access`. This should preferably be used. In the example below, `accessing_obj` is the object requesting the 'delete' access whereas `obj` is the object that might get deleted. This is how it would look (and does look) from inside the `delete` command:
One can call `locks.check()` to perform a lock check, but to hide the underlying implementation all
objects also have a convenience function called `access`. This should preferably be used. In the
example below, `accessing_obj` is the object requesting the 'delete' access whereas `obj` is the
object that might get deleted. This is how it would look (and does look) from inside the `delete`
command:
```python
if not obj.access(accessing_obj, 'delete'):
@ -31,7 +48,8 @@ One can call `locks.check()` to perform a lock check, but to hide the underlying
## Defining locks
Defining a lock (i.e. an access restriction) in Evennia is done by adding simple strings of lock definitions to the object's `locks` property using `obj.locks.add()`.
Defining a lock (i.e. an access restriction) in Evennia is done by adding simple strings of lock
definitions to the object's `locks` property using `obj.locks.add()`.
Here are some examples of lock strings (not including the quotes):
@ -48,28 +66,42 @@ Formally, a lockstring has the following syntax:
access_type: [NOT] lockfunc1([arg1,..]) [AND|OR] [NOT] lockfunc2([arg1,...]) [...]
```
where `[]` marks optional parts. `AND`, `OR` and `NOT` are not case sensitive and excess spaces are ignored. `lockfunc1, lockfunc2` etc are special _lock functions_ available to the lock system.
where `[]` marks optional parts. `AND`, `OR` and `NOT` are not case sensitive and excess spaces are
ignored. `lockfunc1, lockfunc2` etc are special _lock functions_ available to the lock system.
So, a lockstring consists of the type of restriction (the `access_type`), a colon (`:`) and then an expression involving function calls that determine what is needed to pass the lock. Each function returns either `True` or `False`. `AND`, `OR` and `NOT` work as they do normally in Python. If the total result is `True`, the lock is passed.
So, a lockstring consists of the type of restriction (the `access_type`), a colon (`:`) and then an
expression involving function calls that determine what is needed to pass the lock. Each function
returns either `True` or `False`. `AND`, `OR` and `NOT` work as they do normally in Python. If the
total result is `True`, the lock is passed.
You can create several lock types one after the other by separating them with a semicolon (`;`) in the lockstring. The string below yields the same result as the previous example:
You can create several lock types one after the other by separating them with a semicolon (`;`) in
the lockstring. The string below yields the same result as the previous example:
delete:id(34);edit:all();get: not attr(very_weak) or perm(Admin)
### Valid access_types
An `access_type`, the first part of a lockstring, defines what kind of capability a lock controls, such as "delete" or "edit". You may in principle name your `access_type` anything as long as it is unique for the particular object. The name of the access types is not case-sensitive.
An `access_type`, the first part of a lockstring, defines what kind of capability a lock controls,
such as "delete" or "edit". You may in principle name your `access_type` anything as long as it is
unique for the particular object. The name of the access types is not case-sensitive.
If you want to make sure the lock is used however, you should pick `access_type` names that you (or the default command set) actually checks for, as in the example of `delete` above that uses the 'delete' `access_type`.
If you want to make sure the lock is used however, you should pick `access_type` names that you (or
the default command set) actually checks for, as in the example of `delete` above that uses the
'delete' `access_type`.
Below are the access_types checked by the default commandset.
- [Commands](Commands)
- `cmd` - this defines who may call this command at all.
- [Objects](Objects):
- `control` - who is the "owner" of the object. Can set locks, delete it etc. Defaults to the creator of the object.
- `call` - who may call Object-commands stored on this Object except for the Object itself. By default, Objects share their Commands with anyone in the same location (e.g. so you can 'press' a `Button` object in the room). For Characters and Mobs (who likely only use those Commands for themselves and don't want to share them) this should usually be turned off completely, using something like `call:false()`.
- `control` - who is the "owner" of the object. Can set locks, delete it etc. Defaults to the
creator of the object.
- `call` - who may call Object-commands stored on this Object except for the Object itself. By
default, Objects share their Commands with anyone in the same location (e.g. so you can 'press' a
`Button` object in the room). For Characters and Mobs (who likely only use those Commands for
themselves and don't want to share them) this should usually be turned off completely, using
something like `call:false()`.
- `examine` - who may examine this object's properties.
- `delete` - who may delete the object.
- `edit` - who may edit properties and attributes of the object.
@ -92,26 +124,36 @@ Below are the access_types checked by the default commandset.
- `attrread` - see/access attribute
- `attredit` - change/delete attribute
- [Channels](Communications#Channels):
- `control` - who is administrating the channel. This means the ability to delete the channel, boot listeners etc.
- `control` - who is administrating the channel. This means the ability to delete the channel,
boot listeners etc.
- `send` - who may send to the channel.
- `listen` - who may subscribe and listen to the channel.
- [HelpEntry](Help-System):
- `examine` - who may view this help entry (usually everyone)
- `edit` - who may edit this help entry.
So to take an example, whenever an exit is to be traversed, a lock of the type *traverse* will be checked. Defining a suitable lock type for an exit object would thus involve a lockstring `traverse: <lock functions>`.
So to take an example, whenever an exit is to be traversed, a lock of the type *traverse* will be
checked. Defining a suitable lock type for an exit object would thus involve a lockstring `traverse:
<lock functions>`.
### Custom access_types
As stated above, the `access_type` part of the lock is simply the 'name' or 'type' of the lock. The text is an arbitrary string that must be unique for an object. If adding a lock with the same `access_type` as one that already exists on the object, the new one override the old one.
As stated above, the `access_type` part of the lock is simply the 'name' or 'type' of the lock. The
text is an arbitrary string that must be unique for an object. If adding a lock with the same
`access_type` as one that already exists on the object, the new one override the old one.
For example, if you wanted to create a bulletin board system and wanted to restrict who can either read a board or post to a board. You could then define locks such as:
For example, if you wanted to create a bulletin board system and wanted to restrict who can either
read a board or post to a board. You could then define locks such as:
```python
obj.locks.add("read:perm(Player);post:perm(Admin)")
```
This will create a 'read' access type for Characters having the `Player` permission or above and a 'post' access type for those with `Admin` permissions or above (see below how the `perm()` lock function works). When it comes time to test these permissions, simply check like this (in this example, the `obj` may be a board on the bulletin board system and `accessing_obj` is the player trying to read the board):
This will create a 'read' access type for Characters having the `Player` permission or above and a
'post' access type for those with `Admin` permissions or above (see below how the `perm()` lock
function works). When it comes time to test these permissions, simply check like this (in this
example, the `obj` may be a board on the bulletin board system and `accessing_obj` is the player
trying to read the board):
```python
if not obj.access(accessing_obj, 'read'):
@ -121,9 +163,17 @@ This will create a 'read' access type for Characters having the `Player` permiss
### Lock functions
A lock function is a normal Python function put in a place Evennia looks for such functions. The modules Evennia looks at is the list `settings.LOCK_FUNC_MODULES`. *All functions* in any of those modules will automatically be considered a valid lock function. The default ones are found in `evennia/locks/lockfuncs.py` and you can start adding your own in `mygame/server/conf/lockfuncs.py`. You can append the setting to add more module paths. To replace a default lock function, just add your own with the same name.
A lock function is a normal Python function put in a place Evennia looks for such functions. The
modules Evennia looks at is the list `settings.LOCK_FUNC_MODULES`. *All functions* in any of those
modules will automatically be considered a valid lock function. The default ones are found in
`evennia/locks/lockfuncs.py` and you can start adding your own in `mygame/server/conf/lockfuncs.py`.
You can append the setting to add more module paths. To replace a default lock function, just add
your own with the same name.
A lock function must always accept at least two arguments - the *accessing object* (this is the object wanting to get access) and the *accessed object* (this is the object with the lock). Those two are fed automatically as the first two arguments to the function when the lock is checked. Any arguments explicitly given in the lock definition will appear as extra arguments.
A lock function must always accept at least two arguments - the *accessing object* (this is the
object wanting to get access) and the *accessed object* (this is the object with the lock). Those
two are fed automatically as the first two arguments to the function when the lock is checked. Any
arguments explicitly given in the lock definition will appear as extra arguments.
```python
# A simple example lock function. Called with e.g. `id(34)`. This is
@ -154,27 +204,38 @@ We could check if the "edit" lock is passed with something like this:
In this example, everyone except the `caller` with the right `id` will get the error.
> (Using the `*` and `**` syntax causes Python to magically put all extra arguments into a list `args` and all keyword arguments into a dictionary `kwargs` respectively. If you are unfamiliar with how `*args` and `**kwargs` work, see the Python manuals).
> (Using the `*` and `**` syntax causes Python to magically put all extra arguments into a list
`args` and all keyword arguments into a dictionary `kwargs` respectively. If you are unfamiliar with
how `*args` and `**kwargs` work, see the Python manuals).
Some useful default lockfuncs (see `src/locks/lockfuncs.py` for more):
- `true()/all()` - give access to everyone
- `false()/none()/superuser()` - give access to none. Superusers bypass the check entirely and are thus the only ones who will pass this check.
- `perm(perm)` - this tries to match a given `permission` property, on an Account firsthand, on a Character second. See [below](Locks#permissions).
- `false()/none()/superuser()` - give access to none. Superusers bypass the check entirely and are
thus the only ones who will pass this check.
- `perm(perm)` - this tries to match a given `permission` property, on an Account firsthand, on a
Character second. See [below](Locks#permissions).
- `perm_above(perm)` - like `perm` but requires a "higher" permission level than the one given.
- `id(num)/dbref(num)` - checks so the access_object has a certain dbref/id.
- `attr(attrname)` - checks if a certain [Attribute](Attributes) exists on accessing_object.
- `attr(attrname, value)` - checks so an attribute exists on accessing_object *and* has the given value.
- `attr_gt(attrname, value)` - checks so accessing_object has a value larger (`>`) than the given value.
- `attr(attrname, value)` - checks so an attribute exists on accessing_object *and* has the given
value.
- `attr_gt(attrname, value)` - checks so accessing_object has a value larger (`>`) than the given
value.
- `attr_ge, attr_lt, attr_le, attr_ne` - corresponding for `>=`, `<`, `<=` and `!=`.
- `holds(objid)` - checks so the accessing objects contains an object of given name or dbref.
- `inside()` - checks so the accessing object is inside the accessed object (the inverse of `holds()`).
- `pperm(perm)`, `pid(num)/pdbref(num)` - same as `perm`, `id/dbref` but always looks for permissions and dbrefs of *Accounts*, not on Characters.
- `serversetting(settingname, value)` - Only returns True if Evennia has a given setting or a setting set to a given value.
- `inside()` - checks so the accessing object is inside the accessed object (the inverse of
`holds()`).
- `pperm(perm)`, `pid(num)/pdbref(num)` - same as `perm`, `id/dbref` but always looks for
permissions and dbrefs of *Accounts*, not on Characters.
- `serversetting(settingname, value)` - Only returns True if Evennia has a given setting or a
setting set to a given value.
## Checking simple strings
Sometimes you don't really need to look up a certain lock, you just want to check a lockstring. A common use is inside Commands, in order to check if a user has a certain permission. The lockhandler has a method `check_lockstring(accessing_obj, lockstring, bypass_superuser=False)` that allows this.
Sometimes you don't really need to look up a certain lock, you just want to check a lockstring. A
common use is inside Commands, in order to check if a user has a certain permission. The lockhandler
has a method `check_lockstring(accessing_obj, lockstring, bypass_superuser=False)` that allows this.
```python
# inside command definition
@ -183,19 +244,32 @@ Sometimes you don't really need to look up a certain lock, you just want to chec
return
```
Note here that the `access_type` can be left to a dummy value since this method does not actually do a Lock lookup.
Note here that the `access_type` can be left to a dummy value since this method does not actually do
a Lock lookup.
## Default locks
Evennia sets up a few basic locks on all new objects and accounts (if we didn't, noone would have any access to anything from the start). This is all defined in the root [Typeclasses](Typeclasses) of the respective entity, in the hook method `basetype_setup()` (which you usually don't want to edit unless you want to change how basic stuff like rooms and exits store their internal variables). This is called once, before `at_object_creation`, so just put them in the latter method on your child object to change the default. Also creation commands like `create` changes the locks of objects you create - for example it sets the `control` lock_type so as to allow you, its creator, to control and delete the object.
Evennia sets up a few basic locks on all new objects and accounts (if we didn't, noone would have
any access to anything from the start). This is all defined in the root [Typeclasses](Typeclasses)
of the respective entity, in the hook method `basetype_setup()` (which you usually don't want to
edit unless you want to change how basic stuff like rooms and exits store their internal variables).
This is called once, before `at_object_creation`, so just put them in the latter method on your
child object to change the default. Also creation commands like `create` changes the locks of
objects you create - for example it sets the `control` lock_type so as to allow you, its creator, to
control and delete the object.
# Permissions
> This section covers the underlying code use of permissions. If you just want to learn how to practically assign permissions in-game, refer to the [Building Permissions](Building-Permissions) page, which details how you use the `perm` command.
> This section covers the underlying code use of permissions. If you just want to learn how to
practically assign permissions in-game, refer to the [Building Permissions](Building-Permissions)
page, which details how you use the `perm` command.
A *permission* is simply a list of text strings stored in the handler `permissions` on `Objects` and `Accounts`. Permissions can be used as a convenient way to structure access levels and hierarchies. It is set by the `perm` command. Permissions are especially handled by the `perm()` and `pperm()` lock functions listed above.
A *permission* is simply a list of text strings stored in the handler `permissions` on `Objects`
and `Accounts`. Permissions can be used as a convenient way to structure access levels and
hierarchies. It is set by the `perm` command. Permissions are especially handled by the `perm()` and
`pperm()` lock functions listed above.
Let's say we have a `red_key` object. We also have red chests that we want to unlock with this key.
Let's say we have a `red_key` object. We also have red chests that we want to unlock with this key.
perm red_key = unlocks_red_chests
@ -203,9 +277,12 @@ This gives the `red_key` object the permission "unlocks_red_chests". Next we lo
lock red chest = unlock:perm(unlocks_red_chests)
What this lock will expect is to the fed the actual key object. The `perm()` lock function will check the permissions set on the key and only return true if the permission is the one given.
What this lock will expect is to the fed the actual key object. The `perm()` lock function will
check the permissions set on the key and only return true if the permission is the one given.
Finally we need to actually check this lock somehow. Let's say the chest has an command `open <key>` sitting on itself. Somewhere in its code the command needs to figure out which key you are using and test if this key has the correct permission:
Finally we need to actually check this lock somehow. Let's say the chest has an command `open <key>`
sitting on itself. Somewhere in its code the command needs to figure out which key you are using and
test if this key has the correct permission:
```python
# self.obj is the chest
@ -217,9 +294,11 @@ Finally we need to actually check this lock somehow. Let's say the chest has an
return
```
All new accounts are given a default set of permissions defined by `settings.PERMISSION_ACCOUNT_DEFAULT`.
All new accounts are given a default set of permissions defined by
`settings.PERMISSION_ACCOUNT_DEFAULT`.
Selected permission strings can be organized in a *permission hierarchy* by editing the tuple `settings.PERMISSION_HIERARCHY`. Evennia's default permission hierarchy is as follows:
Selected permission strings can be organized in a *permission hierarchy* by editing the tuple
`settings.PERMISSION_HIERARCHY`. Evennia's default permission hierarchy is as follows:
Developer # like superuser but affected by locks
Admin # can administrate accounts
@ -229,20 +308,36 @@ Selected permission strings can be organized in a *permission hierarchy* by edit
(Also the plural form works, so you could use `Developers` etc too).
> There is also a `Guest` level below `Player` that is only active if `settings.GUEST_ENABLED` is set. This is never part of `settings.PERMISSION_HIERARCHY`.
> There is also a `Guest` level below `Player` that is only active if `settings.GUEST_ENABLED` is
set. This is never part of `settings.PERMISSION_HIERARCHY`.
The main use of this is that if you use the lock function `perm()` mentioned above, a lock check for a particular permission in the hierarchy will *also* grant access to those with *higher* hierarchy access. So if you have the permission "Admin" you will also pass a lock defined as `perm(Builder)` or any of those levels below "Admin".
The main use of this is that if you use the lock function `perm()` mentioned above, a lock check for
a particular permission in the hierarchy will *also* grant access to those with *higher* hierarchy
access. So if you have the permission "Admin" you will also pass a lock defined as `perm(Builder)`
or any of those levels below "Admin".
When doing an access check from an [Object](Objects) or Character, the `perm()` lock function will always first use the permissions of any Account connected to that Object before checking for permissions on the Object. In the case of hierarchical permissions (Admins, Builders etc), the Account permission will always be used (this stops an Account from escalating their permission by puppeting a high-level Character). If the permission looked for is not in the hierarchy, an exact match is required, first on the Account and if not found there (or if no Account is connected), then on the Object itself.
When doing an access check from an [Object](Objects) or Character, the `perm()` lock function will
always first use the permissions of any Account connected to that Object before checking for
permissions on the Object. In the case of hierarchical permissions (Admins, Builders etc), the
Account permission will always be used (this stops an Account from escalating their permission by
puppeting a high-level Character). If the permission looked for is not in the hierarchy, an exact
match is required, first on the Account and if not found there (or if no Account is connected), then
on the Object itself.
Here is how you use `perm` to give an account more permissions:
perm/account Tommy = Builders
perm/account/del Tommy = Builders # remove it again
Note the use of the `/account` switch. It means you assign the permission to the [Accounts](Accounts) Tommy instead of any [Character](Objects) that also happens to be named "Tommy".
Note the use of the `/account` switch. It means you assign the permission to the
[Accounts](Accounts) Tommy instead of any [Character](Objects) that also happens to be named
"Tommy".
Putting permissions on the *Account* guarantees that they are kept, *regardless* of which Character they are currently puppeting. This is especially important to remember when assigning permissions from the *hierarchy tree* - as mentioned above, an Account's permissions will overrule that of its character. So to be sure to avoid confusion you should generally put hierarchy permissions on the Account, not on their Characters (but see also [quelling](Locks#Quelling)).
Putting permissions on the *Account* guarantees that they are kept, *regardless* of which Character
they are currently puppeting. This is especially important to remember when assigning permissions
from the *hierarchy tree* - as mentioned above, an Account's permissions will overrule that of its
character. So to be sure to avoid confusion you should generally put hierarchy permissions on the
Account, not on their Characters (but see also [quelling](Locks#Quelling)).
Below is an example of an object without any connected account
@ -265,51 +360,78 @@ And one example of a puppet with a connected account:
## Superusers
There is normally only one *superuser* account and that is the one first created when starting Evennia (User #1). This is sometimes known as the "Owner" or "God" user. A superuser has more than full access - it completely *bypasses* all locks so no checks are even run. This allows for the superuser to always have access to everything in an emergency. But it also hides any eventual errors you might have made in your lock definitions. So when trying out game systems you should either use quelling (see below) or make a second Developer-level character so your locks get tested correctly.
There is normally only one *superuser* account and that is the one first created when starting
Evennia (User #1). This is sometimes known as the "Owner" or "God" user. A superuser has more than
full access - it completely *bypasses* all locks so no checks are even run. This allows for the
superuser to always have access to everything in an emergency. But it also hides any eventual errors
you might have made in your lock definitions. So when trying out game systems you should either use
quelling (see below) or make a second Developer-level character so your locks get tested correctly.
## Quelling
The `quell` command can be used to enforce the `perm()` lockfunc to ignore permissions on the Account and instead use the permissions on the Character only. This can be used e.g. by staff to test out things with a lower permission level. Return to the normal operation with `unquell`. Note that quelling will use the smallest of any hierarchical permission on the Account or Character, so one cannot escalate one's Account permission by quelling to a high-permission Character. Also the superuser can quell their powers this way, making them affectable by locks.
The `quell` command can be used to enforce the `perm()` lockfunc to ignore permissions on the
Account and instead use the permissions on the Character only. This can be used e.g. by staff to
test out things with a lower permission level. Return to the normal operation with `unquell`. Note
that quelling will use the smallest of any hierarchical permission on the Account or Character, so
one cannot escalate one's Account permission by quelling to a high-permission Character. Also the
superuser can quell their powers this way, making them affectable by locks.
## More Lock definition examples
examine: attr(eyesight, excellent) or perm(Builders)
You are only allowed to do *examine* on this object if you have 'excellent' eyesight (that is, has an Attribute `eyesight` with the value `excellent` defined on yourself) or if you have the "Builders" permission string assigned to you.
You are only allowed to do *examine* on this object if you have 'excellent' eyesight (that is, has
an Attribute `eyesight` with the value `excellent` defined on yourself) or if you have the
"Builders" permission string assigned to you.
open: holds('the green key') or perm(Builder)
This could be called by the `open` command on a "door" object. The check is passed if you are a Builder or has the right key in your inventory.
This could be called by the `open` command on a "door" object. The check is passed if you are a
Builder or has the right key in your inventory.
cmd: perm(Builders)
Evennia's command handler looks for a lock of type `cmd` to determine if a user is allowed to even call upon a particular command or not. When you define a command, this is the kind of lock you must set. See the default command set for lots of examples. If a character/account don't pass the `cmd` lock type the command will not even appear in their `help` list.
Evennia's command handler looks for a lock of type `cmd` to determine if a user is allowed to even
call upon a particular command or not. When you define a command, this is the kind of lock you must
set. See the default command set for lots of examples. If a character/account don't pass the `cmd`
lock type the command will not even appear in their `help` list.
cmd: not perm(no_tell)
"Permissions" can also be used to block users or implement highly specific bans. The above example would be be added as a lock string to the `tell` command. This will allow everyone *not* having the "permission" `no_tell` to use the `tell` command. You could easily give an account the "permission" `no_tell` to disable their use of this particular command henceforth.
"Permissions" can also be used to block users or implement highly specific bans. The above example
would be be added as a lock string to the `tell` command. This will allow everyone *not* having the
"permission" `no_tell` to use the `tell` command. You could easily give an account the "permission"
`no_tell` to disable their use of this particular command henceforth.
```python
dbref = caller.id
lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Admin);get:all()" % (dbref, dbref)
lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Admin);get:all()" %
(dbref, dbref)
new_obj.locks.add(lockstring)
```
This is how the `create` command sets up new objects. In sequence, this permission string sets the owner of this object be the creator (the one running `create`). Builders may examine the object whereas only Admins and the creator may delete it. Everyone can pick it up.
This is how the `create` command sets up new objects. In sequence, this permission string sets the
owner of this object be the creator (the one running `create`). Builders may examine the object
whereas only Admins and the creator may delete it. Everyone can pick it up.
## A complete example of setting locks on an object
Assume we have two objects - one is ourselves (not superuser) and the other is an [Object](Objects) called `box`.
Assume we have two objects - one is ourselves (not superuser) and the other is an [Object](Objects)
called `box`.
> create/drop box
> desc box = "This is a very big and heavy box."
We want to limit which objects can pick up this heavy box. Let's say that to do that we require the would-be lifter to to have an attribute *strength* on themselves, with a value greater than 50. We assign it to ourselves to begin with.
We want to limit which objects can pick up this heavy box. Let's say that to do that we require the
would-be lifter to to have an attribute *strength* on themselves, with a value greater than 50. We
assign it to ourselves to begin with.
> set self/strength = 45
Ok, so for testing we made ourselves strong, but not strong enough. Now we need to look at what happens when someone tries to pick up the the box - they use the `get` command (in the default set). This is defined in `evennia/commands/default/general.py`. In its code we find this snippet:
Ok, so for testing we made ourselves strong, but not strong enough. Now we need to look at what
happens when someone tries to pick up the the box - they use the `get` command (in the default set).
This is defined in `evennia/commands/default/general.py`. In its code we find this snippet:
```python
if not obj.access(caller, 'get'):
@ -320,17 +442,23 @@ Ok, so for testing we made ourselves strong, but not strong enough. Now we need
return
```
So the `get` command looks for a lock with the type *get* (not so surprising). It also looks for an [Attribute](Attributes) on the checked object called _get_err_msg_ in order to return a customized error message. Sounds good! Let's start by setting that on the box:
So the `get` command looks for a lock with the type *get* (not so surprising). It also looks for an
[Attribute](Attributes) on the checked object called _get_err_msg_ in order to return a customized
error message. Sounds good! Let's start by setting that on the box:
> set box/get_err_msg = You are not strong enough to lift this box.
Next we need to craft a Lock of type *get* on our box. We want it to only be passed if the accessing object has the attribute *strength* of the right value. For this we would need to create a lock function that checks if attributes have a value greater than a given value. Luckily there is already such a one included in evennia (see `evennia/locks/lockfuncs.py`), called `attr_gt`.
Next we need to craft a Lock of type *get* on our box. We want it to only be passed if the accessing
object has the attribute *strength* of the right value. For this we would need to create a lock
function that checks if attributes have a value greater than a given value. Luckily there is already
such a one included in evennia (see `evennia/locks/lockfuncs.py`), called `attr_gt`.
So the lock string will look like this: `get:attr_gt(strength, 50)`. We put this on the box now:
lock box = get:attr_gt(strength, 50)
Try to `get` the object and you should get the message that we are not strong enough. Increase your strength above 50 however and you'll pick it up no problem. Done! A very heavy box!
Try to `get` the object and you should get the message that we are not strong enough. Increase your
strength above 50 however and you'll pick it up no problem. Done! A very heavy box!
If you wanted to set this up in python code, it would look something like this:
@ -354,6 +482,14 @@ If you wanted to set this up in python code, it would look something like this:
## On Django's permission system
Django also implements a comprehensive permission/security system of its own. The reason we don't use that is because it is app-centric (app in the Django sense). Its permission strings are of the form `appname.permstring` and it automatically adds three of them for each database model in the app - for the app evennia/object this would be for example 'object.create', 'object.admin' and 'object.edit'. This makes a lot of sense for a web application, not so much for a MUD, especially when we try to hide away as much of the underlying architecture as possible.
Django also implements a comprehensive permission/security system of its own. The reason we don't
use that is because it is app-centric (app in the Django sense). Its permission strings are of the
form `appname.permstring` and it automatically adds three of them for each database model in the app
- for the app evennia/object this would be for example 'object.create', 'object.admin' and
'object.edit'. This makes a lot of sense for a web application, not so much for a MUD, especially
when we try to hide away as much of the underlying architecture as possible.
The django permissions are not completely gone however. We use it for validating passwords during login. It is also used exclusively for managing Evennia's web-based admin site, which is a graphical front-end for the database of Evennia. You edit and assign such permissions directly from the web interface. It's stand-alone from the permissions described above.
The django permissions are not completely gone however. We use it for validating passwords during
login. It is also used exclusively for managing Evennia's web-based admin site, which is a graphical
front-end for the database of Evennia. You edit and assign such permissions directly from the web
interface. It's stand-alone from the permissions described above.

View file

@ -1,11 +1,19 @@
# Manually Configuring Color
This is a small tutorial for customizing your character objects, using the example of letting users turn on and off ANSI color parsing as an example. `@options NOCOLOR=True` will now do what this tutorial shows, but the tutorial subject can be applied to other toggles you may want, as well.
This is a small tutorial for customizing your character objects, using the example of letting users
turn on and off ANSI color parsing as an example. `@options NOCOLOR=True` will now do what this
tutorial shows, but the tutorial subject can be applied to other toggles you may want, as well.
In the Building guide's [Colors](TextTags#coloured-text) page you can learn how to add color to your game by using special markup. Colors enhance the gaming experience, but not all users want color. Examples would be users working from clients that don't support color, or people with various seeing disabilities that rely on screen readers to play your game. Also, whereas Evennia normally automatically detects if a client supports color, it may get it wrong. Being able to turn it on manually if you know it **should** work could be a nice feature.
In the Building guide's [Colors](TextTags#coloured-text) page you can learn how to add color to your
game by using special markup. Colors enhance the gaming experience, but not all users want color.
Examples would be users working from clients that don't support color, or people with various seeing
disabilities that rely on screen readers to play your game. Also, whereas Evennia normally
automatically detects if a client supports color, it may get it wrong. Being able to turn it on
manually if you know it **should** work could be a nice feature.
So here's how to allow those users to remove color. It basically means you implementing a simple configuration system for your characters. This is the basic sequence:
So here's how to allow those users to remove color. It basically means you implementing a simple
configuration system for your characters. This is the basic sequence:
1. Define your own default character typeclass, inheriting from Evennia's default.
1. Set an attribute on the character to control markup on/off.
@ -15,9 +23,11 @@ So here's how to allow those users to remove color. It basically means you imple
## Setting up a custom Typeclass
Create a new module in `mygame/typeclasses` named, for example, `mycharacter.py`. Alternatively you can simply add a new class to 'mygamegame/typeclasses/characters.py'.
Create a new module in `mygame/typeclasses` named, for example, `mycharacter.py`. Alternatively you
can simply add a new class to 'mygamegame/typeclasses/characters.py'.
In your new module(or characters.py), create a new [Typeclass](Typeclasses) inheriting from `evennia.DefaultCharacter`. We will also import `evennia.utils.ansi`, which we will use later.
In your new module(or characters.py), create a new [Typeclass](Typeclasses) inheriting from
`evennia.DefaultCharacter`. We will also import `evennia.utils.ansi`, which we will use later.
```python
from evennia import Character
@ -31,21 +41,33 @@ In your new module(or characters.py), create a new [Typeclass](Typeclasses) inhe
Above we set a simple config value as an [Attribute](Attributes).
Let's make sure that new characters are created of this type. Edit your `mygame/server/conf/settings.py` file and add/change `BASE_CHARACTER_TYPECLASS` to point to your new character class. Observe that this will only affect *new* characters, not those already created. You have to convert already created characters to the new typeclass by using the `@typeclass` command (try on a secondary character first though, to test that everything works - you don't want to render your root user unusable!).
Let's make sure that new characters are created of this type. Edit your
`mygame/server/conf/settings.py` file and add/change `BASE_CHARACTER_TYPECLASS` to point to your new
character class. Observe that this will only affect *new* characters, not those already created. You
have to convert already created characters to the new typeclass by using the `@typeclass` command
(try on a secondary character first though, to test that everything works - you don't want to render
your root user unusable!).
@typeclass/reset/force Bob = mycharacter.ColorableCharacter
`@typeclass` changes Bob's typeclass and runs all its creation hooks all over again. The `/reset` switch clears all attributes and properties back to the default for the new typeclass - this is useful in this case to avoid ending up with an object having a "mixture" of properties from the old typeclass and the new one. `/force` might be needed if you edit the typeclass and want to update the object despite the actual typeclass name not having changed.
`@typeclass` changes Bob's typeclass and runs all its creation hooks all over again. The `/reset`
switch clears all attributes and properties back to the default for the new typeclass - this is
useful in this case to avoid ending up with an object having a "mixture" of properties from the old
typeclass and the new one. `/force` might be needed if you edit the typeclass and want to update the
object despite the actual typeclass name not having changed.
## Overload the `msg()` method
Next we need to overload the `msg()` method. What we want is to check the configuration value before calling the main function. The original `msg` method call is seen in `evennia/objects/objects.py` and is called like this:
Next we need to overload the `msg()` method. What we want is to check the configuration value before
calling the main function. The original `msg` method call is seen in `evennia/objects/objects.py`
and is called like this:
```python
msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
```
As long as we define a method on our custom object with the same name and keep the same number of arguments/keywords we will overload the original. Here's how it could look:
As long as we define a method on our custom object with the same name and keep the same number of
arguments/keywords we will overload the original. Here's how it could look:
```python
class ColorableCharacter(Character):
@ -61,17 +83,24 @@ As long as we define a method on our custom object with the same name and keep t
session=session, **kwargs)
```
Above we create a custom version of the `msg()` method. If the configuration Attribute is set, it strips the ANSI from the text it is about to send, and then calls the parent `msg()` as usual. You need to `@reload` before your changes become visible.
Above we create a custom version of the `msg()` method. If the configuration Attribute is set, it
strips the ANSI from the text it is about to send, and then calls the parent `msg()` as usual. You
need to `@reload` before your changes become visible.
There we go! Just flip the attribute `config_color` to False and your users will not see any color. As superuser (assuming you use the Typeclass `ColorableCharacter`) you can test this with the `@py` command:
There we go! Just flip the attribute `config_color` to False and your users will not see any color.
As superuser (assuming you use the Typeclass `ColorableCharacter`) you can test this with the `@py`
command:
@py self.db.config_color = False
## Custom color config command
For completeness, let's add a custom command so users can turn off their color display themselves if they want.
For completeness, let's add a custom command so users can turn off their color display themselves if
they want.
In `mygame/commands`, create a new file, call it for example `configcmds.py` (it's likely that you'll want to add other commands for configuration down the line). You can also copy/rename the command template.
In `mygame/commands`, create a new file, call it for example `configcmds.py` (it's likely that
you'll want to add other commands for configuration down the line). You can also copy/rename the
command template.
```python
from evennia import Command
@ -106,7 +135,9 @@ In `mygame/commands`, create a new file, call it for example `configcmds.py` (it
self.caller.msg("Color was turned off.")
```
Lastly, we make this command available to the user by adding it to the default `CharacterCmdSet` in `mygame/commands/default_cmdsets.py` and reloading the server. Make sure you also import the command:
Lastly, we make this command available to the user by adding it to the default `CharacterCmdSet` in
`mygame/commands/default_cmdsets.py` and reloading the server. Make sure you also import the
command:
```python
from mygame.commands import configcmds
@ -127,9 +158,12 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
## More colors
Apart from ANSI colors, Evennia also supports **Xterm256** colors (See [Colors](TextTags#colored-text)). The `msg()` method supports the `xterm256` keyword for manually activating/deactiving xterm256. It should be easy to expand the above example to allow players to customize xterm256 regardless of if Evennia thinks their client supports it or not.
Apart from ANSI colors, Evennia also supports **Xterm256** colors (See [Colors](TextTags#colored-
text)). The `msg()` method supports the `xterm256` keyword for manually activating/deactiving
xterm256. It should be easy to expand the above example to allow players to customize xterm256
regardless of if Evennia thinks their client supports it or not.
To get a better understanding of how `msg()` works with keywords, you can try this as superuser:
@py self.msg("|123Dark blue with xterm256, bright blue with ANSI", xterm256=True)
@py self.msg("|gThis should be uncolored", nomarkup=True)
@py self.msg("|gThis should be uncolored", nomarkup=True)

View file

@ -40,7 +40,8 @@ non-default mass would be stored on the `mass` [[Attributes]] of the objects.
You can add a `get_mass` definition to characters and rooms, also.
If you were in a one metric-ton elevator with four other friends also wearing armor and carrying gold bricks, you might wonder if this elevator's going to move, and how fast.
If you were in a one metric-ton elevator with four other friends also wearing armor and carrying
gold bricks, you might wonder if this elevator's going to move, and how fast.
Assuming the unit is grams and the elevator itself weights 1,000 kilograms, it would already be
`@set elevator/mass=1000000`, we're `@set me/mass=85000` and our armor is `@set armor/mass=50000`.
@ -92,4 +93,4 @@ class CmdInventory(MuxCommand):
string = "|wYou are carrying:\n%s" % table
self.caller.msg(string)
```
```

View file

@ -1,7 +1,10 @@
# 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 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
@ -40,47 +43,72 @@ depends on the [Protocol](Custom-Protocols) used:
### 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.
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*:
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:
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.
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](Sessions). Data and Session are passed to the server-side `SessionHandler.data_in`. This in turn calls `ServerSession.data_in()`
On the Server side, the AMP unpickles the data and associates the session id with the server-side
[Session](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).
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.
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:
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.
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](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.
The [Inputfunc](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.
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
@ -109,7 +137,11 @@ The call sign of the `msg` method looks like this:
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
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
@ -117,39 +149,63 @@ For our purposes, what is important to know is that with the exception of `from_
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 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.
> 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 ...
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):
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 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.
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.
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>`.
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.
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.
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.
Once arrived at the client, the outputcommand is handled in the way supported by the client (or it
may be quietly ignored if not). "text" commands will be displayed in the main window while others
may trigger changes in the GUI or play a sound etc.

View file

@ -1,13 +1,17 @@
# MonitorHandler
The *MonitorHandler* is a system for watching changes in properties or Attributes on objects. A monitor can be thought of as a sort of trigger that responds to change.
The *MonitorHandler* is a system for watching changes in properties or Attributes on objects. A
monitor can be thought of as a sort of trigger that responds to change.
The main use for the MonitorHandler is to report changes to the client; for example the client Session may ask Evennia to monitor the value of the Characer's `health` attribute and report whenever it changes. This way the client could for example update its health bar graphic as needed.
The main use for the MonitorHandler is to report changes to the client; for example the client
Session may ask Evennia to monitor the value of the Characer's `health` attribute and report
whenever it changes. This way the client could for example update its health bar graphic as needed.
## Using the MonitorHandler
The MontorHandler is accessed from the singleton `evennia.MONITOR_HANDLER`. The code for the handler is in `evennia.scripts.monitorhandler`.
The MontorHandler is accessed from the singleton `evennia.MONITOR_HANDLER`. The code for the handler
is in `evennia.scripts.monitorhandler`.
Here's how to add a new monitor:
@ -19,10 +23,19 @@ MONITOR_HANDLER.add(obj, fieldname, callback,
```
- `obj` ([Typeclassed](Typeclasses) entity) - the object to monitor. Since this must be typeclassed, it means you can't monitor changes on [Sessions](Sessions) with the monitorhandler, for example.
- `fieldname` (str) - the name of a field or [Attribute](Attributes) on `obj`. If you want to monitor a database field you must specify its full name, including the starting `db_` (like `db_key`, `db_location` etc). Any names not starting with `db_` are instead assumed to be the names of Attributes. This difference matters, since the MonitorHandler will automatically know to watch the `db_value` field of the Attribute.
- `callback`(callable) - This will be called as `callback(fieldname=fieldname, obj=obj, **kwargs)` when the field updates.
- `idstring` (str) - this is used to separate multiple monitors on the same object and fieldname. This is required in order to properly identify and remove the monitor later. It's also used for saving it.
- `obj` ([Typeclassed](Typeclasses) entity) - the object to monitor. Since this must be
typeclassed, it means you can't monitor changes on [Sessions](Sessions) with the monitorhandler, for
example.
- `fieldname` (str) - the name of a field or [Attribute](Attributes) on `obj`. If you want to
monitor a database field you must specify its full name, including the starting `db_` (like
`db_key`, `db_location` etc). Any names not starting with `db_` are instead assumed to be the names
of Attributes. This difference matters, since the MonitorHandler will automatically know to watch
the `db_value` field of the Attribute.
- `callback`(callable) - This will be called as `callback(fieldname=fieldname, obj=obj, **kwargs)`
when the field updates.
- `idstring` (str) - this is used to separate multiple monitors on the same object and fieldname.
This is required in order to properly identify and remove the monitor later. It's also used for
saving it.
- `persistent` (bool) - if True, the monitor will survive a server reboot.
Example:
@ -52,12 +65,15 @@ monitorhandler.add(obj, "db_key", _some_other_monitor_callback, id_string="bar")
```
A monitor is uniquely identified by the combination of the *object instance* it is monitoring, the *name* of the field/attribute to monitor on that object and its `idstring` (`obj` + `fieldname` + `idstring`). The `idstring` will be the empty string unless given explicitly.
A monitor is uniquely identified by the combination of the *object instance* it is monitoring, the
*name* of the field/attribute to monitor on that object and its `idstring` (`obj` + `fieldname` +
`idstring`). The `idstring` will be the empty string unless given explicitly.
So to "un-monitor" the above you need to supply enough information for the system to uniquely find the monitor to remove:
So to "un-monitor" the above you need to supply enough information for the system to uniquely find
the monitor to remove:
```
monitorhandler.remove(obj, "desc")
monitorhandler.remove(obj, "db_key", idstring="foo")
monitorhandler.remove(obj, "db_key", idstring="bar")
```
```

View file

@ -1,23 +1,34 @@
# NPC shop Tutorial
This tutorial will describe how to make an NPC-run shop. We will make use of the [EvMenu](EvMenu) system to present shoppers with a menu where they can buy things from the store's stock.
This tutorial will describe how to make an NPC-run shop. We will make use of the [EvMenu](EvMenu)
system to present shoppers with a menu where they can buy things from the store's stock.
Our shop extends over two rooms - a "front" room open to the shop's customers and a locked "store room" holding the wares the shop should be able to sell. We aim for the following features:
Our shop extends over two rooms - a "front" room open to the shop's customers and a locked "store
room" holding the wares the shop should be able to sell. We aim for the following features:
- The front room should have an Attribute `storeroom` that points to the store room.
- Inside the front room, the customer should have a command `buy` or `browse`. This will open a menu listing all items available to buy from the store room.
- Inside the front room, the customer should have a command `buy` or `browse`. This will open a
menu listing all items available to buy from the store room.
- A customer should be able to look at individual items before buying.
- We use "gold" as an example currency. To determine cost, the system will look for an Attribute `gold_value` on the items in the store room. If not found, a fixed base value of 1 will be assumed. The wealth of the customer should be set as an Attribute `gold` on the Character. If not set, they have no gold and can't buy anything.
- When the customer makes a purchase, the system will check the `gold_value` of the goods and compare it to the `gold` Attribute of the customer. If enough gold is available, this will be deducted and the goods transferred from the store room to the inventory of the customer.
- We use "gold" as an example currency. To determine cost, the system will look for an Attribute
`gold_value` on the items in the store room. If not found, a fixed base value of 1 will be assumed.
The wealth of the customer should be set as an Attribute `gold` on the Character. If not set, they
have no gold and can't buy anything.
- When the customer makes a purchase, the system will check the `gold_value` of the goods and
compare it to the `gold` Attribute of the customer. If enough gold is available, this will be
deducted and the goods transferred from the store room to the inventory of the customer.
- We will lock the store room so that only people with the right key can get in there.
### The shop menu
We want to show a menu to the customer where they can list, examine and buy items in the store. This menu should change depending on what is currently for sale. Evennia's *EvMenu* utility will manage the menu for us. It's a good idea to [read up on EvMenu](EvMenu) if you are not familiar with it.
We want to show a menu to the customer where they can list, examine and buy items in the store. This
menu should change depending on what is currently for sale. Evennia's *EvMenu* utility will manage
the menu for us. It's a good idea to [read up on EvMenu](EvMenu) if you are not familiar with it.
#### Designing the menu
The shopping menu's design is straightforward. First we want the main screen. You get this when you enter a shop and use the `browse` or `buy` command:
The shopping menu's design is straightforward. First we want the main screen. You get this when you
enter a shop and use the `browse` or `buy` command:
```
*** Welcome to ye Old Sword shop! ***
@ -28,7 +39,9 @@ _________________________________________________________
3. Excalibur (100 gold)
```
There are only three items to buy in this example but the menu should expand to however many items are needed. When you make a selection you will get a new screen showing the options for that particular item:
There are only three items to buy in this example but the menu should expand to however many items
are needed. When you make a selection you will get a new screen showing the options for that
particular item:
```
You inspect A rusty sword:
@ -53,7 +66,10 @@ After this you should be back to the top level of the shopping menu again and ca
#### Coding the menu
EvMenu defines the *nodes* (each menu screen with options) as normal Python functions. Each node must be able to change on the fly depending on what items are currently for sale. EvMenu will automatically make the `quit` command available to us so we won't add that manually. For compactness we will put everything needed for our shop in one module, `mygame/typeclasses/npcshop.py`.
EvMenu defines the *nodes* (each menu screen with options) as normal Python functions. Each node
must be able to change on the fly depending on what items are currently for sale. EvMenu will
automatically make the `quit` command available to us so we won't add that manually. For compactness
we will put everything needed for our shop in one module, `mygame/typeclasses/npcshop.py`.
```python
# mygame/typeclasses/npcshop.py
@ -86,9 +102,14 @@ def menunode_shopfront(caller):
return text, options
```
In this code we assume the caller to be *inside* the shop when accessing the menu. This means we can access the shop room via `caller.location` and get its `key` to display as the shop's name. We also assume the shop has an Attribute `storeroom` we can use to get to our stock. We loop over our goods to build up the menu's options.
In this code we assume the caller to be *inside* the shop when accessing the menu. This means we can
access the shop room via `caller.location` and get its `key` to display as the shop's name. We also
assume the shop has an Attribute `storeroom` we can use to get to our stock. We loop over our goods
to build up the menu's options.
Note that *all options point to the same menu node* called `menunode_inspect_and_buy`! We can't know which goods will be available to sale so we rely on this node to modify itself depending on the circumstances. Let's create it now.
Note that *all options point to the same menu node* called `menunode_inspect_and_buy`! We can't know
which goods will be available to sale so we rely on this node to modify itself depending on the
circumstances. Let's create it now.
```python
# further down in mygame/typeclasses/npcshop.py
@ -127,15 +148,27 @@ def menunode_inspect_and_buy(caller, raw_string):
return text, options
```
In this menu node we make use of the `raw_string` argument to the node. This is the text the menu user entered on the *previous* node to get here. Since we only allow numbered options in our menu, `raw_input` must be an number for the player to get to this point. So we convert it to an integer index (menu lists start from 1, whereas Python indices always starts at 0, so we need to subtract 1). We then use the index to get the corresponding item from storage.
In this menu node we make use of the `raw_string` argument to the node. This is the text the menu
user entered on the *previous* node to get here. Since we only allow numbered options in our menu,
`raw_input` must be an number for the player to get to this point. So we convert it to an integer
index (menu lists start from 1, whereas Python indices always starts at 0, so we need to subtract
1). We then use the index to get the corresponding item from storage.
We just show the customer the `desc` of the item. In a more elaborate setup you might want to show things like weapon damage and special stats here as well.
We just show the customer the `desc` of the item. In a more elaborate setup you might want to show
things like weapon damage and special stats here as well.
When the user choose the "buy" option, EvMenu will execute the `exec` instruction *before* we go back to the top node (the `goto` instruction). For this we make a little inline function `buy_ware_result`. EvMenu will call the function given to `exec` like any menu node but it does not need to return anything. In `buy_ware_result` we determine if the customer can afford the cost and give proper return messages. This is also where we actually move the bought item into the inventory of the customer.
When the user choose the "buy" option, EvMenu will execute the `exec` instruction *before* we go
back to the top node (the `goto` instruction). For this we make a little inline function
`buy_ware_result`. EvMenu will call the function given to `exec` like any menu node but it does not
need to return anything. In `buy_ware_result` we determine if the customer can afford the cost and
give proper return messages. This is also where we actually move the bought item into the inventory
of the customer.
#### The command to start the menu
We could *in principle* launch the shopping menu the moment a customer steps into our shop room, but this would probably be considered pretty annoying. It's better to create a [Command](Commands) for customers to explicitly wanting to shop around.
We could *in principle* launch the shopping menu the moment a customer steps into our shop room, but
this would probably be considered pretty annoying. It's better to create a [Command](Commands) for
customers to explicitly wanting to shop around.
```python
# mygame/typeclasses/npcshop.py
@ -164,7 +197,10 @@ class CmdBuy(Command):
startnode="menunode_shopfront")
```
This will launch the menu. The `EvMenu` instance is initialized with the path to this very module - since the only global functions available in this module are our menu nodes, this will work fine (you could also have put those in a separate module). We now just need to put this command in a [CmdSet](Command-Sets) so we can add it correctly to the game:
This will launch the menu. The `EvMenu` instance is initialized with the path to this very module -
since the only global functions available in this module are our menu nodes, this will work fine
(you could also have put those in a separate module). We now just need to put this command in a
[CmdSet](Command-Sets) so we can add it correctly to the game:
```python
from evennia import CmdSet
@ -179,9 +215,13 @@ class ShopCmdSet(CmdSet):
There are really only two things that separate our shop from any other Room:
- The shop has the `storeroom` Attribute set on it, pointing to a second (completely normal) room.
- It has the `ShopCmdSet` stored on itself. This makes the `buy` command available to users entering the shop.
- It has the `ShopCmdSet` stored on itself. This makes the `buy` command available to users entering
the shop.
For testing we could easily add these features manually to a room using `@py` or other admin commands. Just to show how it can be done we'll instead make a custom [Typeclass](Typeclasses) for the shop room and make a small command that builders can use to build both the shop and the storeroom at once.
For testing we could easily add these features manually to a room using `@py` or other admin
commands. Just to show how it can be done we'll instead make a custom [Typeclass](Typeclasses) for
the shop room and make a small command that builders can use to build both the shop and the
storeroom at once.
```python
# bottom of mygame/typeclasses/npcshop.py
@ -252,22 +292,43 @@ class CmdBuildShop(Command):
self.caller.msg("The shop %s was created!" % shop)
```
Our typeclass is simple and so is our `buildshop` command. The command (which is for Builders only) just takes the name of the shop and builds the front room and a store room to go with it (always named `"<shopname>-storage"`. It connects the rooms with a two-way exit. You need to add `CmdBuildShop` [to the default cmdset](Adding-Command-Tutorial#step-2-adding-the-command-to-a-default-cmdset) before you can use it. Once having created the shop you can now `@teleport` to it or `@open` a new exit to it. You could also easily expand the above command to automatically create exits to and from the new shop from your current location.
Our typeclass is simple and so is our `buildshop` command. The command (which is for Builders only)
just takes the name of the shop and builds the front room and a store room to go with it (always
named `"<shopname>-storage"`. It connects the rooms with a two-way exit. You need to add
`CmdBuildShop` [to the default cmdset](Adding-Command-Tutorial#step-2-adding-the-command-to-a-
default-cmdset) before you can use it. Once having created the shop you can now `@teleport` to it or
`@open` a new exit to it. You could also easily expand the above command to automatically create
exits to and from the new shop from your current location.
To avoid customers walking in and stealing everything, we create a [Lock](Locks) on the storage door. It's a simple lock that requires the one entering to carry an object named `<shopname>-storekey`. We even create such a key object and drop it in the shop for the new shop keeper to pick up.
To avoid customers walking in and stealing everything, we create a [Lock](Locks) on the storage
door. It's a simple lock that requires the one entering to carry an object named
`<shopname>-storekey`. We even create such a key object and drop it in the shop for the new shop
keeper to pick up.
> If players are given the right to name their own objects, this simple lock is not very secure and you need to come up with a more robust lock-key solution.
> If players are given the right to name their own objects, this simple lock is not very secure and
you need to come up with a more robust lock-key solution.
> We don't add any descriptions to all these objects so looking "at" them will not be too thrilling. You could add better default descriptions as part of the `@buildshop` command or leave descriptions this up to the Builder.
> We don't add any descriptions to all these objects so looking "at" them will not be too thrilling.
You could add better default descriptions as part of the `@buildshop` command or leave descriptions
this up to the Builder.
### The shop is open for business!
We now have a functioning shop and an easy way for Builders to create it. All you need now is to `@open` a new exit from the rest of the game into the shop and put some sell-able items in the store room. Our shop does have some shortcomings:
We now have a functioning shop and an easy way for Builders to create it. All you need now is to
`@open` a new exit from the rest of the game into the shop and put some sell-able items in the store
room. Our shop does have some shortcomings:
- For Characters to be able to buy stuff they need to also have the `gold` Attribute set on themselves.
- We manually remove the "door" exit from our items for sale. But what if there are other unsellable items in the store room? What if the shop owner walks in there for example - anyone in the store could then buy them for 1 gold.
- What if someone else were to buy the item we're looking at just before we decide to buy it? It would then be gone and the counter be wrong - the shop would pass us the next item in the list.
- For Characters to be able to buy stuff they need to also have the `gold` Attribute set on
themselves.
- We manually remove the "door" exit from our items for sale. But what if there are other unsellable
items in the store room? What if the shop owner walks in there for example - anyone in the store
could then buy them for 1 gold.
- What if someone else were to buy the item we're looking at just before we decide to buy it? It
would then be gone and the counter be wrong - the shop would pass us the next item in the list.
Fixing these issues are left as an exercise.
If you want to keep the shop fully NPC-run you could add a [Script](Scripts) to restock the shop's store room regularly. This shop example could also easily be owned by a human Player (run for them by a hired NPC) - the shop owner would get the key to the store room and be responsible for keeping it well stocked.
If you want to keep the shop fully NPC-run you could add a [Script](Scripts) to restock the shop's
store room regularly. This shop example could also easily be owned by a human Player (run for them
by a hired NPC) - the shop owner would get the key to the store room and be responsible for keeping
it well stocked.

View file

@ -2,11 +2,18 @@
*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](Tags) or [Scripts](Scripts) can handle many situations, sometimes things may be easier to handle by adding your own database model.
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](Tags) or [Scripts](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
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 ...
@ -15,7 +22,9 @@ SQL-type databases (which is what Evennia supports) are basically highly optimiz
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
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
@ -33,21 +42,34 @@ You will drop into the database shell. While there, try:
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.
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 [Getting Started](Getting-Started) first):
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 [Getting Started](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. 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):
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", )
@ -58,13 +80,20 @@ interested in for now is `models.py`. In `models.py` you define your model(s). E
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](Version-Control)), don't forget to `git add myapp/*` to add all items to version control.
This will add your new database table to the database. If you have put your game under version
control (if not, [you should](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.
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:
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
@ -80,15 +109,30 @@ class MyDataStore(models.Model):
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.
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](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.
> 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](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.
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.
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.
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
@ -105,33 +149,46 @@ To create a new row in your table, you instantiate the model and then call its `
```
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).
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:
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.
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:
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:
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.
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:
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")
@ -145,13 +202,30 @@ And then later:
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.
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.
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.
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`:
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
@ -168,7 +242,9 @@ class MyDataStore(SharedMemoryModel):
## 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`).
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
@ -184,4 +260,5 @@ To search your new custom database table you need to use its database *manager*
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.
See the [Django query documentation](https://docs.djangoproject.com/en/2.2/topics/db/queries/) for a
lot more information about querying the database.

View file

@ -1,13 +1,20 @@
# Nicks
*Nicks*, short for *Nicknames* is a system allowing an object (usually a [Account](Accounts)) to assign custom replacement names for other game entities.
*Nicks*, short for *Nicknames* is a system allowing an object (usually a [Account](Accounts)) to
assign custom replacement names for other game entities.
Nicks are not to be confused with *Aliases*. Setting an Alias on a game entity actually changes an inherent attribute on that entity, and everyone in the game will be able to use that alias to address the entity thereafter. A *Nick* on the other hand, is used to map a different way *you alone* can refer to that entity. Nicks are also commonly used to replace your input text which means you can create your own aliases to default commands.
Nicks are not to be confused with *Aliases*. Setting an Alias on a game entity actually changes an
inherent attribute on that entity, and everyone in the game will be able to use that alias to
address the entity thereafter. A *Nick* on the other hand, is used to map a different way *you
alone* can refer to that entity. Nicks are also commonly used to replace your input text which means
you can create your own aliases to default commands.
Default Evennia use Nicks in three flavours that determine when Evennia actually tries to do the substitution.
Default Evennia use Nicks in three flavours that determine when Evennia actually tries to do the
substitution.
- inputline - replacement is attempted whenever you write anything on the command line. This is the default.
- inputline - replacement is attempted whenever you write anything on the command line. This is the
default.
- objects - replacement is only attempted when referring to an object
- accounts - replacement is only attempted when referring an account
@ -15,11 +22,13 @@ Here's how to use it in the default command set (using the `nick` command):
nick ls = look
This is a good one for unix/linux users who are accustomed to using the `ls` command in their daily life. It is equivalent to `nick/inputline ls = look`.
This is a good one for unix/linux users who are accustomed to using the `ls` command in their daily
life. It is equivalent to `nick/inputline ls = look`.
nick/object mycar2 = The red sports car
With this example, substitutions will only be done specifically for commands expecting an object reference, such as
With this example, substitutions will only be done specifically for commands expecting an object
reference, such as
look mycar2
@ -31,7 +40,8 @@ This is useful for commands searching for accounts explicitly:
@find *tom
One can use nicks to speed up input. Below we add ourselves a quicker way to build red buttons. In the future just writing *rb* will be enough to execute that whole long string.
One can use nicks to speed up input. Below we add ourselves a quicker way to build red buttons. In
the future just writing *rb* will be enough to execute that whole long string.
nick rb = @create button:examples.red_button.RedButton
@ -43,9 +53,13 @@ The nick replacer also supports unix-style *templating*:
nick build $1 $2 = @create/drop $1;$2
This will catch space separated arguments and store them in the the tags `$1` and `$2`, to be inserted in the replacement string. This example allows you to do `build box crate` and have Evennia see `@create/drop box;crate`. You may use any `$` numbers between 1 and 99, but the markers must match between the nick pattern and the replacement.
This will catch space separated arguments and store them in the the tags `$1` and `$2`, to be
inserted in the replacement string. This example allows you to do `build box crate` and have Evennia
see `@create/drop box;crate`. You may use any `$` numbers between 1 and 99, but the markers must
match between the nick pattern and the replacement.
> If you want to catch "the rest" of a command argument, make sure to put a `$` tag *with no spaces to the right of it* - it will then receive everything up until the end of the line.
> If you want to catch "the rest" of a command argument, make sure to put a `$` tag *with no spaces
to the right of it* - it will then receive everything up until the end of the line.
You can also use [shell-type wildcards](http://www.linfo.org/wildcard.html):
@ -60,7 +74,9 @@ You can also use [shell-type wildcards](http://www.linfo.org/wildcard.html):
## Coding with nicks
Nicks are stored as the `Nick` database model and are referred from the normal Evennia [object](Objects) through the `nicks` property - this is known as the *NickHandler*. The NickHandler offers effective error checking, searches and conversion.
Nicks are stored as the `Nick` database model and are referred from the normal Evennia
[object](Objects) through the `nicks` property - this is known as the *NickHandler*. The NickHandler
offers effective error checking, searches and conversion.
```python
# A command/channel nick:
@ -82,15 +98,22 @@ Nicks are stored as the `Nick` database model and are referred from the normal E
object.nicks.remove("rose", nick_type="object")
```
In a command definition you can reach the nick handler through `self.caller.nicks`. See the `nick` command in `evennia/commands/default/general.py` for more examples.
In a command definition you can reach the nick handler through `self.caller.nicks`. See the `nick`
command in `evennia/commands/default/general.py` for more examples.
As a last note, The Evennia [channel](Communications) alias systems are using nicks with the `nick_type="channel"` in order to allow users to create their own custom aliases to channels.
As a last note, The Evennia [channel](Communications) alias systems are using nicks with the
`nick_type="channel"` in order to allow users to create their own custom aliases to channels.
# Advanced note
Internally, nicks are [Attributes](Attributes) saved with the `db_attrype` set to "nick" (normal Attributes has this set to `None`).
Internally, nicks are [Attributes](Attributes) saved with the `db_attrype` set to "nick" (normal
Attributes has this set to `None`).
The nick stores the replacement data in the Attribute.db_value field as a tuple with four fields `(regex_nick, template_string, raw_nick, raw_template)`. Here `regex_nick` is the converted regex representation of the `raw_nick` and the `template-string` is a version of the `raw_template` prepared for efficient replacement of any `$`- type markers. The `raw_nick` and `raw_template` are basically the unchanged strings you enter to the `nick` command (with unparsed `$` etc).
The nick stores the replacement data in the Attribute.db_value field as a tuple with four fields
`(regex_nick, template_string, raw_nick, raw_template)`. Here `regex_nick` is the converted regex
representation of the `raw_nick` and the `template-string` is a version of the `raw_template`
prepared for efficient replacement of any `$`- type markers. The `raw_nick` and `raw_template` are
basically the unchanged strings you enter to the `nick` command (with unparsed `$` etc).
If you need to access the tuple for some reason, here's how:

View file

@ -1,39 +1,66 @@
# 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.
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:
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](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](Inputfuncs) page for more details).
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](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](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).
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:
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:
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`).
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.
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.
> 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](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).
[Inputfuncs](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
@ -41,26 +68,41 @@ Evennia supports clients using one of the following protocols:
### Telnet
By default telnet (and telnet+SSL) supports only the plain `text` outputcommand. Evennia however detects if the Client supports one of two MUD-specific OOB *extensions* to the standard telnet protocol - GMCP or MSDP. Evennia supports both simultaneously and will switch to the protocol the client uses. If the client supports both, GMCP will be used.
By default telnet (and telnet+SSL) supports only the plain `text` outputcommand. Evennia 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.
> 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:
[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.
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.
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
------------------
@ -70,7 +112,8 @@ Input/Outputfunc | GMCP-Command
`[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:
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
-----------------
@ -83,13 +126,21 @@ GMCP command name | Input/Outputfunc name
#### Telnet + MSDP
[MSDP](http://tintin.sourceforge.net/msdp/), the *Mud Server Data Protocol*, is a competing standard to GMCP. The MSDP protocol page specifies a range of "recommended" available MSDP command names. Evennia does *not* support those - since MSDP doesn't specify a special format for its command names (like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual name.
[MSDP](http://tintin.sourceforge.net/msdp/), the *Mud Server Data Protocol*, is a competing standard
to GMCP. The MSDP protocol page specifies a range of "recommended" available MSDP command names.
Evennia does *not* support those - since MSDP doesn't specify a special format for its command names
(like GMCP does) the client can and should just call the internal Evennia inputfunc by its actual
name.
MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs needed. When sending MSDP for `("cmdname", ("arg",), {})` the resulting MSDP instruction will look like this:
MSDP uses Telnet character constants to package various structured data over the wire. MSDP supports
strings, arrays (lists) and tables (dicts). These are used to define the cmdname, args and kwargs
needed. When sending MSDP for `("cmdname", ("arg",), {})` the resulting MSDP instruction will look
like this:
IAC SB MSDP VAR cmdname VAL arg IAC SE
The various available MSDP constants like `VAR` (variable), `VAL` (value), `ARRAYOPEN`/`ARRAYCLOSE` and `TABLEOPEN`/`TABLECLOSE` are specified in `evennia/server/portal/telnet_oob`.
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
-------------------------
@ -97,9 +148,12 @@ Outputfunc/Inputfunc | MSDP instruction
`[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
`[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.
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
@ -107,8 +161,10 @@ SSH only supports the `text` input/outputcommand.
### Web client
Our web client uses pure JSON structures for all its communication, including `text`. This maps directly to the Evennia internal output/inputcommand, including eventual empty args/kwargs. So the same example `("cmdname", ("arg",), {})` will be sent/received as a valid JSON structure
Our web client uses pure JSON 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.
Since JSON is native to Javascript, this becomes very easy for the webclient to handle.

View file

@ -1,11 +1,17 @@
# Objects
All in-game objects in Evennia, be it characters, chairs, monsters, rooms or hand grenades are represented by an Evennia *Object*. Objects form the core of Evennia and is probably what you'll spend most time working with. Objects are [Typeclassed](Typeclasses) entities.
All in-game objects in Evennia, be it characters, chairs, monsters, rooms or hand grenades are
represented by an Evennia *Object*. Objects form the core of Evennia and is probably what you'll
spend most time working with. Objects are [Typeclassed](Typeclasses) entities.
## How to create your own object types
An Evennia Object is, per definition, a Python class that includes `evennia.DefaultObject` among its parents. In `mygame/typeclasses/objects.py` there is already a class `Object` that inherits from `DefaultObject` and that you can inherit from. You can put your new typeclass directly in that module or you could organize your code in some other way. Here we assume we make a new module `mygame/typeclasses/flowers.py`:
An Evennia Object is, per definition, a Python class that includes `evennia.DefaultObject` among its
parents. In `mygame/typeclasses/objects.py` there is already a class `Object` that inherits from
`DefaultObject` and that you can inherit from. You can put your new typeclass directly in that
module or you could organize your code in some other way. Here we assume we make a new module
`mygame/typeclasses/flowers.py`:
```python
# mygame/typeclasses/flowers.py
@ -23,58 +29,100 @@ An Evennia Object is, per definition, a Python class that includes `evennia.Defa
self.db.desc = "This is a pretty rose with thorns."
```
You could save this in the `mygame/typeclasses/objects.py` (then you'd not need to import `Object`) or you can put it in a new module. Let's say we do the latter, making a module `typeclasses/flowers.py`. Now you just need to point to the class *Rose* with the `@create` command to make a new rose:
You could save this in the `mygame/typeclasses/objects.py` (then you'd not need to import `Object`)
or you can put it in a new module. Let's say we do the latter, making a module
`typeclasses/flowers.py`. Now you just need to point to the class *Rose* with the `@create` command
to make a new rose:
@create/drop MyRose:flowers.Rose
What the `@create` command actually *does* is to use `evennia.create_object`. You can do the same thing yourself in code:
What the `@create` command actually *does* is to use `evennia.create_object`. You can do the same
thing yourself in code:
```python
from evennia import create_object
new_rose = create_object("typeclasses.flowers.Rose", key="MyRose")
```
(The `@create` command will auto-append the most likely path to your typeclass, if you enter the call manually you have to give the full path to the class. The `create.create_object` function is powerful and should be used for all coded object creating (so this is what you use when defining your own building commands). Check out the `ev.create_*` functions for how to build other entities like [Scripts](Scripts)).
(The `@create` command will auto-append the most likely path to your typeclass, if you enter the
call manually you have to give the full path to the class. The `create.create_object` function is
powerful and should be used for all coded object creating (so this is what you use when defining
your own building commands). Check out the `ev.create_*` functions for how to build other entities
like [Scripts](Scripts)).
This particular Rose class doesn't really do much, all it does it make sure the attribute `desc`(which is what the `look` command looks for) is pre-set, which is pretty pointless since you will usually want to change this at build time (using the `@desc` command or using the [Spawner](Spawner-and-Prototypes)). The `Object` typeclass offers many more hooks that is available to use though - see next section.
This particular Rose class doesn't really do much, all it does it make sure the attribute
`desc`(which is what the `look` command looks for) is pre-set, which is pretty pointless since you
will usually want to change this at build time (using the `@desc` command or using the
[Spawner](Spawner-and-Prototypes)). The `Object` typeclass offers many more hooks that is available
to use though - see next section.
## Properties and functions on Objects
Beyond the properties assigned to all [typeclassed](Typeclasses) objects (see that page for a list of those), the Object also has the following custom properties:
Beyond the properties assigned to all [typeclassed](Typeclasses) objects (see that page for a list
of those), the Object also has the following custom properties:
- `aliases` - a handler that allows you to add and remove aliases from this object. Use `aliases.add()` to add a new alias and `aliases.remove()` to remove one.
- `aliases` - a handler that allows you to add and remove aliases from this object. Use
`aliases.add()` to add a new alias and `aliases.remove()` to remove one.
- `location` - a reference to the object currently containing this object.
- `home` is a backup location. The main motivation is to have a safe place to move the object to if its `location` is destroyed. All objects should usually have a home location for safety.
- `destination` - this holds a reference to another object this object links to in some way. Its main use is for [Exits](Objects#Exits), it's otherwise usually unset.
- `nicks` - as opposed to aliases, a [Nick](Nicks) holds a convenient nickname replacement for a real name, word or sequence, only valid for this object. This mainly makes sense if the Object is used as a game character - it can then store briefer shorts, example so as to quickly reference game commands or other characters. Use nicks.add(alias, realname) to add a new one.
- `account` - this holds a reference to a connected [Account](Accounts) controlling this object (if any). Note that this is set also if the controlling account is *not* currently online - to test if an account is online, use the `has_account` property instead.
- `sessions` - if `account` field is set *and the account is online*, this is a list of all active sessions (server connections) to contact them through (it may be more than one if multiple connections are allowed in settings).
- `has_account` - a shorthand for checking if an *online* account is currently connected to this object.
- `contents` - this returns a list referencing all objects 'inside' this object (i,e. which has this object set as their `location`).
- `exits` - this returns all objects inside this object that are *Exits*, that is, has the `destination` property set.
- `home` is a backup location. The main motivation is to have a safe place to move the object to if
its `location` is destroyed. All objects should usually have a home location for safety.
- `destination` - this holds a reference to another object this object links to in some way. Its
main use is for [Exits](Objects#Exits), it's otherwise usually unset.
- `nicks` - as opposed to aliases, a [Nick](Nicks) holds a convenient nickname replacement for a
real name, word or sequence, only valid for this object. This mainly makes sense if the Object is
used as a game character - it can then store briefer shorts, example so as to quickly reference game
commands or other characters. Use nicks.add(alias, realname) to add a new one.
- `account` - this holds a reference to a connected [Account](Accounts) controlling this object (if
any). Note that this is set also if the controlling account is *not* currently online - to test if
an account is online, use the `has_account` property instead.
- `sessions` - if `account` field is set *and the account is online*, this is a list of all active
sessions (server connections) to contact them through (it may be more than one if multiple
connections are allowed in settings).
- `has_account` - a shorthand for checking if an *online* account is currently connected to this
object.
- `contents` - this returns a list referencing all objects 'inside' this object (i,e. which has this
object set as their `location`).
- `exits` - this returns all objects inside this object that are *Exits*, that is, has the
`destination` property set.
The last two properties are special:
- `cmdset` - this is a handler that stores all [command sets](Commands#Command_Sets) defined on the object (if any).
- `cmdset` - this is a handler that stores all [command sets](Commands#Command_Sets) defined on the
object (if any).
- `scripts` - this is a handler that manages [Scripts](Scripts) attached to the object (if any).
The Object also has a host of useful utility functions. See the function headers in `src/objects/objects.py` for their arguments and more details.
The Object also has a host of useful utility functions. See the function headers in
`src/objects/objects.py` for their arguments and more details.
- `msg()` - this function is used to send messages from the server to an account connected to this object.
- `msg()` - this function is used to send messages from the server to an account connected to this
object.
- `msg_contents()` - calls `msg` on all objects inside this object.
- `search()` - this is a convenient shorthand to search for a specific object, at a given location or globally. It's mainly useful when defining commands (in which case the object executing the command is named `caller` and one can do `caller.search()` to find objects in the room to operate on).
- `search()` - this is a convenient shorthand to search for a specific object, at a given location
or globally. It's mainly useful when defining commands (in which case the object executing the
command is named `caller` and one can do `caller.search()` to find objects in the room to operate
on).
- `execute_cmd()` - Lets the object execute the given string as if it was given on the command line.
- `move_to` - perform a full move of this object to a new location. This is the main move method and will call all relevant hooks, do all checks etc.
- `move_to` - perform a full move of this object to a new location. This is the main move method
and will call all relevant hooks, do all checks etc.
- `clear_exits()` - will delete all [Exits](Objects#Exits) to *and* from this object.
- `clear_contents()` - this will not delete anything, but rather move all contents (except Exits) to their designated `Home` locations.
- `clear_contents()` - this will not delete anything, but rather move all contents (except Exits) to
their designated `Home` locations.
- `delete()` - deletes this object, first calling `clear_exits()` and
`clear_contents()`.
The Object Typeclass defines many more *hook methods* beyond `at_object_creation`. Evennia calls these hooks at various points. When implementing your custom objects, you will inherit from the base parent and overload these hooks with your own custom code. See `evennia.objects.objects` for an updated list of all the available hooks or the [API for DefaultObject here](api:evennia.objects.objects#defaultobject).
The Object Typeclass defines many more *hook methods* beyond `at_object_creation`. Evennia calls
these hooks at various points. When implementing your custom objects, you will inherit from the
base parent and overload these hooks with your own custom code. See `evennia.objects.objects` for an
updated list of all the available hooks or the [API for DefaultObject
here](api:evennia.objects.objects#defaultobject).
## Subclasses of `Object`
There are three special subclasses of *Object* in default Evennia - *Characters*, *Rooms* and *Exits*. The reason they are separated is because these particular object types are fundamental, something you will always need and in some cases requires some extra attention in order to be recognized by the game engine (there is nothing stopping you from redefining them though). In practice they are all pretty similar to the base Object.
There are three special subclasses of *Object* in default Evennia - *Characters*, *Rooms* and
*Exits*. The reason they are separated is because these particular object types are fundamental,
something you will always need and in some cases requires some extra attention in order to be
recognized by the game engine (there is nothing stopping you from redefining them though). In
practice they are all pretty similar to the base Object.
### Characters
@ -90,20 +138,39 @@ to modify.
### Rooms
*Rooms* are the root containers of all other objects. The only thing really separating a room from any other object is that they have no `location` of their own and that default commands like `@dig` creates objects of this class - so if you want to expand your rooms with more functionality, just inherit from `ev.DefaultRoom`. In `mygame/typeclasses/rooms.py` is an empty `Room` class ready for you to modify.
*Rooms* are the root containers of all other objects. The only thing really separating a room from
any other object is that they have no `location` of their own and that default commands like `@dig`
creates objects of this class - so if you want to expand your rooms with more functionality, just
inherit from `ev.DefaultRoom`. In `mygame/typeclasses/rooms.py` is an empty `Room` class ready for
you to modify.
### Exits
*Exits* are objects connecting other objects (usually *Rooms*) together. An object named *North* or *in* might be an exit, as well as *door*, *portal* or *jump out the window*. An exit has two things that separate them from other objects. Firstly, their *destination* property is set and points to a valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits define a special [Transit Command](Commands) on themselves when they are created. This command is named the same as the exit object and will, when called, handle the practicalities of moving the character to the Exits's *destination* - this allows you to just enter the name of the exit on its own to move around, just as you would expect.
*Exits* are objects connecting other objects (usually *Rooms*) together. An object named *North* or
*in* might be an exit, as well as *door*, *portal* or *jump out the window*. An exit has two things
that separate them from other objects. Firstly, their *destination* property is set and points to a
valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits
define a special [Transit Command](Commands) on themselves when they are created. This command is
named the same as the exit object and will, when called, handle the practicalities of moving the
character to the Exits's *destination* - this allows you to just enter the name of the exit on its
own to move around, just as you would expect.
The exit functionality is all defined on the Exit typeclass, so you could in principle completely change how exits work in your game (it's not recommended though, unless you really know what you are doing). Exits are [locked](Locks) using an access_type called *traverse* and also make use of a few hook methods for giving feedback if the traversal fails. See `evennia.DefaultExit` for more info. In `mygame/typeclasses/exits.py` there is an empty `Exit` class for you to modify.
The exit functionality is all defined on the Exit typeclass, so you could in principle completely
change how exits work in your game (it's not recommended though, unless you really know what you are
doing). Exits are [locked](Locks) using an access_type called *traverse* and also make use of a few
hook methods for giving feedback if the traversal fails. See `evennia.DefaultExit` for more info.
In `mygame/typeclasses/exits.py` there is an empty `Exit` class for you to modify.
The process of traversing an exit is as follows:
1. The traversing `obj` sends a command that matches the Exit-command name on the Exit object. The [cmdhandler](Commands) detects this and triggers the command defined on the Exit. Traversal always involves the "source" (the current location) and the `destination` (this is stored on the Exit object).
1. The traversing `obj` sends a command that matches the Exit-command name on the Exit object. The
[cmdhandler](Commands) detects this and triggers the command defined on the Exit. Traversal always
involves the "source" (the current location) and the `destination` (this is stored on the Exit
object).
1. The Exit command checks the `traverse` lock on the Exit object
1. The Exit command triggers `at_traverse(obj, destination)` on the Exit object.
1. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks, in order:
1. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks,
in order:
1. `obj.at_before_move(destination)` - if this returns False, move is aborted.
1. `origin.at_before_leave(obj, destination)`
1. `obj.announce_move_from(destination)`
@ -113,5 +180,6 @@ The process of traversing an exit is as follows:
1. `obj.at_after_move(source)`
1. On the Exit object, `at_after_traverse(obj, source)` is triggered.
If the move fails for whatever reason, the Exit will look for an Attribute `err_traverse` on itself and display this as an error message. If this is not found, the Exit will instead call
`at_failed_traverse(obj)` on itself.
If the move fails for whatever reason, the Exit will look for an Attribute `err_traverse` on itself
and display this as an error message. If this is not found, the Exit will instead call
`at_failed_traverse(obj)` on itself.

View file

@ -1,36 +1,77 @@
# Online Setup
Evennia development can be made without any Internet connection beyond fetching updates. At some point however, you are likely to want to make your game visible online, either as part opening it to the public or to allow other developers or beta testers access to it.
Evennia development can be made without any Internet connection beyond fetching updates. At some
point however, you are likely to want to make your game visible online, either as part opening it to
the public or to allow other developers or beta testers access to it.
## Connecting from the outside
Accessing your Evennia server from the outside is not hard on its own. Any issues are usually due to the various security measures your computer, network or hosting service has. These will generally (and correctly) block outside access to servers on your machine unless you tell them otherwise.
Accessing your Evennia server from the outside is not hard on its own. Any issues are usually due to
the various security measures your computer, network or hosting service has. These will generally
(and correctly) block outside access to servers on your machine unless you tell them otherwise.
We will start by showing how to host your server on your own local computer. Even if you plan to host your "real" game on a remote host later, setting it up locally is useful practice. We cover remote hosting later in this document.
We will start by showing how to host your server on your own local computer. Even if you plan to
host your "real" game on a remote host later, setting it up locally is useful practice. We cover
remote hosting later in this document.
Out of the box, Evennia uses three ports for outward communication. If your computer has a firewall, these should be open for in/out communication (and only these, other ports used by Evennia are internal to your computer only).
Out of the box, Evennia uses three ports for outward communication. If your computer has a firewall,
these should be open for in/out communication (and only these, other ports used by Evennia are
internal to your computer only).
- `4000`, telnet, for traditional mud clients
- `4001`, HTTP for the website)
- `4002`, websocket, for the web client
Evennia will by default accept incoming connections on all interfaces (`0.0.0.0`) so in principle anyone knowing the ports to use and has the IP address to your machine should be able to connect to your game.
Evennia will by default accept incoming connections on all interfaces (`0.0.0.0`) so in principle
anyone knowing the ports to use and has the IP address to your machine should be able to connect to
your game.
- Make sure Evennia is installed and that you have activated the virtualenv. Start the server with `evennia start --log`. The `--log` (or `-l`) will make sure that the logs are echoed to the terminal.
> Note: If you need to close the log-view, use `Ctrl-C`. Use just `evennia --log` on its own to start tailing the logs again.
- Make sure you can connect with your web browser to `http://localhost:4001` or, alternatively, `http:127.0.0.1:4001` which is the same thing. You should get your Evennia web site and be able to play the game in the web client. Also check so that you can connect with a mud client to host `localhost`, port `4000` or host `127.0.0.1`, port `4000`.
- [Google for "my ip"](https://www.google.se/search?q=my+ip) or use any online service to figure out what your "outward-facing" IP address is. For our purposes, let's say your outward-facing IP is `203.0.113.0`.
- Next try your outward-facing IP by opening `http://203.0.113.0:4001` in a browser. If this works, that's it! Also try telnet, with the server set to `203.0.113.0` and port `4000`. However, most likely it will *not* work. If so, read on.
- If your computer has a firewall, it may be blocking the ports we need (it may also block telnet overall). If so, you need to open the outward-facing ports to in/out communication. See the manual/instructions for your firewall software on how to do this. To test you could also temporarily turn off your firewall entirely to see if that was indeed the problem.
- Another common problem for not being able to connect is that you are using a hardware router (like a wifi router). The router sits 'between' your computer and the Internet. So the IP you find with Google is the *router's* IP, not that of your computer. To resolve this you need to configure your router to *forward* data it gets on its ports to the IP and ports of your computer sitting in your private network. How to do this depends on the make of your router; you usually configure it using a normal web browser. In the router interface, look for "Port forwarding" or maybe "Virtual server". If that doesn't work, try to temporarily wire your computer directly to the Internet outlet (assuming your computer has the ports for it). You'll need to check for your IP again. If that works, you know the problem is the router.
- Make sure Evennia is installed and that you have activated the virtualenv. Start the server with
`evennia start --log`. The `--log` (or `-l`) will make sure that the logs are echoed to the
terminal.
> Note: If you need to close the log-view, use `Ctrl-C`. Use just `evennia --log` on its own to
start tailing the logs again.
- Make sure you can connect with your web browser to `http://localhost:4001` or, alternatively,
`http:127.0.0.1:4001` which is the same thing. You should get your Evennia web site and be able to
play the game in the web client. Also check so that you can connect with a mud client to host
`localhost`, port `4000` or host `127.0.0.1`, port `4000`.
- [Google for "my ip"](https://www.google.se/search?q=my+ip) or use any online service to figure out
what your "outward-facing" IP address is. For our purposes, let's say your outward-facing IP is
`203.0.113.0`.
- Next try your outward-facing IP by opening `http://203.0.113.0:4001` in a browser. If this works,
that's it! Also try telnet, with the server set to `203.0.113.0` and port `4000`. However, most
likely it will *not* work. If so, read on.
- If your computer has a firewall, it may be blocking the ports we need (it may also block telnet
overall). If so, you need to open the outward-facing ports to in/out communication. See the
manual/instructions for your firewall software on how to do this. To test you could also temporarily
turn off your firewall entirely to see if that was indeed the problem.
- Another common problem for not being able to connect is that you are using a hardware router
(like a wifi router). The router sits 'between' your computer and the Internet. So the IP you find
with Google is the *router's* IP, not that of your computer. To resolve this you need to configure
your router to *forward* data it gets on its ports to the IP and ports of your computer sitting in
your private network. How to do this depends on the make of your router; you usually configure it
using a normal web browser. In the router interface, look for "Port forwarding" or maybe "Virtual
server". If that doesn't work, try to temporarily wire your computer directly to the Internet outlet
(assuming your computer has the ports for it). You'll need to check for your IP again. If that
works, you know the problem is the router.
> Note: If you need to reconfigure a router, the router's Internet-facing ports do *not* have to have to have the same numbers as your computer's (and Evennia's) ports! For example, you might want to connect Evennia's outgoing port 4001 to an outgoing router port 80 - this is the port HTTP requests use and web browsers automatically look for - if you do that you could go to `http://203.0.113.0` without having to add the port at the end. This would collide with any other web services you are running through this router though.
> Note: If you need to reconfigure a router, the router's Internet-facing ports do *not* have to
have to have the same numbers as your computer's (and Evennia's) ports! For example, you might want
to connect Evennia's outgoing port 4001 to an outgoing router port 80 - this is the port HTTP
requests use and web browsers automatically look for - if you do that you could go to
`http://203.0.113.0` without having to add the port at the end. This would collide with any other
web services you are running through this router though.
### Settings example
You can connect Evennia to the Internet without any changes to your settings. The default settings are easy to use but are not necessarily the safest. You can customize your online presence in your [settings file](Server-Conf#settings-file). To have Evennia recognize changed port settings you have to do a full `evennia reboot` to also restart the Portal and not just the Server component.
You can connect Evennia to the Internet without any changes to your settings. The default settings
are easy to use but are not necessarily the safest. You can customize your online presence in your
[settings file](Server-Conf#settings-file). To have Evennia recognize changed port settings you have
to do a full `evennia reboot` to also restart the Portal and not just the Server component.
Below is an example of a simple set of settings, mostly using the defaults. Evennia will require access to five computer ports, of which three (only) should be open to the outside world. Below we continue to assume that our server address is `203.0.113.0`.
Below is an example of a simple set of settings, mostly using the defaults. Evennia will require
access to five computer ports, of which three (only) should be open to the outside world. Below we
continue to assume that our server address is `203.0.113.0`.
```python
# in mygame/server/conf/settings.py
@ -70,7 +111,9 @@ TELNET_PORTS = [4000]
TELNET_INTERFACES = ['0.0.0.0']
```
The `TELNET_*` settings are the most important ones for getting a traditional base game going. Which IP addresses you have available depends on your server hosting solution (see the next sections). Some hosts will restrict which ports you are allowed you use so make sure to check.
The `TELNET_*` settings are the most important ones for getting a traditional base game going. Which
IP addresses you have available depends on your server hosting solution (see the next sections).
Some hosts will restrict which ports you are allowed you use so make sure to check.
### Web server
@ -91,8 +134,13 @@ WEBSERVER_INTERFACES = ['0.0.0.0']
ALLOWED_HOSTS = ['*']
```
The web server is always configured with two ports at a time. The *outgoing* port (`4001` by default) is the port external connections can use. If you don't want users to have to specify the port when they connect, you should set this to `80` - this however only works if you are not running any other web server on the machine.
The *internal* port (`4005` by default) is used internally by Evennia to communicate between the Server and the Portal. It should not be available to the outside world. You usually only need to change the outgoing port unless the default internal port is clashing with some other program.
The web server is always configured with two ports at a time. The *outgoing* port (`4001` by
default) is the port external connections can use. If you don't want users to have to specify the
port when they connect, you should set this to `80` - this however only works if you are not running
any other web server on the machine.
The *internal* port (`4005` by default) is used internally by Evennia to communicate between the
Server and the Portal. It should not be available to the outside world. You usually only need to
change the outgoing port unless the default internal port is clashing with some other program.
### Web client
@ -110,7 +158,10 @@ WEBSOCKET_CLIENT_URL = ""
WEBSOCKET_CLIENT_PORT = 4002
```
The websocket-based web client needs to be able to call back to the server, and these settings must be changed for it to find where to look. If it cannot find the server you will get an warning in your browser's Console (in the dev tools of the browser), and the client will revert to the AJAX-based of the client instead, which tends to be slower.
The websocket-based web client needs to be able to call back to the server, and these settings must
be changed for it to find where to look. If it cannot find the server you will get an warning in
your browser's Console (in the dev tools of the browser), and the client will revert to the AJAX-
based of the client instead, which tends to be slower.
### Other ports
@ -127,15 +178,25 @@ SSH_INTERFACES = ['0.0.0.0']
AMP_PORT = 4006
```
The `AMP_PORT` is required to work, since this is the internal port linking Evennia's [Server and Portal](Portal-And-Server) components together. The other ports are encrypted ports that may be useful for custom protocols but are otherwise not used.
The `AMP_PORT` is required to work, since this is the internal port linking Evennia's [Server and
Portal](Portal-And-Server) components together. The other ports are encrypted ports that may be
useful for custom protocols but are otherwise not used.
### Lockdown mode
When you test things out and check configurations you may not want players to drop in on you. Similarly, if you are doing maintenance on a live game you may want to take it offline for a while to fix eventual problems without risking people connecting. To do this, stop the server with `evennia stop` and add `LOCKDOWN_MODE = True` to your settings file. When you start the server again, your game will only be accessible from localhost.
When you test things out and check configurations you may not want players to drop in on you.
Similarly, if you are doing maintenance on a live game you may want to take it offline for a while
to fix eventual problems without risking people connecting. To do this, stop the server with
`evennia stop` and add `LOCKDOWN_MODE = True` to your settings file. When you start the server
again, your game will only be accessible from localhost.
### Registering with the Evennia game directory
Once your game is online you should make sure to register it with the [Evennia Game Index](http://games.evennia.com/). Registering with the index will help people find your server, drum up interest for your game and also shows people that Evennia is being used. You can do this even if you are just starting development - if you don't give any telnet/web address it will appear as _Not yet public_ and just be a teaser. If so, pick _pre-alpha_ as the development status.
Once your game is online you should make sure to register it with the [Evennia Game
Index](http://games.evennia.com/). Registering with the index will help people find your server,
drum up interest for your game and also shows people that Evennia is being used. You can do this
even if you are just starting development - if you don't give any telnet/web address it will appear
as _Not yet public_ and just be a teaser. If so, pick _pre-alpha_ as the development status.
To register, stand in your game dir, run
@ -145,7 +206,10 @@ and follow the instructions. See the [Game index page](Evennia-Game-Index) for m
## SSL
SSL can be very useful for web clients. It will protect the credentials and gameplay of your users over a web client if they are in a public place, and your websocket can also be switched to WSS for the same benefit. SSL certificates used to cost money on a yearly basis, but there is now a program that issues them for free with assisted setup to make the entire process less painful.
SSL can be very useful for web clients. It will protect the credentials and gameplay of your users
over a web client if they are in a public place, and your websocket can also be switched to WSS for
the same benefit. SSL certificates used to cost money on a yearly basis, but there is now a program
that issues them for free with assisted setup to make the entire process less painful.
Options that may be useful in combination with an SSL proxy:
@ -162,16 +226,27 @@ WEBSOCKET_CLIENT_URL = "wss://fqdn:4002"
### Let's Encrypt
[Let's Encrypt](https://letsencrypt.org) is a certificate authority offering free certificates to secure a website with HTTPS. To get started issuing a certificate for your web server using Let's Encrypt, see these links:
[Let's Encrypt](https://letsencrypt.org) is a certificate authority offering free certificates to
secure a website with HTTPS. To get started issuing a certificate for your web server using Let's
Encrypt, see these links:
- [Let's Encrypt - Getting Started](https://letsencrypt.org/getting-started/)
- The [CertBot Client](https://certbot.eff.org/) is a program for automatically obtaining a certificate, use it and maintain it with your website.
- The [CertBot Client](https://certbot.eff.org/) is a program for automatically obtaining a
certificate, use it and maintain it with your website.
Also, on Freenode visit the #letsencrypt channel for assistance from the community. For an additional resource, Let's Encrypt has a very active [community forum](https://community.letsencrypt.org/).
Also, on Freenode visit the #letsencrypt channel for assistance from the community. For an
additional resource, Let's Encrypt has a very active [community
forum](https://community.letsencrypt.org/).
[A blog where someone sets up Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04)
[A blog where someone sets up Let's Encrypt](https://www.digitalocean.com/community/tutorials/how-
to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04)
The only process missing from all of the above documentation is how to pass verification. This is how Let's Encrypt verifies that you have control over your domain (not necessarily ownership, it's Domain Validation (DV)). This can be done either with configuring a certain path on your web server or through a TXT record in your DNS. Which one you will want to do is a personal preference, but can also be based on your hosting choice. In a controlled/cPanel environment, you will most likely have to use DNS verification.
The only process missing from all of the above documentation is how to pass verification. This is
how Let's Encrypt verifies that you have control over your domain (not necessarily ownership, it's
Domain Validation (DV)). This can be done either with configuring a certain path on your web server
or through a TXT record in your DNS. Which one you will want to do is a personal preference, but can
also be based on your hosting choice. In a controlled/cPanel environment, you will most likely have
to use DNS verification.
## Relevant SSL Proxy Setup Information
- [Apache webserver configuration](Apache-Config) (optional)
@ -181,7 +256,9 @@ The only process missing from all of the above documentation is how to pass veri
### Using your own computer as a server
What we showed above is by far the simplest and probably cheapest option: Run Evennia on your own home computer. Moreover, since Evennia is its own web server, you don't need to install anything extra to have a website.
What we showed above is by far the simplest and probably cheapest option: Run Evennia on your own
home computer. Moreover, since Evennia is its own web server, you don't need to install anything
extra to have a website.
**Advantages**
- Free (except for internet costs and the electrical bill).
@ -191,69 +268,140 @@ What we showed above is by far the simplest and probably cheapest option: Run Ev
**Disadvantages**
- You need a good internet connection, ideally without any upload/download limits/costs.
- If you want to run a full game this way, your computer needs to always be on. It could be noisy, and as mentioned, the electrical bill must be considered.
- No support or safety - if your house burns down, so will your game. Also, you are yourself responsible for doing regular backups.
- If you want to run a full game this way, your computer needs to always be on. It could be noisy,
and as mentioned, the electrical bill must be considered.
- No support or safety - if your house burns down, so will your game. Also, you are yourself
responsible for doing regular backups.
- Potentially not as easy if you don't know how to open ports in your firewall or router.
- Home IP numbers are often dynamically allocated, so for permanent online time you need to set up a DNS to always re-point to the right place (see below).
- You are personally responsible for any use/misuse of your internet connection-- though unlikely (but not impossible) if running your server somehow causes issues for other customers on the network, goes against your ISP's terms of service (many ISPs insist on upselling you to a business-tier connection) or you are the subject of legal action by a copyright holder, you may find your main internet connection terminated as a consequence.
- Home IP numbers are often dynamically allocated, so for permanent online time you need to set up a
DNS to always re-point to the right place (see below).
- You are personally responsible for any use/misuse of your internet connection-- though unlikely
(but not impossible) if running your server somehow causes issues for other customers on the
network, goes against your ISP's terms of service (many ISPs insist on upselling you to a business-
tier connection) or you are the subject of legal action by a copyright holder, you may find your
main internet connection terminated as a consequence.
#### Setting up your own machine as a server
[The first section](Online-Setup#connecting-from-the-outside) of this page describes how to do this and allow users to connect to the IP address of your machine/router.
[The first section](Online-Setup#connecting-from-the-outside) of this page describes how to do this
and allow users to connect to the IP address of your machine/router.
A complication with using a specific IP address like this is that your home IP might not remain the same. Many ISPs (Internet Service Providers) allocates a *dynamic* IP to you which could change at any time. When that happens, that IP you told people to go to will be worthless. Also, that long string of numbers is not very pretty, is it? It's hard to remember and not easy to use in marketing your game. What you need is to alias it to a more sensible domain name - an alias that follows you around also when the IP changes.
A complication with using a specific IP address like this is that your home IP might not remain the
same. Many ISPs (Internet Service Providers) allocates a *dynamic* IP to you which could change at
any time. When that happens, that IP you told people to go to will be worthless. Also, that long
string of numbers is not very pretty, is it? It's hard to remember and not easy to use in marketing
your game. What you need is to alias it to a more sensible domain name - an alias that follows you
around also when the IP changes.
1. To set up a domain name alias, we recommend starting with a free domain name from [FreeDNS](http://freedns.afraid.org/). Once you register there (it's free) you have access to tens of thousands domain names that people have "donated" to allow you to use for your own sub domain. For example, `strangled.net` is one of those available domains. So tying our IP address to `strangled.net` using the subdomain `evennia` would mean that one could henceforth direct people to `http://evennia.strangled.net:4001` for their gaming needs - far easier to remember!
1. So how do we make this new, nice domain name follow us also if our IP changes? For this we need to set up a little program on our computer. It will check whenever our ISP decides to change our IP and tell FreeDNS that. There are many alternatives to be found from FreeDNS:s homepage, one that works on multiple platforms is [inadyn](http://www.inatech.eu/inadyn/). Get it from their page or, in Linux, through something like `apt-get install inadyn`.
1. Next, you login to your account on FreeDNS and go to the [Dynamic](http://freedns.afraid.org/dynamic/) page. You should have a list of your subdomains. Click the `Direct URL` link and you'll get a page with a text message. Ignore that and look at the URL of the page. It should be ending in a lot of random letters. Everything after the question mark is your unique "hash". Copy this string.
1. To set up a domain name alias, we recommend starting with a free domain name from
[FreeDNS](http://freedns.afraid.org/). Once you register there (it's free) you have access to tens
of thousands domain names that people have "donated" to allow you to use for your own sub domain.
For example, `strangled.net` is one of those available domains. So tying our IP address to
`strangled.net` using the subdomain `evennia` would mean that one could henceforth direct people to
`http://evennia.strangled.net:4001` for their gaming needs - far easier to remember!
1. So how do we make this new, nice domain name follow us also if our IP changes? For this we need
to set up a little program on our computer. It will check whenever our ISP decides to change our IP
and tell FreeDNS that. There are many alternatives to be found from FreeDNS:s homepage, one that
works on multiple platforms is [inadyn](http://www.inatech.eu/inadyn/). Get it from their page or,
in Linux, through something like `apt-get install inadyn`.
1. Next, you login to your account on FreeDNS and go to the
[Dynamic](http://freedns.afraid.org/dynamic/) page. You should have a list of your subdomains. Click
the `Direct URL` link and you'll get a page with a text message. Ignore that and look at the URL of
the page. It should be ending in a lot of random letters. Everything after the question mark is your
unique "hash". Copy this string.
1. You now start inadyn with the following command (Linux):
`inadyn --dyndns_system default@freedns.afraid.org -a <my.domain>,<hash> &`
where `<my.domain>` would be `evennia.strangled.net` and `<hash>` the string of numbers we copied from FreeDNS. The `&` means we run in the background (might not be valid in other operating systems). `inadyn` will henceforth check for changes every 60 seconds. You should put the `inadyn` command string in a startup script somewhere so it kicks into gear whenever your computer starts.
where `<my.domain>` would be `evennia.strangled.net` and `<hash>` the string of numbers we copied
from FreeDNS. The `&` means we run in the background (might not be valid in other operating
systems). `inadyn` will henceforth check for changes every 60 seconds. You should put the `inadyn`
command string in a startup script somewhere so it kicks into gear whenever your computer starts.
### Remote hosting
Your normal "web hotel" will probably not be enough to run Evennia. A web hotel is normally aimed at a very specific usage - delivering web pages, at the most with some dynamic content. The "Python scripts" they refer to on their home pages are usually only intended to be CGI-like scripts launched by their webserver. Even if they allow you shell access (so you can install the Evennia dependencies in the first place), resource usage will likely be very restricted. Running a full-fledged game server like Evennia will probably be shunned upon or be outright impossible. If you are unsure, contact your web hotel and ask about their policy on you running third-party servers that will want to open custom ports.
Your normal "web hotel" will probably not be enough to run Evennia. A web hotel is normally aimed at
a very specific usage - delivering web pages, at the most with some dynamic content. The "Python
scripts" they refer to on their home pages are usually only intended to be CGI-like scripts launched
by their webserver. Even if they allow you shell access (so you can install the Evennia dependencies
in the first place), resource usage will likely be very restricted. Running a full-fledged game
server like Evennia will probably be shunned upon or be outright impossible. If you are unsure,
contact your web hotel and ask about their policy on you running third-party servers that will want
to open custom ports.
The options you probably need to look for are *shell account services*, *VPS:es* or *Cloud services*. A "Shell account" service means that you get a shell account on a server and can log in like any normal user. By contrast, a *VPS* (Virtual Private Server) service usually means that you get `root` access, but in a virtual machine. There are also *Cloud*-type services which allows for starting up multiple virtual machines and pay for what resources you use.
The options you probably need to look for are *shell account services*, *VPS:es* or *Cloud
services*. A "Shell account" service means that you get a shell account on a server and can log in
like any normal user. By contrast, a *VPS* (Virtual Private Server) service usually means that you
get `root` access, but in a virtual machine. There are also *Cloud*-type services which allows for
starting up multiple virtual machines and pay for what resources you use.
**Advantages**
- Shell accounts/VPS/clouds offer more flexibility than your average web hotel - it's the ability to log onto a shared computer away from home.
- Shell accounts/VPS/clouds offer more flexibility than your average web hotel - it's the ability to
log onto a shared computer away from home.
- Usually runs a Linux flavor, making it easy to install Evennia.
- Support. You don't need to maintain the server hardware. If your house burns down, at least your game stays online. Many services guarantee a certain level of up-time and also do regular backups for you. Make sure to check, some offer lower rates in exchange for you yourself being fully responsible for your data/backups.
- Support. You don't need to maintain the server hardware. If your house burns down, at least your
game stays online. Many services guarantee a certain level of up-time and also do regular backups
for you. Make sure to check, some offer lower rates in exchange for you yourself being fully
responsible for your data/backups.
- Usually offers a fixed domain name, so no need to mess with IP addresses.
- May have the ability to easily deploy [docker](Running-Evennia-in-Docker) versions of evennia and/or your game.
- May have the ability to easily deploy [docker](Running-Evennia-in-Docker) versions of evennia
and/or your game.
**Disadvantages**
- Might be pretty expensive (more so than a web hotel). Note that Evennia will normally need at least 100MB RAM and likely much more for a large production game.
- Might be pretty expensive (more so than a web hotel). Note that Evennia will normally need at
least 100MB RAM and likely much more for a large production game.
- Linux flavors might feel unfamiliar to users not used to ssh/PuTTy and the Linux command line.
- You are probably sharing the server with many others, so you are not completely in charge. CPU usage might be limited. Also, if the server people decides to take the server down for maintenance, you have no choice but to sit it out (but you'll hopefully be warned ahead of time).
- You are probably sharing the server with many others, so you are not completely in charge. CPU
usage might be limited. Also, if the server people decides to take the server down for maintenance,
you have no choice but to sit it out (but you'll hopefully be warned ahead of time).
#### Installing Evennia on a remote server
Firstly, if you are familiar with server infrastructure, consider using [Docker](Running-Evennia-in-Docker) to deploy your game to the remote server; it will likely ease installation and deployment. Docker images may be a little confusing if you are completely new to them though.
Firstly, if you are familiar with server infrastructure, consider using [Docker](Running-Evennia-in-
Docker) to deploy your game to the remote server; it will likely ease installation and deployment.
Docker images may be a little confusing if you are completely new to them though.
If not using docker, and assuming you know how to connect to your account over ssh/PuTTy, you should be able to follow the [Getting Started](Getting-Started) instructions normally. You only need Python and GIT pre-installed; these should both be available on any servers (if not you should be able to easily ask for them to be installed). On a VPS or Cloud service you can install them yourself as needed.
If not using docker, and assuming you know how to connect to your account over ssh/PuTTy, you should
be able to follow the [Getting Started](Getting-Started) instructions normally. You only need Python
and GIT pre-installed; these should both be available on any servers (if not you should be able to
easily ask for them to be installed). On a VPS or Cloud service you can install them yourself as
needed.
If `virtualenv` is not available and you can't get it, you can download it (it's just a single file) from [the virtualenv pypi](https://pypi.python.org/pypi/virtualenv). Using `virtualenv` you can install everything without actually needing to have further `root` access. Ports might be an issue, so make sure you know which ports are available to use and reconfigure Evennia accordingly.
If `virtualenv` is not available and you can't get it, you can download it (it's just a single file)
from [the virtualenv pypi](https://pypi.python.org/pypi/virtualenv). Using `virtualenv` you can
install everything without actually needing to have further `root` access. Ports might be an issue,
so make sure you know which ports are available to use and reconfigure Evennia accordingly.
### Hosting options
To find commercial solutions, browse the web for "shell access", "VPS" or "Cloud services" in your region. You may find useful offers for "low cost" VPS hosting on [Low End Box][7]. The associated [Low End Talk][8] forum can be useful for health checking the many small businesses that offer "value" hosting, and occasionally for technical suggestions.
To find commercial solutions, browse the web for "shell access", "VPS" or "Cloud services" in your
region. You may find useful offers for "low cost" VPS hosting on [Low End Box][7]. The associated
[Low End Talk][8] forum can be useful for health checking the many small businesses that offer
"value" hosting, and occasionally for technical suggestions.
There are all sorts of services available. Below are some international suggestions offered by Evennia users:
There are all sorts of services available. Below are some international suggestions offered by
Evennia users:
Hosting name | Type | Lowest price | Comments
:--------------:|:-------:---------------
[silvren.com][1] | Shell account | Free for MU* | Private hobby provider so don't assume backups or expect immediate support. To ask for an account, connect with a MUD client to iweb.localecho.net, port 4201 and ask for "Jarin".
[Digital Ocean][2] | VPS | $5/month | You can get a $50 credit if you use the referral link https://m.do.co/c/8f64fec2670c - if you do, once you've had it long enough to have paid $25 we will get that as a referral bonus to help Evennia development.
[Amazon Web services][3] | Cloud | ~$5/month / on-demand | Free Tier first 12 months. Regions available around the globe.
[silvren.com][1] | Shell account | Free for MU* | Private hobby provider so don't assume backups
or expect immediate support. To ask for an account, connect with a MUD client to iweb.localecho.net,
port 4201 and ask for "Jarin".
[Digital Ocean][2] | VPS | $5/month | You can get a $50 credit if you use the referral link
https://m.do.co/c/8f64fec2670c - if you do, once you've had it long enough to have paid $25 we will
get that as a referral bonus to help Evennia development.
[Amazon Web services][3] | Cloud | ~$5/month / on-demand | Free Tier first 12 months. Regions
available around the globe.
[Amazon Lightsail][9] | Cloud | $5/month | Free first month. AWS's new "fixed cost" offering.
[Genesis MUD hosting][4] | Shell account | $8/month | Dedicated MUD host with very limited memory offerings. As for 2017, runs a 13 years old Python version (2.4) so you'd need to either convince them to update or compile yourself. Note that Evennia needs *at least* the "Deluxe" package (50MB RAM) and probably *a lot* higher for a production game. This host is *not* recommended for Evennia.
[Genesis MUD hosting][4] | Shell account | $8/month | Dedicated MUD host with very limited memory
offerings. As for 2017, runs a 13 years old Python version (2.4) so you'd need to either convince
them to update or compile yourself. Note that Evennia needs *at least* the "Deluxe" package (50MB
RAM) and probably *a lot* higher for a production game. This host is *not* recommended for Evennia.
[Host1Plus][5] | VPS & Cloud | $4/month | $4-$8/month depending on length of sign-up period.
[Scaleway][6] | Cloud | &euro;3/month / on-demand | EU based (Paris, Amsterdam). Smallest option provides 2GB RAM.
[Prgmr][10] | VPS | $5/month | 1 month free with a year prepay. You likely want some experience with servers with this option as they don't have a lot of support.
[Scaleway][6] | Cloud | &euro;3/month / on-demand | EU based (Paris, Amsterdam). Smallest option
provides 2GB RAM.
[Prgmr][10] | VPS | $5/month | 1 month free with a year prepay. You likely want some experience with
servers with this option as they don't have a lot of support.
[Linode][11] | Cloud | $5/month / on-demand | Multiple regions. Smallest option provides 1GB RAM
*Please help us expand this list.*
@ -271,7 +419,13 @@ Hosting name | Type | Lowest price | Comments
## Cloud9
If you are interested in running Evennia in the online dev environment [Cloud9](https://c9.io/), you can spin it up through their normal online setup using the Evennia Linux install instructions. The one extra thing you will have to do is update `mygame/server/conf/settings.py` and add `WEBSERVER_PORTS = [(8080, 4001)]`. This will then let you access the web server and do everything else as normal.
Note that, as of December 2017, Cloud9 was re-released by Amazon as a service within their AWS cloud service offering. New customers entitled to the 1 year AWS "free tier" may find it provides sufficient resources to operate a Cloud9 development environment without charge. https://aws.amazon.com/cloud9/
If you are interested in running Evennia in the online dev environment [Cloud9](https://c9.io/), you
can spin it up through their normal online setup using the Evennia Linux install instructions. The
one extra thing you will have to do is update `mygame/server/conf/settings.py` and add
`WEBSERVER_PORTS = [(8080, 4001)]`. This will then let you access the web server and do everything
else as normal.
Note that, as of December 2017, Cloud9 was re-released by Amazon as a service within their AWS cloud
service offering. New customers entitled to the 1 year AWS "free tier" may find it provides
sufficient resources to operate a Cloud9 development environment without charge.
https://aws.amazon.com/cloud9/

View file

@ -1,9 +1,14 @@
# Parsing command arguments, theory and best practices
This tutorial will elaborate on the many ways one can parse command arguments. The first step after [adding a command](Adding-Command-Tutorial) usually is to parse its arguments. There are lots of ways to do it, but some are indeed better than others and this tutorial will try to present them.
This tutorial will elaborate on the many ways one can parse command arguments. The first step after
[adding a command](Adding-Command-Tutorial) usually is to parse its arguments. There are lots of
ways to do it, but some are indeed better than others and this tutorial will try to present them.
If you're a Python beginner, this tutorial might help you a lot. If you're already familiar with Python syntax, this tutorial might still contain useful information. There are still a lot of things I find in the standard library that come as a surprise, though they were there all along. This might be true for others.
If you're a Python beginner, this tutorial might help you a lot. If you're already familiar with
Python syntax, this tutorial might still contain useful information. There are still a lot of
things I find in the standard library that come as a surprise, though they were there all along.
This might be true for others.
In this tutorial we will:
@ -14,29 +19,42 @@ In this tutorial we will:
## What are command arguments?
I'm going to talk about command arguments and parsing a lot in this tutorial. So let's be sure we talk about the same thing before going any further:
I'm going to talk about command arguments and parsing a lot in this tutorial. So let's be sure we
talk about the same thing before going any further:
> A command is an Evennia object that handles specific user input.
For instance, the default `look` is a command. After having created your Evennia game, and connected to it, you should be able to type `look` to see what's around. In this context, `look` is a command.
For instance, the default `look` is a command. After having created your Evennia game, and
connected to it, you should be able to type `look` to see what's around. In this context, `look` is
a command.
> Command arguments are additional text passed after the command.
Following the same example, you can type `look self` to look at yourself. In this context, `self` is the text specified after `look`. `" self"` is the argument to the `look` command.
Following the same example, you can type `look self` to look at yourself. In this context, `self`
is the text specified after `look`. `" self"` is the argument to the `look` command.
Part of our task as a game developer is to connect user inputs (mostly commands) with actions in the game. And most of the time, entering commands is not enough, we have to rely on arguments for specifying actions with more accuracy.
Part of our task as a game developer is to connect user inputs (mostly commands) with actions in the
game. And most of the time, entering commands is not enough, we have to rely on arguments for
specifying actions with more accuracy.
Take the `say` command. If you couldn't specify what to say as a command argument (`say hello!`), you would have trouble communicating with others in the game. One would need to create a different command for every kind of word or sentence, which is, of course, not practical.
Take the `say` command. If you couldn't specify what to say as a command argument (`say hello!`),
you would have trouble communicating with others in the game. One would need to create a different
command for every kind of word or sentence, which is, of course, not practical.
Last thing: what is parsing?
> In our case, parsing is the process by which we convert command arguments into something we can work with.
> In our case, parsing is the process by which we convert command arguments into something we can
work with.
We don't usually use the command argument as is (which is just text, of type `str` in Python). We need to extract useful information. We might want to ask the user for a number, or the name of another character present in the same room. We're going to see how to do all that now.
We don't usually use the command argument as is (which is just text, of type `str` in Python). We
need to extract useful information. We might want to ask the user for a number, or the name of
another character present in the same room. We're going to see how to do all that now.
## Working with strings
In object terms, when you write a command in Evennia (when you write the Python class), the arguments are stored in the `args` attribute. Which is to say, inside your `func` method, you can access the command arguments in `self.args`.
In object terms, when you write a command in Evennia (when you write the Python class), the
arguments are stored in the `args` attribute. Which is to say, inside your `func` method, you can
access the command arguments in `self.args`.
### self.args
@ -61,7 +79,8 @@ class CmdTest(Command):
self.msg(f"You have entered: {self.args}.")
```
If you add this command and test it, you will receive exactly what you have entered without any parsing:
If you add this command and test it, you will receive exactly what you have entered without any
parsing:
```
> test Whatever
@ -70,14 +89,18 @@ You have entered: Whatever.
You have entered: .
```
> The lines starting with `>` indicate what you enter into your client. The other lines are what you receive from the game server.
> The lines starting with `>` indicate what you enter into your client. The other lines are what
you receive from the game server.
Notice two things here:
1. The left space between our command key ("test", here) and our command argument is not removed. That's why there are two spaces in our output at line 2. Try entering something like "testok".
2. Even if you don't enter command arguments, the command will still be called with an empty string in `self.args`.
1. The left space between our command key ("test", here) and our command argument is not removed.
That's why there are two spaces in our output at line 2. Try entering something like "testok".
2. Even if you don't enter command arguments, the command will still be called with an empty string
in `self.args`.
Perhaps a slight modification to our code would be appropriate to see what's happening. We will force Python to display the command arguments as a debug string using a little shortcut.
Perhaps a slight modification to our code would be appropriate to see what's happening. We will
force Python to display the command arguments as a debug string using a little shortcut.
```python
class CmdTest(Command):
@ -98,7 +121,8 @@ class CmdTest(Command):
self.msg(f"You have entered: {self.args!r}.")
```
The only line we have changed is the last one, and we have added `!r` between our braces to tell Python to print the debug version of the argument (the repr-ed version). Let's see the result:
The only line we have changed is the last one, and we have added `!r` between our braces to tell
Python to print the debug version of the argument (the repr-ed version). Let's see the result:
```
> test Whatever
@ -109,21 +133,32 @@ You have entered: ''.
You have entered: " And something with '?".
```
This displays the string in a way you could see in the Python interpreter. It might be easier to read... to debug, anyway.
This displays the string in a way you could see in the Python interpreter. It might be easier to
read... to debug, anyway.
I insist so much on that point because it's crucial: the command argument is just a string (of type `str`) and we will use this to parse it. What you will see is mostly not Evennia-specific, it's Python-specific and could be used in any other project where you have the same need.
I insist so much on that point because it's crucial: the command argument is just a string (of type
`str`) and we will use this to parse it. What you will see is mostly not Evennia-specific, it's
Python-specific and could be used in any other project where you have the same need.
### Stripping
As you've seen, our command arguments are stored with the space. And the space between the command and the arguments is often of no importance.
As you've seen, our command arguments are stored with the space. And the space between the command
and the arguments is often of no importance.
> Why is it ever there?
Evennia will try its best to find a matching command. If the user enters your command key with arguments (but omits the space), Evennia will still be able to find and call the command. You might have seen what happened if the user entered `testok`. In this case, `testok` could very well be a command (Evennia checks for that) but seeing none, and because there's a `test` command, Evennia calls it with the arguments `"ok"`.
Evennia will try its best to find a matching command. If the user enters your command key with
arguments (but omits the space), Evennia will still be able to find and call the command. You might
have seen what happened if the user entered `testok`. In this case, `testok` could very well be a
command (Evennia checks for that) but seeing none, and because there's a `test` command, Evennia
calls it with the arguments `"ok"`.
But most of the time, we don't really care about this left space, so you will often see code to remove it. There are different ways to do it in Python, but a command use case is the `strip` method on `str` and its cousins, `lstrip` and `rstrip`.
But most of the time, we don't really care about this left space, so you will often see code to
remove it. There are different ways to do it in Python, but a command use case is the `strip`
method on `str` and its cousins, `lstrip` and `rstrip`.
- `strip`: removes one or more characters (either spaces or other characters) from both ends of the string.
- `strip`: removes one or more characters (either spaces or other characters) from both ends of the
string.
- `lstrip`: same thing but only removes from the left end (left strip) of the string.
- `rstrip`: same thing but only removes from the right end (right strip) of the string.
@ -140,7 +175,8 @@ Some Python examples might help:
'Now, what is it'
```
Usually, since we don't need the space separator, but still want our command to work if there's no separator, we call `lstrip` on the command arguments:
Usually, since we don't need the space separator, but still want our command to work if there's no
separator, we call `lstrip` on the command arguments:
```python
class CmdTest(Command):
@ -165,7 +201,9 @@ class CmdTest(Command):
self.msg(f"You have entered: {self.args!r}.")
```
> We are now beginning to override the command's `parse` method, which is typically useful just for argument parsing. This method is executed before `func` and so `self.args` in `func()` will contain our `self.args.lstrip()`.
> We are now beginning to override the command's `parse` method, which is typically useful just for
argument parsing. This method is executed before `func` and so `self.args` in `func()` will contain
our `self.args.lstrip()`.
Let's try it:
@ -182,13 +220,18 @@ You have entered: 'And something with lots of spaces'.
Spaces at the end of the string are kept, but all spaces at the beginning are removed:
> `strip`, `lstrip` and `rstrip` without arguments will strip spaces, line breaks and other common separators. You can specify one or more characters as a parameter. If you specify more than one character, all of them will be stripped from your original string.
> `strip`, `lstrip` and `rstrip` without arguments will strip spaces, line breaks and other common
separators. You can specify one or more characters as a parameter. If you specify more than one
character, all of them will be stripped from your original string.
### Convert arguments to numbers
As pointed out, `self.args` is a string (of type `str`). What if we want the user to enter a number?
As pointed out, `self.args` is a string (of type `str`). What if we want the user to enter a
number?
Let's take a very simple example: creating a command, `roll`, that allows to roll a six-sided die. The player has to guess the number, specifying the number as argument. To win, the player has to match the number with the die. Let's see an example:
Let's take a very simple example: creating a command, `roll`, that allows to roll a six-sided die.
The player has to guess the number, specifying the number as argument. To win, the player has to
match the number with the die. Let's see an example:
```
> roll 3
@ -202,7 +245,9 @@ You roll a die. It lands on the number 1.
You played 1, you have won!
```
If that's your first command, it's a good opportunity to try to write it. A command with a simple and finite role always is a good starting choice. Here's how we could (first) write it... but it won't work as is, I warn you:
If that's your first command, it's a good opportunity to try to write it. A command with a simple
and finite role always is a good starting choice. Here's how we could (first) write it... but it
won't work as is, I warn you:
```python
from random import randint
@ -242,11 +287,20 @@ class CmdRoll(Command):
self.msg(f"You played {self.args}, you have lost.")
```
If you try this code, Python will complain that you try to compare a number with a string: `figure` is a number and `self.args` is a string and can't be compared as-is in Python. Python doesn't do "implicit converting" as some languages do. By the way, this might be annoying sometimes, and other times you will be glad it tries to encourage you to be explicit rather than implicit about what to do. This is an ongoing debate between programmers. Let's move on!
If you try this code, Python will complain that you try to compare a number with a string: `figure`
is a number and `self.args` is a string and can't be compared as-is in Python. Python doesn't do
"implicit converting" as some languages do. By the way, this might be annoying sometimes, and other
times you will be glad it tries to encourage you to be explicit rather than implicit about what to
do. This is an ongoing debate between programmers. Let's move on!
So we need to convert the command argument from a `str` into an `int`. There are a few ways to do it. But the proper way is to try to convert and deal with the `ValueError` Python exception.
So we need to convert the command argument from a `str` into an `int`. There are a few ways to do
it. But the proper way is to try to convert and deal with the `ValueError` Python exception.
Converting a `str` into an `int` in Python is extremely simple: just use the `int` function, give it the string and it returns an integer, if it could. If it can't, it will raise `ValueError`. So we'll need to catch that. However, we also have to indicate to Evennia that, should the number be invalid, no further parsing should be done. Here's a new attempt at our command with this converting:
Converting a `str` into an `int` in Python is extremely simple: just use the `int` function, give it
the string and it returns an integer, if it could. If it can't, it will raise `ValueError`. So
we'll need to catch that. However, we also have to indicate to Evennia that, should the number be
invalid, no further parsing should be done. Here's a new attempt at our command with this
converting:
```python
from random import randint
@ -295,13 +349,24 @@ class CmdRoll(Command):
self.msg(f"You played {self.entered}, you have lost.")
```
Before enjoying the result, let's examine the `parse` method a little more: what it does is try to convert the entered argument from a `str` to an `int`. This might fail (if a user enters `roll something`). In such a case, Python raises a `ValueError` exception. We catch it in our `try/except` block, send a message to the user and raise the `InterruptCommand` exception in response to tell Evennia to not run `func()`, since we have no valid number to give it.
Before enjoying the result, let's examine the `parse` method a little more: what it does is try to
convert the entered argument from a `str` to an `int`. This might fail (if a user enters `roll
something`). In such a case, Python raises a `ValueError` exception. We catch it in our
`try/except` block, send a message to the user and raise the `InterruptCommand` exception in
response to tell Evennia to not run `func()`, since we have no valid number to give it.
In the `func` method, instead of using `self.args`, we use `self.entered` which we have defined in our `parse` method. You can expect that, if `func()` is run, then `self.entered` contains a valid number.
In the `func` method, instead of using `self.args`, we use `self.entered` which we have defined in
our `parse` method. You can expect that, if `func()` is run, then `self.entered` contains a valid
number.
If you try this command, it will work as expected this time: the number is converted as it should and compared to the die roll. You might spend some minutes playing this game. Time out!
If you try this command, it will work as expected this time: the number is converted as it should
and compared to the die roll. You might spend some minutes playing this game. Time out!
Something else we could want to address: in our small example, we only want the user to enter a positive number between 1 and 6. And the user can enter `roll 0` or `roll -8` or `roll 208` for that matter, the game still works. It might be worth addressing. Again, you could write a condition to do that, but since we're catching an exception, we might end up with something cleaner by grouping:
Something else we could want to address: in our small example, we only want the user to enter a
positive number between 1 and 6. And the user can enter `roll 0` or `roll -8` or `roll 208` for
that matter, the game still works. It might be worth addressing. Again, you could write a
condition to do that, but since we're catching an exception, we might end up with something cleaner
by grouping:
```python
from random import randint
@ -351,13 +416,19 @@ class CmdRoll(Command):
self.msg(f"You played {self.entered}, you have lost.")
```
Using grouped exceptions like that makes our code easier to read, but if you feel more comfortable checking, afterward, that the number the user entered is in the right range, you can do so in a latter condition.
Using grouped exceptions like that makes our code easier to read, but if you feel more comfortable
checking, afterward, that the number the user entered is in the right range, you can do so in a
latter condition.
> Notice that we have updated our `parse` method only in this last attempt, not our `func()` method which remains the same. This is one goal of separating argument parsing from command processing, these two actions are best kept isolated.
> Notice that we have updated our `parse` method only in this last attempt, not our `func()` method
which remains the same. This is one goal of separating argument parsing from command processing,
these two actions are best kept isolated.
### Working with several arguments
Often a command expects several arguments. So far, in our example with the "roll" command, we only expect one argument: a number and just a number. What if we want the user to specify several numbers? First the number of dice to roll, then the guess?
Often a command expects several arguments. So far, in our example with the "roll" command, we only
expect one argument: a number and just a number. What if we want the user to specify several
numbers? First the number of dice to roll, then the guess?
> You won't win often if you roll 5 dice but that's for the example.
@ -367,7 +438,9 @@ So we would like to interpret a command like this:
(To be understood: roll 3 dice, my guess is the total number will be 12.)
What we need is to cut our command argument, which is a `str`, break it at the space (we use the space as a delimiter). Python provides the `str.split` method which we'll use. Again, here are some examples from the Python interpreter:
What we need is to cut our command argument, which is a `str`, break it at the space (we use the
space as a delimiter). Python provides the `str.split` method which we'll use. Again, here are
some examples from the Python interpreter:
>>> args = "3 12"
>>> args.split(" ")
@ -377,9 +450,12 @@ What we need is to cut our command argument, which is a `str`, break it at the s
['a', 'command', 'with', 'several', 'arguments']
>>>
As you can see, `str.split` will "convert" our strings into a list of strings. The specified argument (`" "` in our case) is used as delimiter. So Python browses our original string. When it sees a delimiter, it takes whatever is before this delimiter and append it to a list.
As you can see, `str.split` will "convert" our strings into a list of strings. The specified
argument (`" "` in our case) is used as delimiter. So Python browses our original string. When it
sees a delimiter, it takes whatever is before this delimiter and append it to a list.
The point here is that `str.split` will be used to split our argument. But, as you can see from the above output, we can never be sure of the length of the list at this point:
The point here is that `str.split` will be used to split our argument. But, as you can see from the
above output, we can never be sure of the length of the list at this point:
>>> args = "something"
>>> args.split(" ")
@ -389,14 +465,20 @@ The point here is that `str.split` will be used to split our argument. But, as
['']
>>>
Again we could use a condition to check the number of split arguments, but Python offers a better approach, making use of its exception mechanism. We'll give a second argument to `str.split`, the maximum number of splits to do. Let's see an example, this feature might be confusing at first glance:
Again we could use a condition to check the number of split arguments, but Python offers a better
approach, making use of its exception mechanism. We'll give a second argument to `str.split`, the
maximum number of splits to do. Let's see an example, this feature might be confusing at first
glance:
>>> args = "that is something great"
>>> args.split(" ", 1) # one split, that is a list with two elements (before, after)
['that', 'is something great']
>>>
Read this example as many times as needed to understand it. The second argument we give to `str.split` is not the length of the list that should be returned, but the number of times we have to split. Therefore, we specify 1 here, but we get a list of two elements (before the separator, after the separator).
Read this example as many times as needed to understand it. The second argument we give to
`str.split` is not the length of the list that should be returned, but the number of times we have
to split. Therefore, we specify 1 here, but we get a list of two elements (before the separator,
after the separator).
> What will happen if Python can't split the number of times we ask?
@ -407,9 +489,12 @@ It won't:
['whatever']
>>>
This is one moment I would have hoped for an exception and didn't get one. But there's another way which will raise an exception if there is an error: variable unpacking.
This is one moment I would have hoped for an exception and didn't get one. But there's another way
which will raise an exception if there is an error: variable unpacking.
We won't talk about this feature in details here. It would be complicated. But the code is really straightforward to use. Let's take our example of the roll command but let's add a first argument: the number of dice to roll.
We won't talk about this feature in details here. It would be complicated. But the code is really
straightforward to use. Let's take our example of the roll command but let's add a first argument:
the number of dice to roll.
```python
from random import randint
@ -490,13 +575,23 @@ except ValueError:
raise InterruptCommand
```
We split the argument using `str.split` but we capture the result in two variables. Python is smart enough to know that we want what's left of the space in the first variable, what's right of the space in the second variable. If there is not even a space in the string, Python will raise a `ValueError` exception.
We split the argument using `str.split` but we capture the result in two variables. Python is smart
enough to know that we want what's left of the space in the first variable, what's right of the
space in the second variable. If there is not even a space in the string, Python will raise a
`ValueError` exception.
This code is much easier to read than browsing through the returned strings of `str.split`. We can convert both variables the way we did previously. Actually there are not so many changes in this version and the previous one, most of it is due to name changes for clarity.
This code is much easier to read than browsing through the returned strings of `str.split`. We can
convert both variables the way we did previously. Actually there are not so many changes in this
version and the previous one, most of it is due to name changes for clarity.
> Splitting a string with a maximum of splits is a common occurrence while parsing command arguments. You can also see the `str.rspli8t` method that does the same thing but from the right of the string. Therefore, it will attempt to find delimiters at the end of the string and work toward the beginning of it.
> Splitting a string with a maximum of splits is a common occurrence while parsing command
arguments. You can also see the `str.rspli8t` method that does the same thing but from the right of
the string. Therefore, it will attempt to find delimiters at the end of the string and work toward
the beginning of it.
We have used a space as a delimiter. This is absolutely not necessary. You might remember that most default Evennia commands can take an `=` sign as a delimiter. Now you know how to parse them as well:
We have used a space as a delimiter. This is absolutely not necessary. You might remember that
most default Evennia commands can take an `=` sign as a delimiter. Now you know how to parse them
as well:
>>> cmd_key = "tel"
>>> cmd_args = "book = chest"
@ -509,14 +604,18 @@ We have used a space as a delimiter. This is absolutely not necessary. You mig
### Optional arguments
Sometimes, you'll come across commands that have optional arguments. These arguments are not necessary but they can be set if more information is needed. I will not provide the entire command code here but just enough code to show the mechanism in Python:
Sometimes, you'll come across commands that have optional arguments. These arguments are not
necessary but they can be set if more information is needed. I will not provide the entire command
code here but just enough code to show the mechanism in Python:
Again, we'll use `str.split`, knowing that we might not have any delimiter at all. For instance, the player could enter the "tel" command like this:
Again, we'll use `str.split`, knowing that we might not have any delimiter at all. For instance,
the player could enter the "tel" command like this:
> tel book
> tell book = chest
The equal sign is optional along with whatever is specified after it. A possible solution in our `parse` method would be:
The equal sign is optional along with whatever is specified after it. A possible solution in our
`parse` method would be:
```python
def parse(self):
@ -530,34 +629,51 @@ The equal sign is optional along with whatever is specified after it. A possibl
destination = None
```
This code would place everything the user entered in `obj` if she didn't specify any equal sign. Otherwise, what's before the equal sign will go in `obj`, what's after the equal sign will go in `destination`. This makes for quick testing after that, more robust code with less conditions that might too easily break your code if you're not careful.
This code would place everything the user entered in `obj` if she didn't specify any equal sign.
Otherwise, what's before the equal sign will go in `obj`, what's after the equal sign will go in
`destination`. This makes for quick testing after that, more robust code with less conditions that
might too easily break your code if you're not careful.
> Again, here we specified a maximum numbers of splits. If the users enters:
> tel book = chest = chair
Then `destination` will contain: `" chest = chair"`. This is often desired, but it's up to you to set parsing however you like.
Then `destination` will contain: `" chest = chair"`. This is often desired, but it's up to you to
set parsing however you like.
## Evennia searches
After this quick tour of some `str` methods, we'll take a look at some Evennia-specific features that you won't find in standard Python.
After this quick tour of some `str` methods, we'll take a look at some Evennia-specific features
that you won't find in standard Python.
One very common task is to convert a `str` into an Evennia object. Take the previous example: having `"book"` in a variable is great, but we would prefer to know what the user is talking about... what is this `"book"`?
One very common task is to convert a `str` into an Evennia object. Take the previous example:
having `"book"` in a variable is great, but we would prefer to know what the user is talking
about... what is this `"book"`?
To get an object from a string, we perform an Evennia search. Evennia provides a `search` method on all typeclassed objects (you will most likely use the one on characters or accounts). This method supports a very wide array of arguments and has [its own tutorial](Tutorial-Searching-For-Objects). Some examples of useful cases follow:
To get an object from a string, we perform an Evennia search. Evennia provides a `search` method on
all typeclassed objects (you will most likely use the one on characters or accounts). This method
supports a very wide array of arguments and has [its own tutorial](Tutorial-Searching-For-Objects).
Some examples of useful cases follow:
### Local searches
When an account or a character enters a command, the account or character is found in the `caller` attribute. Therefore, `self.caller` will contain an account or a character (or a session if that's a session command, though that's not as frequent). The `search` method will be available on this caller.
When an account or a character enters a command, the account or character is found in the `caller`
attribute. Therefore, `self.caller` will contain an account or a character (or a session if that's
a session command, though that's not as frequent). The `search` method will be available on this
caller.
Let's take the same example of our little "tel" command. The user can specify an object as argument:
Let's take the same example of our little "tel" command. The user can specify an object as
argument:
```python
def parse(self):
name = self.args.lstrip()
```
We then need to "convert" this string into an Evennia object. The Evennia object will be searched in the caller's location and its contents by default (that is to say, if the command has been entered by a character, it will search the object in the character's room and the character's inventory).
We then need to "convert" this string into an Evennia object. The Evennia object will be searched
in the caller's location and its contents by default (that is to say, if the command has been
entered by a character, it will search the object in the character's room and the character's
inventory).
```python
def parse(self):
@ -566,7 +682,9 @@ We then need to "convert" this string into an Evennia object. The Evennia objec
self.obj = self.caller.search(name)
```
We specify only one argument to the `search` method here: the string to search. If Evennia finds a match, it will return it and we keep it in the `obj` attribute. If it can't find anything, it will return `None` so we need to check for that:
We specify only one argument to the `search` method here: the string to search. If Evennia finds a
match, it will return it and we keep it in the `obj` attribute. If it can't find anything, it will
return `None` so we need to check for that:
```python
def parse(self):
@ -578,11 +696,14 @@ We specify only one argument to the `search` method here: the string to search.
raise InterruptCommand
```
That's it. After this condition, you know that whatever is in `self.obj` is a valid Evennia object (another character, an object, an exit...).
That's it. After this condition, you know that whatever is in `self.obj` is a valid Evennia object
(another character, an object, an exit...).
### Quiet searches
By default, Evennia will handle the case when more than one match is found in the search. The user will be asked to narrow down and re-enter the command. You can, however, ask to be returned the list of matches and handle this list yourself:
By default, Evennia will handle the case when more than one match is found in the search. The user
will be asked to narrow down and re-enter the command. You can, however, ask to be returned the
list of matches and handle this list yourself:
```python
def parse(self):
@ -597,11 +718,16 @@ By default, Evennia will handle the case when more than one match is found in th
self.obj = objs[0] # Take the first match even if there are several
```
All we have changed to obtain a list is a keyword argument in the `search` method: `quiet`. If set to `True`, then errors are ignored and a list is always returned, so we need to handle it as such. Notice in this example, `self.obj` will contain a valid object too, but if several matches are found, `self.obj` will contain the first one, even if more matches are available.
All we have changed to obtain a list is a keyword argument in the `search` method: `quiet`. If set
to `True`, then errors are ignored and a list is always returned, so we need to handle it as such.
Notice in this example, `self.obj` will contain a valid object too, but if several matches are
found, `self.obj` will contain the first one, even if more matches are available.
### Global searches
By default, Evennia will perform a local search, that is, a search limited by the location in which the caller is. If you want to perform a global search (search in the entire database), just set the `global_search` keyword argument to `True`:
By default, Evennia will perform a local search, that is, a search limited by the location in which
the caller is. If you want to perform a global search (search in the entire database), just set the
`global_search` keyword argument to `True`:
```python
def parse(self):
@ -611,4 +737,12 @@ By default, Evennia will perform a local search, that is, a search limited by th
## Conclusion
Parsing command arguments is vital for most game designers. If you design "intelligent" commands, users should be able to guess how to use them without reading the help, or with a very quick peek at said help. Good commands are intuitive to users. Better commands do what they're told to do. For game designers working on MUDs, commands are the main entry point for users into your game. This is no trivial. If commands execute correctly (if their argument is parsed, if they don't behave in unexpected ways and report back the right errors), you will have happier players that might stay longer on your game. I hope this tutorial gave you some pointers on ways to improve your command parsing. There are, of course, other ways you will discover, or ways you are already using in your code.
Parsing command arguments is vital for most game designers. If you design "intelligent" commands,
users should be able to guess how to use them without reading the help, or with a very quick peek at
said help. Good commands are intuitive to users. Better commands do what they're told to do. For
game designers working on MUDs, commands are the main entry point for users into your game. This is
no trivial. If commands execute correctly (if their argument is parsed, if they don't behave in
unexpected ways and report back the right errors), you will have happier players that might stay
longer on your game. I hope this tutorial gave you some pointers on ways to improve your command
parsing. There are, of course, other ways you will discover, or ways you are already using in your
code.

View file

@ -8,7 +8,8 @@ If you are new to the concept, the main purpose of separating the two is to have
the Portal but keep the MUD running on the Server. This way one can restart/reload the game (the
Server part) without Accounts getting disconnected.
![portal and server layout](https://474a3b9f-a-62cb3a1a-s-sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia_server_portal.png)
![portal and server layout](https://474a3b9f-a-62cb3a1a-s-
sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia_server_portal.png)
The Server and Portal are glued together via an AMP (Asynchronous Messaging Protocol) connection.
This allows the two programs to communicate seamlessly.
This allows the two programs to communicate seamlessly.

View file

@ -8,7 +8,8 @@ Sometimes it can be useful to try to determine just how efficient a particular p
to figure out if one could speed up things more than they are. There are many ways to test the
performance of Python and the running server.
Before digging into this section, remember Donald Knuth's [words of wisdom](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize):
Before digging into this section, remember Donald Knuth's [words of
wisdom](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize):
> *[...]about 97% of the time: Premature optimization is the root of all evil*.
@ -19,7 +20,8 @@ and maintainability and you may find that a small gain in speed is just not wort
## Simple timer tests
Python's `timeit` module is very good for testing small things. For example, in order to test if it is faster to use a `for` loop or a list comprehension you could use the following code:
Python's `timeit` module is very good for testing small things. For example, in order to test if it
is faster to use a `for` loop or a list comprehension you could use the following code:
```python
import timeit
@ -31,54 +33,93 @@ Python's `timeit` module is very good for testing small things. For example, in
<<< 5.358283996582031
```
The `setup` keyword is used to set up things that should not be included in the time measurement, like `a = []` in the first call.
The `setup` keyword is used to set up things that should not be included in the time measurement,
like `a = []` in the first call.
By default the `timeit` function will re-run the given test 1000000 times and returns the *total time* to do so (so *not* the average per test). A hint is to not use this default for testing something that includes database writes - for that you may want to use a lower number of repeats (say 100 or 1000) using the `number=100` keyword.
By default the `timeit` function will re-run the given test 1000000 times and returns the *total
time* to do so (so *not* the average per test). A hint is to not use this default for testing
something that includes database writes - for that you may want to use a lower number of repeats
(say 100 or 1000) using the `number=100` keyword.
## Using cProfile
Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done with `pypy` at this point). Due to the way Evennia's processes are handled, there is no point in using the normal way to start the profiler (`python -m cProfile evennia.py`). Instead you start the profiler through the launcher:
Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done
with `pypy` at this point). Due to the way Evennia's processes are handled, there is no point in
using the normal way to start the profiler (`python -m cProfile evennia.py`). Instead you start the
profiler through the launcher:
evennia --profiler start
This will start Evennia with the Server component running (in daemon mode) under cProfile. You could instead try `--profile` with the `portal` argument to profile the Portal (you would then need to [start the Server separately](Start-Stop-Reload)).
This will start Evennia with the Server component running (in daemon mode) under cProfile. You could
instead try `--profile` with the `portal` argument to profile the Portal (you would then need to
[start the Server separately](Start-Stop-Reload)).
Please note that while the profiler is running, your process will use a lot more memory than usual. Memory usage is even likely to climb over time. So don't leave it running perpetually but monitor it carefully (for example using the `top` command on Linux or the Task Manager's memory display on Windows).
Please note that while the profiler is running, your process will use a lot more memory than usual.
Memory usage is even likely to climb over time. So don't leave it running perpetually but monitor it
carefully (for example using the `top` command on Linux or the Task Manager's memory display on
Windows).
Once you have run the server for a while, you need to stop it so the profiler can give its report. Do *not* kill the program from your task manager or by sending it a kill signal - this will most likely also mess with the profiler. Instead either use `evennia.py stop` or (which may be even better), use `@shutdown` from inside the game.
Once you have run the server for a while, you need to stop it so the profiler can give its report.
Do *not* kill the program from your task manager or by sending it a kill signal - this will most
likely also mess with the profiler. Instead either use `evennia.py stop` or (which may be even
better), use `@shutdown` from inside the game.
Once the server has fully shut down (this may be a lot slower than usual) you will find that profiler has created a new file `mygame/server/logs/server.prof`.
Once the server has fully shut down (this may be a lot slower than usual) you will find that
profiler has created a new file `mygame/server/logs/server.prof`.
## Analyzing the profile
The `server.prof` file is a binary file. There are many ways to analyze and display its contents, all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works).
The `server.prof` file is a binary file. There are many ways to analyze and display its contents,
all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works).
We recommend the
[Runsnake](http://www.vrplumber.com/programming/runsnakerun/) visualizer to see the processor usage of different processes in a graphical form. For more detailed listing of usage time, you can use [KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html). To make KCachegrind work with Python profiles you also need the wrapper script [pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/). You can get pyprof2calltree via `pip` whereas KCacheGrind is something you need to get via your package manager or their homepage.
[Runsnake](http://www.vrplumber.com/programming/runsnakerun/) visualizer to see the processor usage
of different processes in a graphical form. For more detailed listing of usage time, you can use
[KCachegrind](http://kcachegrind.sourceforge.net/html/Home.html). To make KCachegrind work with
Python profiles you also need the wrapper script
[pyprof2calltree](https://pypi.python.org/pypi/pyprof2calltree/). You can get pyprof2calltree via
`pip` whereas KCacheGrind is something you need to get via your package manager or their homepage.
How to analyze and interpret profiling data is not a trivial issue and depends on what you are profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing list if you need help and be ready to be able to supply your `server.prof` file for comparison, along with the exact conditions under which it was obtained.
How to analyze and interpret profiling data is not a trivial issue and depends on what you are
profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing
list if you need help and be ready to be able to supply your `server.prof` file for comparison,
along with the exact conditions under which it was obtained.
## The Dummyrunner
It is difficult to test "actual" game performance without having players in your game. For this reason Evennia comes with the *Dummyrunner* system. The Dummyrunner is a stress-testing system: a separate program that logs into your game with simulated players (aka "bots" or "dummies"). Once connected these dummies will semi-randomly perform various tasks from a list of possible actions. Use `Ctrl-C` to stop the Dummyrunner.
It is difficult to test "actual" game performance without having players in your game. For this
reason Evennia comes with the *Dummyrunner* system. The Dummyrunner is a stress-testing system: a
separate program that logs into your game with simulated players (aka "bots" or "dummies"). Once
connected these dummies will semi-randomly perform various tasks from a list of possible actions.
Use `Ctrl-C` to stop the Dummyrunner.
> Warning: You should not run the Dummyrunner on a production database. It will spawn many objects and also needs to run with general permissions.
> Warning: You should not run the Dummyrunner on a production database. It will spawn many objects
and also needs to run with general permissions.
To launch the Dummyrunner, first start your server normally (with or without profiling, as above). Then start a new terminal/console window and active your virtualenv there too. In the new terminal, try to connect 10 dummy players:
To launch the Dummyrunner, first start your server normally (with or without profiling, as above).
Then start a new terminal/console window and active your virtualenv there too. In the new terminal,
try to connect 10 dummy players:
evennia --dummyrunner 10
The first time you do this you will most likely get a warning from Dummyrunner. It will tell you to copy an import string to the end of your settings file. Quit the Dummyrunner (`Ctrl-C`) and follow the instructions. Restart Evennia and try `evennia --dummyrunner 10` again. Make sure to remove that extra settings line when running a public server.
The first time you do this you will most likely get a warning from Dummyrunner. It will tell you to
copy an import string to the end of your settings file. Quit the Dummyrunner (`Ctrl-C`) and follow
the instructions. Restart Evennia and try `evennia --dummyrunner 10` again. Make sure to remove that
extra settings line when running a public server.
The actions perform by the dummies is controlled by a settings file. The default Dummyrunner settings file is `evennia/server/server/profiling/dummyrunner_settings.py` but you shouldn't modify this directly. Rather create/copy the default file to `mygame/server/conf/` and modify it there. To make sure to use your file over the default, add the following line to your settings file:
The actions perform by the dummies is controlled by a settings file. The default Dummyrunner
settings file is `evennia/server/server/profiling/dummyrunner_settings.py` but you shouldn't modify
this directly. Rather create/copy the default file to `mygame/server/conf/` and modify it there. To
make sure to use your file over the default, add the following line to your settings file:
```python
DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py"
```
> Hint: Don't start with too many dummies. The Dummyrunner defaults to taxing the server much more intensely than an equal number of human players. A good dummy number to start with is 10-100.
> Hint: Don't start with too many dummies. The Dummyrunner defaults to taxing the server much more
intensely than an equal number of human players. A good dummy number to start with is 10-100.
Once you have the dummyrunner running, stop it with `Ctrl-C`.
Generally, the dummyrunner system makes for a decent test of general performance; but it is of
course hard to actually mimic human user behavior. For this, actual real-game testing is required.
course hard to actually mimic human user behavior. For this, actual real-game testing is required.

View file

@ -1,6 +1,7 @@
# Python 3
> *Note: Evennia only supports Python 2.7+ at this time. This page gathers various development information relevant to server developers.*
> *Note: Evennia only supports Python 2.7+ at this time. This page gathers various development
information relevant to server developers.*
Django can work with Python 2 and 3 already, though changes may be required to how the Evennia code
uses it. Twisted has much Python 3 compatibility, but not all modules within it have been ported
@ -17,16 +18,20 @@ it still works correctly with Twisted on Python 3.
# "Strings"
Broadly (and perhaps over-simplified):
* Twisted [expects bytes](http://twistedmatrix.com/trac/wiki/FrequentlyAskedQuestions#WhydontTwistedsnetworkmethodssupportUnicodeobjectsaswellasstrings)
* Django [expects "" to be unicode](https://docs.djangoproject.com/en/1.8/topics/python3/#unicode-literals)
* Twisted [expects
bytes](http://twistedmatrix.com/trac/wiki/FrequentlyAskedQuestions#WhydontTwistedsnetworkmethodssupportUnicodeobjectsaswellasstrings)
* Django [expects "" to be unicode](https://docs.djangoproject.com/en/1.8/topics/python3/#unicode-
literals)
I think we should use (roughly speaking) "" for unicode and b"" for bytes everywhere, but I need to look at the impacts of this more closely.
I think we should use (roughly speaking) "" for unicode and b"" for bytes everywhere, but I need to
look at the impacts of this more closely.
# Links
* http://twistedmatrix.com/documents/current/core/howto/python3.html
* https://twistedmatrix.com/trac/wiki/Plan/Python3
* [Twisted Python3 bugs](https://twistedmatrix.com/trac/query?status=assigned&status=new&status=reopened&group=status&milestone=Python-3.x)
* [Twisted Python3
bugs](https://twistedmatrix.com/trac/query?status=assigned&status=new&status=reopened&group=status&milestone=Python-3.x)
# Twisted module status
@ -79,4 +84,4 @@ x = not ported to Python 3
* https://twistedmatrix.com/trac/ticket/6320
* "Python 3 support for twisted.words.protocols.irc"
* ~https://twistedmatrix.com/trac/ticket/6564~
* "Replace usage of builtin reduce in twisted.words"
* "Replace usage of builtin reduce in twisted.words"

View file

@ -1,6 +1,10 @@
# Python basic introduction
This is the first part of our beginner's guide to the basics of using Python with Evennia. It's aimed at you with limited or no programming/Python experience. But also if you are an experienced programmer new to Evennia or Python you might still pick up a thing or two. It is by necessity brief and low on detail. There are countless Python guides and tutorials, books and videos out there for learning more in-depth - use them!
This is the first part of our beginner's guide to the basics of using Python with Evennia. It's
aimed at you with limited or no programming/Python experience. But also if you are an experienced
programmer new to Evennia or Python you might still pick up a thing or two. It is by necessity brief
and low on detail. There are countless Python guides and tutorials, books and videos out there for
learning more in-depth - use them!
**Contents:**
- [Evennia Hello world](Python-basic-introduction#evennia-hello-world)
@ -10,13 +14,17 @@ This is the first part of our beginner's guide to the basics of using Python wit
- [Looking at the log](Python-basic-introduction#looking-at-the-log)
- (continued in [part 2](Python-basic-tutorial-part-two))
This quickstart assumes you have [gotten Evennia started](Getting-Started). You should make sure that you are able to see the output from the server in the console from which you started it. Log into the game either with a mud client on `localhost:4000` or by pointing a web browser to `localhost:4001/webclient`. Log in as your superuser (the user you created during install).
This quickstart assumes you have [gotten Evennia started](Getting-Started). You should make sure
that you are able to see the output from the server in the console from which you started it. Log
into the game either with a mud client on `localhost:4000` or by pointing a web browser to
`localhost:4001/webclient`. Log in as your superuser (the user you created during install).
Below, lines starting with a single `>` means command input.
### Evennia Hello world
The `py` (or `!` which is an alias) command allows you as a superuser to run raw Python from in-game. From the game's input line, enter the following:
The `py` (or `!` which is an alias) command allows you as a superuser to run raw Python from in-
game. From the game's input line, enter the following:
> py print("Hello World!")
@ -25,28 +33,48 @@ You will see
> print("Hello world!")
Hello World
To understand what is going on: some extra info: The `print(...)` *function* is the basic, in-built way to output text in Python. The quotes `"..."` means you are inputing a *string* (i.e. text). You could also have used single-quotes `'...'`, Python accepts both.
To understand what is going on: some extra info: The `print(...)` *function* is the basic, in-built
way to output text in Python. The quotes `"..."` means you are inputing a *string* (i.e. text). You
could also have used single-quotes `'...'`, Python accepts both.
The first return line (with `>>>`) is just `py` echoing what you input (we won't include that in the examples henceforth).
The first return line (with `>>>`) is just `py` echoing what you input (we won't include that in the
examples henceforth).
> Note: You may sometimes see people/docs refer to `@py` or other commands starting with `@`. Evennia ignores `@` by default, so `@py` is the exact same thing as `py`.
> Note: You may sometimes see people/docs refer to `@py` or other commands starting with `@`.
Evennia ignores `@` by default, so `@py` is the exact same thing as `py`.
The `print` command is a standard Python structure. We can use that here in the `py` command, and it's great for debugging and quick testing. But if you need to send a text to an actual player, `print` won't do, because it doesn't know _who_ to send to. Try this:
The `print` command is a standard Python structure. We can use that here in the `py` command, and
it's great for debugging and quick testing. But if you need to send a text to an actual player,
`print` won't do, because it doesn't know _who_ to send to. Try this:
> py me.msg("Hello world!")
Hello world!
This looks the same as the `print` result, but we are now actually messaging a specific *object*, `me`. The `me` is something uniquely available in the `py` command (we could also use `self`, it's an alias). It represents "us", the ones calling the `py` command. The `me` is an example of an *Object instance*. Objects are fundamental in Python and Evennia. The `me` object not only represents the character we play in the game, it also contains a lot of useful resources for doing things with that Object. One such resource is `msg`. `msg` works like `print` except it sends the text to the object it is attached to. So if we, for example, had an object `you`, doing `you.msg(...)` would send a message to the object `you`.
This looks the same as the `print` result, but we are now actually messaging a specific *object*,
`me`. The `me` is something uniquely available in the `py` command (we could also use `self`, it's
an alias). It represents "us", the ones calling the `py` command. The `me` is an example of an
*Object instance*. Objects are fundamental in Python and Evennia. The `me` object not only
represents the character we play in the game, it also contains a lot of useful resources for doing
things with that Object. One such resource is `msg`. `msg` works like `print` except it sends the
text to the object it is attached to. So if we, for example, had an object `you`, doing
`you.msg(...)` would send a message to the object `you`.
You access an Object's resources by using the full-stop character `.`. So `self.msg` accesses the `msg` resource and then we call it like we did print, with our "Hello World!" greeting in parentheses.
You access an Object's resources by using the full-stop character `.`. So `self.msg` accesses the
`msg` resource and then we call it like we did print, with our "Hello World!" greeting in
parentheses.
> Important: something like `print(...)` we refer to as a *function*, while `msg(...)` which sits on an object is called a *method*.
> Important: something like `print(...)` we refer to as a *function*, while `msg(...)` which sits on
an object is called a *method*.
For now, `print` and `me.msg` behaves the same, just remember that you're going to mostly be using the latter in the future. Try printing other things. Also try to include `|r` at the start of your string to make the output red in-game. Use `color` to learn more color tags.
For now, `print` and `me.msg` behaves the same, just remember that you're going to mostly be using
the latter in the future. Try printing other things. Also try to include `|r` at the start of your
string to make the output red in-game. Use `color` to learn more color tags.
### Importing modules
Keep your game running, then open a text editor of your choice. If your game folder is called `mygame`, create a new text file `test.py` in the subfolder `mygame/world`. This is how the file structure should look:
Keep your game running, then open a text editor of your choice. If your game folder is called
`mygame`, create a new text file `test.py` in the subfolder `mygame/world`. This is how the file
structure should look:
```
mygame/
@ -60,26 +88,40 @@ For now, only add one line to `test.py`:
print("Hello World!")
```
Don't forget to save the file. A file with the ending `.py` is referred to as a Python *module*. To use this in-game we have to *import* it. Try this:
Don't forget to save the file. A file with the ending `.py` is referred to as a Python *module*. To
use this in-game we have to *import* it. Try this:
```python
> @py import world.test
Hello World
```
If you make some error (we'll cover how to handle errors below) you may need to run the `@reload` command for your changes to take effect.
If you make some error (we'll cover how to handle errors below) you may need to run the `@reload`
command for your changes to take effect.
So importing `world.test` actually means importing `world/test.py`. Think of the period `.` as replacing `/` (or `\` for Windows) in your path. The `.py` ending of `test.py` is also never included in this "Python-path", but _only_ files with that ending can be imported this way. Where is `mygame` in that Python-path? The answer is that Evennia has already told Python that your `mygame` folder is a good place to look for imports. So we don't include `mygame` in the path - Evennia handles this for us.
So importing `world.test` actually means importing `world/test.py`. Think of the period `.` as
replacing `/` (or `\` for Windows) in your path. The `.py` ending of `test.py` is also never
included in this "Python-path", but _only_ files with that ending can be imported this way. Where is
`mygame` in that Python-path? The answer is that Evennia has already told Python that your `mygame`
folder is a good place to look for imports. So we don't include `mygame` in the path - Evennia
handles this for us.
When you import the module, the top "level" of it will execute. In this case, it will immediately print "Hello World".
When you import the module, the top "level" of it will execute. In this case, it will immediately
print "Hello World".
> If you look in the folder you'll also often find new files ending with `.pyc`. These are compiled Python binaries that Python auto-creates when running code. Just ignore them, you should never edit those anyway.
> If you look in the folder you'll also often find new files ending with `.pyc`. These are compiled
Python binaries that Python auto-creates when running code. Just ignore them, you should never edit
those anyway.
Now try to run this a second time:
```python
> py import world.test
```
You will *not* see any output this second time or any subsequent times! This is not a bug. Rather it is because Python is being clever - it stores all imported modules and to be efficient it will avoid importing them more than once. So your `print` will only run the first time, when the module is first imported. To see it again you need to `@reload` first, so Python forgets about the module and has to import it again.
You will *not* see any output this second time or any subsequent times! This is not a bug. Rather
it is because Python is being clever - it stores all imported modules and to be efficient it will
avoid importing them more than once. So your `print` will only run the first time, when the module
is first imported. To see it again you need to `@reload` first, so Python forgets about the module
and has to import it again.
We'll get back to importing code in the second part of this tutorial. For now, let's press on.
@ -92,7 +134,8 @@ me.msg("Hello World!")
```
As you recall we used this from `py` earlier - it echoed "Hello World!" in-game.
Save your file and `reload` your server - this makes sure Evennia sees the new version of your code. Try to import it from `py` in the same way as earlier:
Save your file and `reload` your server - this makes sure Evennia sees the new version of your code.
Try to import it from `py` in the same way as earlier:
```python
> py import world.test
@ -106,20 +149,33 @@ File "./world/test.py", line 1, in <module>
NameError: name 'me' is not defined
```
This is called a *traceback*. Python's errors are very friendly and will most of the time tell you exactly what and where things are wrong. It's important that you learn to parse tracebacks so you can fix your code. Let's look at this one. A traceback is to be read from the _bottom up_. The last line is the error Python balked at, while the two lines above it details exactly where that error was encountered.
This is called a *traceback*. Python's errors are very friendly and will most of the time tell you
exactly what and where things are wrong. It's important that you learn to parse tracebacks so you
can fix your code. Let's look at this one. A traceback is to be read from the _bottom up_. The last
line is the error Python balked at, while the two lines above it details exactly where that error
was encountered.
1. An error of type `NameError` is the problem ...
2. ... more specifically it is due to the variable `me` not being defined.
3. This happened on the line `me.msg("Hello world!")` ...
4. ... which is on line `1` of the file `./world/test.py`.
In our case the traceback is short. There may be many more lines above it, tracking just how different modules called each other until it got to the faulty line. That can sometimes be useful information, but reading from the bottom is always a good start.
In our case the traceback is short. There may be many more lines above it, tracking just how
different modules called each other until it got to the faulty line. That can sometimes be useful
information, but reading from the bottom is always a good start.
The `NameError` we see here is due to a module being its own isolated thing. It knows nothing about the environment into which it is imported. It knew what `print` is because that is a special [reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html). But `me` is *not* such a reserved word. As far as the module is concerned `me` is just there out of nowhere. Hence the `NameError`.
The `NameError` we see here is due to a module being its own isolated thing. It knows nothing about
the environment into which it is imported. It knew what `print` is because that is a special
[reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html). But `me` is *not* such a
reserved word. As far as the module is concerned `me` is just there out of nowhere. Hence the
`NameError`.
### Our first function
Let's see if we can resolve that `NameError` from the previous section. We know that `me` is defined at the time we use the `@py` command because if we do `py me.msg("Hello World!")` directly in-game it works fine. What if we could *send* that `me` to the `test.py` module so it knows what it is? One way to do this is with a *function*.
Let's see if we can resolve that `NameError` from the previous section. We know that `me` is defined
at the time we use the `@py` command because if we do `py me.msg("Hello World!")` directly in-game
it works fine. What if we could *send* that `me` to the `test.py` module so it knows what it is? One
way to do this is with a *function*.
Change your `mygame/world/test.py` file to look like this:
@ -130,44 +186,82 @@ def hello_world(who):
Now that we are moving onto multi-line Python code, there are some important things to remember:
- Capitalization matters in Python. It must be `def` and not `DEF`, `who` is not the same as `Who` etc.
- Indentation matters in Python. The second line must be indented or it's not valid code. You should also use a consistent indentation length. We *strongly* recommend that you set up your editor to always indent *4 spaces* (**not** a single tab-character) when you press the TAB key - it will make your life a lot easier.
- `def` is short for "define" and defines a *function* (or a *method*, if sitting on an object). This is a [reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html); try not to use these words anywhere else.
- A function name can not have spaces but otherwise we could have called it almost anything. We call it `hello_world`. Evennia follows [Python's standard naming style](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#a-quick-list-of-code-style-points) with lowercase letters and underscores. Use this style for now.
- `who` is what we call the *argument* to our function. Arguments are variables we pass to the function. We could have named it anything and we could also have multiple arguments separated by commas. What `who` is depends on what we pass to this function when we *call* it later (hint: we'll pass `me` to it).
- The colon (`:`) at the end of the first line indicates that the header of the function is complete.
- The indentation marks the beginning of the actual operating code of the function (the function's *body*). If we wanted more lines to belong to this function those lines would all have to have to start at this indentation level.
- In the function body we take the `who` argument and treat it as we would have treated `me` earlier - we expect it to have a `.msg` method we can use to send "Hello World" to.
- Capitalization matters in Python. It must be `def` and not `DEF`, `who` is not the same as `Who`
etc.
- Indentation matters in Python. The second line must be indented or it's not valid code. You should
also use a consistent indentation length. We *strongly* recommend that you set up your editor to
always indent *4 spaces* (**not** a single tab-character) when you press the TAB key - it will make
your life a lot easier.
- `def` is short for "define" and defines a *function* (or a *method*, if sitting on an object).
This is a [reserved Python keyword](https://docs.python.org/2.5/ref/keywords.html); try not to use
these words anywhere else.
- A function name can not have spaces but otherwise we could have called it almost anything. We call
it `hello_world`. Evennia follows [Python's standard naming
style](https://github.com/evennia/evennia/blob/master/CODING_STYLE.md#a-quick-list-of-code-style-
points) with lowercase letters and underscores. Use this style for now.
- `who` is what we call the *argument* to our function. Arguments are variables we pass to the
function. We could have named it anything and we could also have multiple arguments separated by
commas. What `who` is depends on what we pass to this function when we *call* it later (hint: we'll
pass `me` to it).
- The colon (`:`) at the end of the first line indicates that the header of the function is
complete.
- The indentation marks the beginning of the actual operating code of the function (the function's
*body*). If we wanted more lines to belong to this function those lines would all have to have to
start at this indentation level.
- In the function body we take the `who` argument and treat it as we would have treated `me` earlier
- we expect it to have a `.msg` method we can use to send "Hello World" to.
First, `reload` your game to make it aware of the updated Python module. Now we have defined our first function, let's use it.
First, `reload` your game to make it aware of the updated Python module. Now we have defined our
first function, let's use it.
> reload
> py import world.test
Nothing happened! That is because the function in our module won't do anything just by importing it. It will only act when we *call* it. We will need to enter the module we just imported and do so.
Nothing happened! That is because the function in our module won't do anything just by importing it.
It will only act when we *call* it. We will need to enter the module we just imported and do so.
> py import world.test ; world.test.hello_world(me)
Hello world!
There is our "Hello World"! The `;` is the way to put multiple Python-statements on one line.
> Some MUD clients use `;` for their own purposes to separate client-inputs. If so you'll get a `NameError` stating that `world` is not defined. Check so you understand why this is! Change the use of `;` in your client or use the Evennia web client if this is a problem.
> Some MUD clients use `;` for their own purposes to separate client-inputs. If so you'll get a
`NameError` stating that `world` is not defined. Check so you understand why this is! Change the use
of `;` in your client or use the Evennia web client if this is a problem.
In the second statement we access the module path we imported (`world.test`) and reach for the `hello_world` function within. We *call* the function with `me`, which becomes the `who` variable we use inside the `hello_function`.
In the second statement we access the module path we imported (`world.test`) and reach for the
`hello_world` function within. We *call* the function with `me`, which becomes the `who` variable we
use inside the `hello_function`.
> As an exercise, try to pass something else into `hello_world`. Try for example to pass _who_ as the number `5` or the simple string `"foo"`. You'll get errors that they don't have the attribute `msg`. As we've seen, `me` *does* make `msg` available which is why it works (you'll learn more about Objects like `me` in the next part of this tutorial). If you are familiar with other programming languages you may be tempted to start *validating* `who` to make sure it works as expected. This is usually not recommended in Python which suggests it's better to [handle](https://docs.python.org/2/tutorial/errors.html) the error if it happens rather than to make a lot of code to prevent it from happening. See also [duck typing](https://en.wikipedia.org/wiki/Duck_typing).
> As an exercise, try to pass something else into `hello_world`. Try for example to pass _who_ as
the number `5` or the simple string `"foo"`. You'll get errors that they don't have the attribute
`msg`. As we've seen, `me` *does* make `msg` available which is why it works (you'll learn more
about Objects like `me` in the next part of this tutorial). If you are familiar with other
programming languages you may be tempted to start *validating* `who` to make sure it works as
expected. This is usually not recommended in Python which suggests it's better to
[handle](https://docs.python.org/2/tutorial/errors.html) the error if it happens rather than to make
a lot of code to prevent it from happening. See also [duck
typing](https://en.wikipedia.org/wiki/Duck_typing).
# Looking at the log
As you start to explore Evennia, it's important that you know where to look when things go wrong. While using the friendly `py` command you'll see errors directly in-game. But if something goes wrong in your code while the game runs, you must know where to find the _log_.
As you start to explore Evennia, it's important that you know where to look when things go wrong.
While using the friendly `py` command you'll see errors directly in-game. But if something goes
wrong in your code while the game runs, you must know where to find the _log_.
Open a terminal (or go back to the terminal you started Evennia in), make sure your `virtualenv` is active and that you are standing in your game directory (the one created with `evennia --init` during installation). Enter
Open a terminal (or go back to the terminal you started Evennia in), make sure your `virtualenv` is
active and that you are standing in your game directory (the one created with `evennia --init`
during installation). Enter
```
evennia --log
```
(or `evennia -l`)
This will show the log. New entries will show up in real time. Whenever you want to leave the log, enter `Ctrl-C` or `Cmd-C` depending on your system. As a game dev it is important to look at the log output when working in Evennia - many errors will only appear with full details here. You may sometimes have to scroll up in the history if you miss it.
This will show the log. New entries will show up in real time. Whenever you want to leave the log,
enter `Ctrl-C` or `Cmd-C` depending on your system. As a game dev it is important to look at the
log output when working in Evennia - many errors will only appear with full details here. You may
sometimes have to scroll up in the history if you miss it.
This tutorial is continued in [Part 2](Python-basic-tutorial-part-two), where we'll start learning about objects and to explore the Evennia library.
This tutorial is continued in [Part 2](Python-basic-tutorial-part-two), where we'll start learning
about objects and to explore the Evennia library.

View file

@ -1,6 +1,9 @@
# Python basic tutorial part two
[In the first part](Python-basic-introduction) of this Python-for-Evennia basic tutorial we learned how to run some simple Python code from inside the game. We also made our first new *module* containing a *function* that we called. Now we're going to start exploring the very important subject of *objects*.
[In the first part](Python-basic-introduction) of this Python-for-Evennia basic tutorial we learned
how to run some simple Python code from inside the game. We also made our first new *module*
containing a *function* that we called. Now we're going to start exploring the very important
subject of *objects*.
**Contents:**
- [On the subject of objects](Python-basic-tutorial-part-two#on-the-subject-of-objects)
@ -15,9 +18,11 @@ In the first part of the tutorial we did things like
> py me.msg("Hello World!")
To learn about functions and imports we also passed that `me` on to a function `hello_world` in another module.
To learn about functions and imports we also passed that `me` on to a function `hello_world` in
another module.
Let's learn some more about this `me` thing we are passing around all over the place. In the following we assume that we named our superuser Character "Christine".
Let's learn some more about this `me` thing we are passing around all over the place. In the
following we assume that we named our superuser Character "Christine".
> py me
Christine
@ -31,30 +36,53 @@ These returns look the same at first glance, but not if we examine them more clo
> py type(me.key)
<type str>
> Note: In some MU clients, such as Mudlet and MUSHclient simply returning `type(me)`, you may not see the proper return from the above commands. This is likely due to the HTML-like tags `<...>`, being swallowed by the client.
> Note: In some MU clients, such as Mudlet and MUSHclient simply returning `type(me)`, you may not
see the proper return from the above commands. This is likely due to the HTML-like tags `<...>`,
being swallowed by the client.
The `type` function is, like `print`, another in-built function in Python. It
tells us that we (`me`) are of the *class* `typeclasses.characters.Character`.
Meanwhile `me.key` is a *property* on us, a string. It holds the name of this
object.
> When you do `py me`, the `me` is defined in such a way that it will use its `.key` property to represent itself. That is why the result is the same as when doing `py me.key`. Also, remember that as noted in the first part of the tutorial, the `me` is *not* a reserved Python word; it was just defined by the Evennia developers as a convenient short-hand when creating the `py` command. So don't expect `me` to be available elsewhere.
> When you do `py me`, the `me` is defined in such a way that it will use its `.key` property to
represent itself. That is why the result is the same as when doing `py me.key`. Also, remember that
as noted in the first part of the tutorial, the `me` is *not* a reserved Python word; it was just
defined by the Evennia developers as a convenient short-hand when creating the `py` command. So
don't expect `me` to be available elsewhere.
A *class* is like a "factory" or blueprint. From a class you then create individual *instances*. So if class is`Dog`, an instance of `Dog` might be `fido`. Our in-game persona is of a class `Character`. The superuser `christine` is an *instance* of the `Character` class (an instance is also often referred to as an *object*). This is an important concept in *object oriented programming*. You are wise to [familiarize yourself with it](https://en.wikipedia.org/wiki/Class-based_programming) a little.
A *class* is like a "factory" or blueprint. From a class you then create individual *instances*. So
if class is`Dog`, an instance of `Dog` might be `fido`. Our in-game persona is of a class
`Character`. The superuser `christine` is an *instance* of the `Character` class (an instance is
also often referred to as an *object*). This is an important concept in *object oriented
programming*. You are wise to [familiarize yourself with it](https://en.wikipedia.org/wiki/Class-
based_programming) a little.
> In other terms:
> * class: A description of a thing, all the methods (code) and data (information)
> * object: A thing, defined as an *instance* of a class.
>
> So in "Fido is a Dog", "Fido" is an object--a unique thing--and "Dog" is a class. Coders would also say, "Fido is an instance of Dog". There can be other dogs too, such as Butch and Fifi. They, too, would be instances of Dog.
> So in "Fido is a Dog", "Fido" is an object--a unique thing--and "Dog" is a class. Coders would
also say, "Fido is an instance of Dog". There can be other dogs too, such as Butch and Fifi. They,
too, would be instances of Dog.
>
> As another example: "Christine is a Character", or "Christine is an instance of typeclasses.characters.Character". To start, all characters will be instances of typeclass.characters.Character.
> As another example: "Christine is a Character", or "Christine is an instance of
typeclasses.characters.Character". To start, all characters will be instances of
typeclass.characters.Character.
>
> You'll be writing your own class soon! The important thing to know here is how classes and objects relate.
> You'll be writing your own class soon! The important thing to know here is how classes and objects
relate.
The string `'typeclasses.characters.Character'` we got from the `type()` function is not arbitrary. You'll recognize this from when we _imported_ `world.test` in part one. This is a _path_ exactly describing where to find the python code describing this class. Python treats source code files on your hard drive (known as *modules*) as well as folders (known as *packages*) as objects that you access with the `.` operator. It starts looking at a place that Evennia has set up for you - namely the root of your own game directory.
The string `'typeclasses.characters.Character'` we got from the `type()` function is not arbitrary.
You'll recognize this from when we _imported_ `world.test` in part one. This is a _path_ exactly
describing where to find the python code describing this class. Python treats source code files on
your hard drive (known as *modules*) as well as folders (known as *packages*) as objects that you
access with the `.` operator. It starts looking at a place that Evennia has set up for you - namely
the root of your own game directory.
Open and look at your game folder (named `mygame` if you exactly followed the Getting Started instructions) in a file editor or in a new terminal/console. Locate the file `mygame/typeclasses/characters.py`
Open and look at your game folder (named `mygame` if you exactly followed the Getting Started
instructions) in a file editor or in a new terminal/console. Locate the file
`mygame/typeclasses/characters.py`
```
mygame/
@ -62,7 +90,9 @@ mygame/
characters.py
```
This represents the first part of the python path - `typeclasses.characters` (the `.py` file ending is never included in the python path). The last bit, `.Character` is the actual class name inside the `characters.py` module. Open that file in a text editor and you will see something like this:
This represents the first part of the python path - `typeclasses.characters` (the `.py` file ending
is never included in the python path). The last bit, `.Character` is the actual class name inside
the `characters.py` module. Open that file in a text editor and you will see something like this:
```python
"""
@ -79,9 +109,18 @@ class Character(DefaultCharacter):
```
There is `Character`, the last part of the path. Note how empty this file is. At first glance one would think a Character had no functionality at all. But from what we have used already we know it has at least the `key` property and the method `msg`! Where is the code? The answer is that this 'emptiness' is an illusion caused by something called *inheritance*. Read on.
There is `Character`, the last part of the path. Note how empty this file is. At first glance one
would think a Character had no functionality at all. But from what we have used already we know it
has at least the `key` property and the method `msg`! Where is the code? The answer is that this
'emptiness' is an illusion caused by something called *inheritance*. Read on.
Firstly, in the same way as the little `hello.py` we did in the first part of the tutorial, this is an example of full, multi-line Python code. Those triple-quoted strings are used for strings that have line breaks in them. When they appear on their own like this, at the top of a python module, class or similar they are called *doc strings*. Doc strings are read by Python and is used for producing online help about the function/method/class/module. By contrast, a line starting with `#` is a *comment*. It is ignored completely by Python and is only useful to help guide a human to understand the code.
Firstly, in the same way as the little `hello.py` we did in the first part of the tutorial, this is
an example of full, multi-line Python code. Those triple-quoted strings are used for strings that
have line breaks in them. When they appear on their own like this, at the top of a python module,
class or similar they are called *doc strings*. Doc strings are read by Python and is used for
producing online help about the function/method/class/module. By contrast, a line starting with `#`
is a *comment*. It is ignored completely by Python and is only useful to help guide a human to
understand the code.
The line
@ -89,21 +128,35 @@ The line
class Character(DefaultCharacter):
```
means that the class `Character` is a *child* of the class `DefaultCharacter`. This is called *inheritance* and is another fundamental concept. The answer to the question "where is the code?" is that the code is *inherited* from its parent, `DefaultCharacter`. And that in turn may inherit code from *its* parent(s) and so on. Since our child, `Character` is empty, its functionality is *exactly identical* to that of its parent. The moment we add new things to Character, these will take precedence. And if we add something that already existed in the parent, our child-version will *override* the version in the parent. This is very practical: It means that we can let the parent do the heavy lifting and only tweak the things we want to change. It also means that we could easily have many different Character classes, all inheriting from `DefaultCharacter` but changing different things. And those can in turn also have children ...
means that the class `Character` is a *child* of the class `DefaultCharacter`. This is called
*inheritance* and is another fundamental concept. The answer to the question "where is the code?" is
that the code is *inherited* from its parent, `DefaultCharacter`. And that in turn may inherit code
from *its* parent(s) and so on. Since our child, `Character` is empty, its functionality is *exactly
identical* to that of its parent. The moment we add new things to Character, these will take
precedence. And if we add something that already existed in the parent, our child-version will
*override* the version in the parent. This is very practical: It means that we can let the parent do
the heavy lifting and only tweak the things we want to change. It also means that we could easily
have many different Character classes, all inheriting from `DefaultCharacter` but changing different
things. And those can in turn also have children ...
Let's go on an expedition up the inheritance tree.
### Exploring the Evennia library
Let's figure out how to tweak `Character`. Right now we don't know much about `DefaultCharacter` though. Without knowing that we won't know what to override. At the top of the file you find
Let's figure out how to tweak `Character`. Right now we don't know much about `DefaultCharacter`
though. Without knowing that we won't know what to override. At the top of the file you find
```python
from evennia import DefaultCharacter
```
This is an `import` statement again, but on a different form to what we've seen before. `from ... import ...` is very commonly used and allows you to precisely dip into a module to extract just the component you need to use. In this case we head into the `evennia` package to get `DefaultCharacter`.
This is an `import` statement again, but on a different form to what we've seen before. `from ...
import ...` is very commonly used and allows you to precisely dip into a module to extract just the
component you need to use. In this case we head into the `evennia` package to get
`DefaultCharacter`.
Where is `evennia`? To find it you need to go to the `evennia` folder (repository) you originally cloned from us. If you open it, this is how it looks:
Where is `evennia`? To find it you need to go to the `evennia` folder (repository) you originally
cloned from us. If you open it, this is how it looks:
```
evennia/
@ -114,24 +167,48 @@ evennia/
evennia/
...
```
There are lots of things in there. There are some docs but most of those have to do with the distribution of Evennia and does not concern us right now. The `evennia` subfolder is what we are looking for. *This* is what you are accessing when you do `from evennia import ...`. It's set up by Evennia as a good place to find modules when the server starts. The exact layout of the Evennia library [is covered by our directory overview](Directory-Overview#evennia-library-layout). You can also explore it [online on github](https://github.com/evennia/evennia/tree/master/evennia).
There are lots of things in there. There are some docs but most of those have to do with the
distribution of Evennia and does not concern us right now. The `evennia` subfolder is what we are
looking for. *This* is what you are accessing when you do `from evennia import ...`. It's set up by
Evennia as a good place to find modules when the server starts. The exact layout of the Evennia
library [is covered by our directory overview](Directory-Overview#evennia-library-layout). You can
also explore it [online on github](https://github.com/evennia/evennia/tree/master/evennia).
The structure of the library directly reflects how you import from it.
- To, for example, import [the text justify function](https://github.com/evennia/evennia/blob/master/evennia/utils/utils.py#L201) from `evennia/utils/utils.py` you would do `from evennia.utils.utils import justify`. In your code you could then just call `justify(...)` to access its functionality.
- You could also do `from evennia.utils import utils`. In code you would then have to write `utils.justify(...)`. This is practical if want a lot of stuff from that `utils.py` module and don't want to import each component separately.
- You could also do `import evennia`. You would then have to enter the full `evennia.utils.utils.justify(...)` every time you use it. Using `from` to only import the things you need is usually easier and more readable.
- See [this overview](http://effbot.org/zone/import-confusion.htm) about the different ways to import in Python.
- To, for example, import [the text justify
function](https://github.com/evennia/evennia/blob/master/evennia/utils/utils.py#L201) from
`evennia/utils/utils.py` you would do `from evennia.utils.utils import justify`. In your code you
could then just call `justify(...)` to access its functionality.
- You could also do `from evennia.utils import utils`. In code you would then have to write
`utils.justify(...)`. This is practical if want a lot of stuff from that `utils.py` module and don't
want to import each component separately.
- You could also do `import evennia`. You would then have to enter the full
`evennia.utils.utils.justify(...)` every time you use it. Using `from` to only import the things you
need is usually easier and more readable.
- See [this overview](http://effbot.org/zone/import-confusion.htm) about the different ways to
import in Python.
Now, remember that our `characters.py` module did `from evennia import DefaultCharacter`. But if we look at the contents of the `evennia` folder, there is no `DefaultCharacter` anywhere! This is because Evennia gives a large number of optional "shortcuts", known as [the "flat" API](Evennia-API). The intention is to make it easier to remember where to find stuff. The flat API is defined in that weirdly named `__init__.py` file. This file just basically imports useful things from all over Evennia so you can more easily find them in one place.
Now, remember that our `characters.py` module did `from evennia import DefaultCharacter`. But if we
look at the contents of the `evennia` folder, there is no `DefaultCharacter` anywhere! This is
because Evennia gives a large number of optional "shortcuts", known as [the "flat" API](Evennia-
API). The intention is to make it easier to remember where to find stuff. The flat API is defined in
that weirdly named `__init__.py` file. This file just basically imports useful things from all over
Evennia so you can more easily find them in one place.
We could [just look at the documenation](github:evennia#typeclasses) to find out where we can look at our `DefaultCharacter` parent. But for practice, let's figure it out. Here is where `DefaultCharacter` [is imported from](https://github.com/evennia/evennia/blob/master/evennia/__init__.py#L188) inside `__init__.py`:
We could [just look at the documenation](github:evennia#typeclasses) to find out where we can look
at our `DefaultCharacter` parent. But for practice, let's figure it out. Here is where
`DefaultCharacter` [is imported
from](https://github.com/evennia/evennia/blob/master/evennia/__init__.py#L188) inside `__init__.py`:
```python
from .objects.objects import DefaultCharacter
```
The period at the start means that it imports beginning from the same location this module sits(i.e. the `evennia` folder). The full python-path accessible from the outside is thus `evennia.objects.objects.DefaultCharacter`. So to import this into our game it'd be perfectly valid to do
The period at the start means that it imports beginning from the same location this module sits(i.e.
the `evennia` folder). The full python-path accessible from the outside is thus
`evennia.objects.objects.DefaultCharacter`. So to import this into our game it'd be perfectly valid
to do
```python
from evennia.objects.objects import DefaultCharacter
@ -177,7 +254,8 @@ class DefaultCharacter(DefaultObject):
"""
super().basetype_setup()
self.locks.add(";".join(["get:false()", # noone can pick up the character
"call:false()"])) # no commands can be called on character from outside
"call:false()"])) # no commands can be called on character from
outside
# add the default cmdset
self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
@ -197,14 +275,29 @@ class DefaultCharacter(DefaultObject):
```
... And so on (you can see the full [class online here](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L1915)). Here we have functional code! These methods may not be directly visible in `Character` back in our game dir, but they are still available since `Character` is a child of `DefaultCharacter` above. Here is a brief summary of the methods we find in `DefaultCharacter` (follow in the code to see if you can see roughly where things happen)::
... And so on (you can see the full [class online
here](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L1915)). Here we
have functional code! These methods may not be directly visible in `Character` back in our game dir,
but they are still available since `Character` is a child of `DefaultCharacter` above. Here is a
brief summary of the methods we find in `DefaultCharacter` (follow in the code to see if you can see
roughly where things happen)::
- `basetype_setup` is called by Evennia only once, when a Character is first created. In the `DefaultCharacter` class it sets some particular [Locks](Locks) so that people can't pick up and puppet Characters just like that. It also adds the [Character Cmdset](Command-Sets) so that Characters always can accept command-input (this should usually not be modified - the normal hook to override is `at_object_creation`, which is called after `basetype_setup` (it's in the parent)).
- `at_after_move` makes it so that every time the Character moves, the `look` command is automatically fired (this would not make sense for just any regular Object).
- `at_pre_puppet` is called when an Account begins to puppet this Character. When not puppeted, the Character is hidden away to a `None` location. This brings it back to the location it was in before. Without this, "headless" Characters would remain in the game world just standing around.
- `at_post_puppet` is called when puppeting is complete. It echoes a message to the room that his Character has now connected.
- `at_post_unpuppet` is called once stopping puppeting of the Character. This hides away the Character to a `None` location again.
- There are also some utility properties which makes it easier to get some time stamps from the Character.
- `basetype_setup` is called by Evennia only once, when a Character is first created. In the
`DefaultCharacter` class it sets some particular [Locks](Locks) so that people can't pick up and
puppet Characters just like that. It also adds the [Character Cmdset](Command-Sets) so that
Characters always can accept command-input (this should usually not be modified - the normal hook to
override is `at_object_creation`, which is called after `basetype_setup` (it's in the parent)).
- `at_after_move` makes it so that every time the Character moves, the `look` command is
automatically fired (this would not make sense for just any regular Object).
- `at_pre_puppet` is called when an Account begins to puppet this Character. When not puppeted, the
Character is hidden away to a `None` location. This brings it back to the location it was in before.
Without this, "headless" Characters would remain in the game world just standing around.
- `at_post_puppet` is called when puppeting is complete. It echoes a message to the room that his
Character has now connected.
- `at_post_unpuppet` is called once stopping puppeting of the Character. This hides away the
Character to a `None` location again.
- There are also some utility properties which makes it easier to get some time stamps from the
Character.
Reading the class we notice another thing:
@ -213,24 +306,49 @@ class DefaultCharacter(DefaultObject):
# ...
```
This means that `DefaultCharacter` is in *itself* a child of something called `DefaultObject`! Let's see what this parent class provides. It's in the same module as `DefaultCharacter`, you just need to [scroll up near the top](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L182):
This means that `DefaultCharacter` is in *itself* a child of something called `DefaultObject`! Let's
see what this parent class provides. It's in the same module as `DefaultCharacter`, you just need to
[scroll up near the
top](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L182):
```python
class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# ...
```
This is a really big class where the bulk of code defining an in-game object resides. It consists of a large number of methods, all of which thus also becomes available on the `DefaultCharacter` class below *and* by extension in your `Character` class over in your game dir. In this class you can for example find the `msg` method we have been using before.
This is a really big class where the bulk of code defining an in-game object resides. It consists of
a large number of methods, all of which thus also becomes available on the `DefaultCharacter` class
below *and* by extension in your `Character` class over in your game dir. In this class you can for
example find the `msg` method we have been using before.
> You should probably not expect to understand all details yet, but as an exercise, find and read the doc string of `msg`.
> You should probably not expect to understand all details yet, but as an exercise, find and read
the doc string of `msg`.
> As seen, `DefaultObject` actually has multiple parents. In one of those the basic `key` property is defined, but we won't travel further up the inheritance tree in this tutorial. If you are interested to see them, you can find `TypeclassBase` in [evennia/typeclasses/models.py](https://github.com/evennia/evennia/blob/master/evennia/typeclasses/models.py#L93) and `ObjectDB` in [evennia/objects/models.py](https://github.com/evennia/evennia/blob/master/evennia/objects/models.py#L121). We will also not go into the details of [Multiple Inheritance](https://docs.python.org/2/tutorial/classes.html#multiple-inheritance) or [Metaclasses](http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html) here. The general rule is that if you realize that you need these features, you already know enough to use them.
> As seen, `DefaultObject` actually has multiple parents. In one of those the basic `key` property
is defined, but we won't travel further up the inheritance tree in this tutorial. If you are
interested to see them, you can find `TypeclassBase` in
[evennia/typeclasses/models.py](https://github.com/evennia/evennia/blob/master/evennia/typeclasses/models.py#L93)
and `ObjectDB` in
[evennia/objects/models.py](https://github.com/evennia/evennia/blob/master/evennia/objects/models.py#L121).
We will also not go into the details of [Multiple
Inheritance](https://docs.python.org/2/tutorial/classes.html#multiple-inheritance) or
[Metaclasses](http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html) here. The general rule
is that if you realize that you need these features, you already know enough to use them.
Remember the `at_pre_puppet` method we looked at in `DefaultCharacter`? If you look at the `at_pre_puppet` hook as defined in `DefaultObject` you'll find it to be completely empty (just a `pass`). So if you puppet a regular object it won't be hiding/retrieving the object when you unpuppet it. The `DefaultCharacter` class *overrides* its parent's functionality with a version of its own. And since it's `DefaultCharacter` that our `Character` class inherits back in our game dir, it's *that* version of `at_pre_puppet` we'll get. Anything not explicitly overridden will be passed down as-is.
Remember the `at_pre_puppet` method we looked at in `DefaultCharacter`? If you look at the
`at_pre_puppet` hook as defined in `DefaultObject` you'll find it to be completely empty (just a
`pass`). So if you puppet a regular object it won't be hiding/retrieving the object when you
unpuppet it. The `DefaultCharacter` class *overrides* its parent's functionality with a version of
its own. And since it's `DefaultCharacter` that our `Character` class inherits back in our game dir,
it's *that* version of `at_pre_puppet` we'll get. Anything not explicitly overridden will be passed
down as-is.
While it's useful to read the code, we should never actually modify anything inside the `evennia` folder. Only time you would want that is if you are planning to release a bug fix or new feature for Evennia itself. Instead you *override* the default functionality inside your game dir.
While it's useful to read the code, we should never actually modify anything inside the `evennia`
folder. Only time you would want that is if you are planning to release a bug fix or new feature for
Evennia itself. Instead you *override* the default functionality inside your game dir.
So to conclude our little foray into classes, objects and inheritance, locate the simple little `at_before_say` method in the `DefaultObject` class:
So to conclude our little foray into classes, objects and inheritance, locate the simple little
`at_before_say` method in the `DefaultObject` class:
```python
def at_before_say(self, message, **kwargs):
@ -240,24 +358,37 @@ So to conclude our little foray into classes, objects and inheritance, locate th
return message
```
If you read the doc string you'll find that this can be used to modify the output of `say` before it goes out. You can think of it like this: Evennia knows the name of this method, and when someone speaks, Evennia will make sure to redirect the outgoing message through this method. It makes it ripe for us to replace with a version of our own.
If you read the doc string you'll find that this can be used to modify the output of `say` before it
goes out. You can think of it like this: Evennia knows the name of this method, and when someone
speaks, Evennia will make sure to redirect the outgoing message through this method. It makes it
ripe for us to replace with a version of our own.
> In the Evennia documentation you may sometimes see the term *hook* used for a method explicitly meant to be overridden like this.
> In the Evennia documentation you may sometimes see the term *hook* used for a method explicitly
meant to be overridden like this.
As you can see, the first argument to `at_before_say` is `self`. In Python, the first argument of a method is *always a back-reference to the object instance on which the method is defined*. By convention this argument is always called `self` but it could in principle be named anything. The `self` is very useful. If you wanted to, say, send a message to the same object from inside `at_before_say`, you would do `self.msg(...)`.
As you can see, the first argument to `at_before_say` is `self`. In Python, the first argument of a
method is *always a back-reference to the object instance on which the method is defined*. By
convention this argument is always called `self` but it could in principle be named anything. The
`self` is very useful. If you wanted to, say, send a message to the same object from inside
`at_before_say`, you would do `self.msg(...)`.
What can trip up newcomers is that you *don't* include `self` when you *call* the method. Try:
> @py me.at_before_say("Hello World!")
Hello World!
Note that we don't send `self` but only the `message` argument. Python will automatically add `self` for us. In this case, `self` will become equal to the Character instance `me`.
Note that we don't send `self` but only the `message` argument. Python will automatically add `self`
for us. In this case, `self` will become equal to the Character instance `me`.
By default the `at_before_say` method doesn't do anything. It just takes the `message` input and `return`s it just the way it was (the `return` is another reserved Python word).
By default the `at_before_say` method doesn't do anything. It just takes the `message` input and
`return`s it just the way it was (the `return` is another reserved Python word).
> We won't go into `**kwargs` here, but it (and its sibling `*args`) is also important to understand, extra reading is [here for `**kwargs`](https://stackoverflow.com/questions/1769403/understanding-kwargs-in-python).
> We won't go into `**kwargs` here, but it (and its sibling `*args`) is also important to
understand, extra reading is [here for
`**kwargs`](https://stackoverflow.com/questions/1769403/understanding-kwargs-in-python).
Now, open your game folder and edit `mygame/typeclasses/characters.py`. Locate your `Character` class and modify it as such:
Now, open your game folder and edit `mygame/typeclasses/characters.py`. Locate your `Character`
class and modify it as such:
```python
class Character(DefaultCharacter):
@ -269,11 +400,21 @@ class Character(DefaultCharacter):
return f"{message} ..."
```
So we add our own version of `at_before_say`, duplicating the `def` line from the parent but putting new code in it. All we do in this tutorial is to add an ellipsis (`...`) to the message as it passes through the method.
So we add our own version of `at_before_say`, duplicating the `def` line from the parent but putting
new code in it. All we do in this tutorial is to add an ellipsis (`...`) to the message as it passes
through the method.
Note that `f` in front of the string, it means we turned the string into a 'formatted string'. We can now easily inject stuff directly into the string by wrapping them in curly brackets `{ }`. In this example, we put the incoming `message` into the string, followed by an ellipsis. This is only one way to format a string. Python has very powerful [string formatting](https://docs.python.org/2/library/string.html#format-specification-mini-language) and you are wise to learn it well, considering your game will be mainly text-based.
Note that `f` in front of the string, it means we turned the string into a 'formatted string'. We
can now easily inject stuff directly into the string by wrapping them in curly brackets `{ }`. In
this example, we put the incoming `message` into the string, followed by an ellipsis. This is only
one way to format a string. Python has very powerful [string
formatting](https://docs.python.org/2/library/string.html#format-specification-mini-language) and
you are wise to learn it well, considering your game will be mainly text-based.
> You could also copy & paste the relevant method from `DefaultObject` here to get the full doc string. For more complex methods, or if you only want to change some small part of the default behavior, copy & pasting will eliminate the need to constantly look up the original method and keep you sane.
> You could also copy & paste the relevant method from `DefaultObject` here to get the full doc
string. For more complex methods, or if you only want to change some small part of the default
behavior, copy & pasting will eliminate the need to constantly look up the original method and keep
you sane.
In-game, now try
@ -281,23 +422,35 @@ In-game, now try
> say Hello
You say, "Hello ..."
An ellipsis `...` is added to what you said! This is a silly example but you have just made your first code change to core functionality - without touching any of Evennia's original code! We just plugged in our own version of the `at_before_say` method and it replaced the default one. Evennia happily redirected the message through our version and we got a different output.
An ellipsis `...` is added to what you said! This is a silly example but you have just made your
first code change to core functionality - without touching any of Evennia's original code! We just
plugged in our own version of the `at_before_say` method and it replaced the default one. Evennia
happily redirected the message through our version and we got a different output.
> For sane overriding of parent methods you should also be aware of Python's [super](https://docs.python.org/3/library/functions.html#super), which allows you to call the methods defined on a parent in your child class.
> For sane overriding of parent methods you should also be aware of Python's
[super](https://docs.python.org/3/library/functions.html#super), which allows you to call the
methods defined on a parent in your child class.
### The Evennia shell
Now on to some generally useful tools as you continue learning Python and Evennia. We have so far explored using `py` and have inserted Python code directly in-game. We have also modified Evennia's behavior by overriding default functionality with our own. There is a third way to conveniently explore Evennia and Python - the Evennia shell.
Now on to some generally useful tools as you continue learning Python and Evennia. We have so far
explored using `py` and have inserted Python code directly in-game. We have also modified Evennia's
behavior by overriding default functionality with our own. There is a third way to conveniently
explore Evennia and Python - the Evennia shell.
Outside of your game, `cd` to your mygame folder and make sure any needed virtualenv is running. Next:
Outside of your game, `cd` to your mygame folder and make sure any needed virtualenv is running.
Next:
> pip install ipython # only needed once
The [`IPython`](https://en.wikipedia.org/wiki/IPython) program is just a nicer interface to the Python interpreter - you only need to install it once, after which Evennia will use it automatically.
The [`IPython`](https://en.wikipedia.org/wiki/IPython) program is just a nicer interface to the
Python interpreter - you only need to install it once, after which Evennia will use it
automatically.
> evennia shell
If you did this call from your game dir you will now be in a Python prompt managed by the IPython program.
If you did this call from your game dir you will now be in a Python prompt managed by the IPython
program.
IPython ...
...
@ -308,26 +461,46 @@ IPython has some very nice ways to explore what Evennia has to offer.
> import evennia
> evennia.<TAB>
That is, write `evennia.` and press the Tab key. You will be presented with a list of all available resources in the Evennia Flat API. We looked at the `__init__.py` file in the `evennia` folder earlier, so some of what you see should be familiar. From the IPython prompt, do:
That is, write `evennia.` and press the Tab key. You will be presented with a list of all available
resources in the Evennia Flat API. We looked at the `__init__.py` file in the `evennia` folder
earlier, so some of what you see should be familiar. From the IPython prompt, do:
> from evennia import DefaultCharacter
> DefaultCharacter.at_before_say?
Don't forget that you can use `<TAB>` to auto-complete code as you write. Appending a single `?` to the end will show you the doc-string for `at_before_say` we looked at earlier. Use `??` to get the whole source code.
Don't forget that you can use `<TAB>` to auto-complete code as you write. Appending a single `?` to
the end will show you the doc-string for `at_before_say` we looked at earlier. Use `??` to get the
whole source code.
Let's look at our over-ridden version instead. Since we started the `evennia shell` from our game dir we can easily get to our code too:
Let's look at our over-ridden version instead. Since we started the `evennia shell` from our game
dir we can easily get to our code too:
> from typeclasses.characters import Character
> Character.at_before_say??
This will show us the changed code we just did. Having a window with IPython running is very convenient for quickly exploring code without having to go digging through the file structure!
This will show us the changed code we just did. Having a window with IPython running is very
convenient for quickly exploring code without having to go digging through the file structure!
### Where to go from here
This should give you a running start using Python with Evennia. If you are completely new to programming or Python you might want to look at a more formal Python tutorial. You can find links and resources [on our link page](Links).
This should give you a running start using Python with Evennia. If you are completely new to
programming or Python you might want to look at a more formal Python tutorial. You can find links
and resources [on our link page](Links).
We have touched upon many of the concepts here but to use Evennia and to be able to follow along in the code, you will need basic understanding of Python [modules](http://docs.python.org/2/tutorial/modules.html), [variables](http://www.tutorialspoint.com/python/python_variable_types.htm), [conditional statements](http://docs.python.org/tutorial/controlflow.html#if-statements), [loops](http://docs.python.org/tutorial/controlflow.html#for-statements), [functions](http://docs.python.org/tutorial/controlflow.html#defining-functions), [lists, dictionaries, list comprehensions](http://docs.python.org/tutorial/datastructures.html) and [string formatting](http://docs.python.org/tutorial/introduction.html#strings). You should also have a basic understanding of [object-oriented programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) and what Python [Classes](http://docs.python.org/tutorial/classes.html) are.
We have touched upon many of the concepts here but to use Evennia and to be able to follow along in
the code, you will need basic understanding of Python
[modules](http://docs.python.org/2/tutorial/modules.html),
[variables](http://www.tutorialspoint.com/python/python_variable_types.htm), [conditional
statements](http://docs.python.org/tutorial/controlflow.html#if-statements),
[loops](http://docs.python.org/tutorial/controlflow.html#for-statements),
[functions](http://docs.python.org/tutorial/controlflow.html#defining-functions), [lists,
dictionaries, list comprehensions](http://docs.python.org/tutorial/datastructures.html) and [string
formatting](http://docs.python.org/tutorial/introduction.html#strings). You should also have a basic
understanding of [object-oriented
programming](http://www.tutorialspoint.com/python/python_classes_objects.htm) and what Python
[Classes](http://docs.python.org/tutorial/classes.html) are.
Once you have familiarized yourself, or if you prefer to pick Python up as you go, continue to one of the beginning-level [Evennia tutorials](Tutorials) to gradually build up your understanding.
Once you have familiarized yourself, or if you prefer to pick Python up as you go, continue to one
of the beginning-level [Evennia tutorials](Tutorials) to gradually build up your understanding.
Good luck!
Good luck!

View file

@ -1,19 +1,32 @@
# Quirks
This is a list of various quirks or common stumbling blocks that people often ask about or report when using (or trying to use) Evennia. They are not bugs.
This is a list of various quirks or common stumbling blocks that people often ask about or report
when using (or trying to use) Evennia. They are not bugs.
### Forgetting to use @reload to see changes to your typeclasses
Firstly: Reloading the server is a safe and usually quick operation which will *not* disconnect any accounts.
Firstly: Reloading the server is a safe and usually quick operation which will *not* disconnect any
accounts.
New users tend to forget this step. When editing source code (such as when tweaking typeclasses and commands or adding new commands to command sets) you need to either use the in-game `@reload` command or, from the command line do `python evennia.py reload` before you see your changes.
New users tend to forget this step. When editing source code (such as when tweaking typeclasses and
commands or adding new commands to command sets) you need to either use the in-game `@reload`
command or, from the command line do `python evennia.py reload` before you see your changes.
### Web admin to create new Account
If you use the default login system and are trying to use the Web admin to create a new Player account, you need to consider which `MULTIACCOUNT_MODE` you are in. If you are in `MULTIACCOUNT_MODE` `0` or `1`, the login system expects each Account to also have a Character object named the same as the Account - there is no character creation screen by default. If using the normal mud login screen, a Character with the same name is automatically created and connected to your Account. From the web interface you must do this manually.
If you use the default login system and are trying to use the Web admin to create a new Player
account, you need to consider which `MULTIACCOUNT_MODE` you are in. If you are in
`MULTIACCOUNT_MODE` `0` or `1`, the login system expects each Account to also have a Character
object named the same as the Account - there is no character creation screen by default. If using
the normal mud login screen, a Character with the same name is automatically created and connected
to your Account. From the web interface you must do this manually.
So, when creating the Account, make sure to also create the Character *from the same form* as you create the Account from. This should set everything up for you. Otherwise you need to manually set the "account" property on the Character and the "character" property on the Account to point to each other. You must also set the lockstring of the Character to allow the Account to "puppet" this particular character.
So, when creating the Account, make sure to also create the Character *from the same form* as you
create the Account from. This should set everything up for you. Otherwise you need to manually set
the "account" property on the Character and the "character" property on the Account to point to each
other. You must also set the lockstring of the Character to allow the Account to "puppet" this
particular character.
### Mutable attributes and their connection to the database
@ -23,16 +36,21 @@ When storing a mutable object (usually a list or a dictionary) in an Attribute
object.db.mylist = [1,2,3]
```
you should know that the connection to the database is retained also if you later extract that Attribute into another variable (what is stored and retrieved is actually a `PackedList` or a `PackedDict` that works just like their namesakes except they save themselves to the database when changed). So if you do
you should know that the connection to the database is retained also if you later extract that
Attribute into another variable (what is stored and retrieved is actually a `PackedList` or a
`PackedDict` that works just like their namesakes except they save themselves to the database when
changed). So if you do
```python
alist = object.db.mylist
alist.append(4)
```
this updates the database behind the scenes, so both `alist` and `object.db.mylist` are now `[1,2,3,4]`
this updates the database behind the scenes, so both `alist` and `object.db.mylist` are now
`[1,2,3,4]`
If you don't want this, Evennia provides a way to stably disconnect the mutable from the database by use of `evennia.utils.dbserialize.deserialize`:
If you don't want this, Evennia provides a way to stably disconnect the mutable from the database by
use of `evennia.utils.dbserialize.deserialize`:
```python
from evennia.utils.dbserialize import deserialize
@ -41,29 +59,59 @@ If you don't want this, Evennia provides a way to stably disconnect the mutable
blist.append(4)
```
The property `blist` is now `[1,2,3,4]` whereas `object.db.mylist` remains unchanged. If you want to update the database you'd need to explicitly re-assign the updated data to the `mylist` Attribute.
The property `blist` is now `[1,2,3,4]` whereas `object.db.mylist` remains unchanged. If you want to
update the database you'd need to explicitly re-assign the updated data to the `mylist` Attribute.
### Commands are matched by name *or* alias
When merging [command sets](Commands) it's important to remember that command objects are identified *both* by key *or* alias. So if you have a command with a key `look` and an alias `ls`, introducing another command with a key `ls` will be assumed by the system to be *identical* to the first one. This usually means merging cmdsets will overload one of them depending on priority. Whereas this is logical once you know how command objects are handled, it may be confusing if you are just looking at the command strings thinking they are parsed as-is.
When merging [command sets](Commands) it's important to remember that command objects are identified
*both* by key *or* alias. So if you have a command with a key `look` and an alias `ls`, introducing
another command with a key `ls` will be assumed by the system to be *identical* to the first one.
This usually means merging cmdsets will overload one of them depending on priority. Whereas this is
logical once you know how command objects are handled, it may be confusing if you are just looking
at the command strings thinking they are parsed as-is.
### Objects turning to `DefaultObject`
A common confusing error for new developers is finding that one or more objects in-game are suddenly of the type `DefaultObject` rather than the typeclass you wanted it to be. This happens when you introduce a critical Syntax error to the module holding your custom class. Since such a module is not valid Python, Evennia can't load it at all to get to the typeclasses within. To keep on running, Evennia will solve this by printing the full traceback to the terminal/console and temporarily fall back to the safe `DefaultObject` until you fix the problem and reload. Most errors of this kind will be caught by any good text editors. Keep an eye on the terminal/console during a reload to catch such errors - you may have to scroll up if your window is small.
A common confusing error for new developers is finding that one or more objects in-game are suddenly
of the type `DefaultObject` rather than the typeclass you wanted it to be. This happens when you
introduce a critical Syntax error to the module holding your custom class. Since such a module is
not valid Python, Evennia can't load it at all to get to the typeclasses within. To keep on running,
Evennia will solve this by printing the full traceback to the terminal/console and temporarily fall
back to the safe `DefaultObject` until you fix the problem and reload. Most errors of this kind will
be caught by any good text editors. Keep an eye on the terminal/console during a reload to catch
such errors - you may have to scroll up if your window is small.
### Overriding of magic methods
Python implements a system of [magic methods](https://docs.python.org/3/reference/datamodel.html#emulating-container-types), usually prefixed and suffixed by double-underscores (`__example__`) that allow object instances to have certain operations performed on them without needing to do things like turn them into strings or numbers first-- for example, is `obj1` greater than or equal to `obj2`?
Python implements a system of [magic
methods](https://docs.python.org/3/reference/datamodel.html#emulating-container-types), usually
prefixed and suffixed by double-underscores (`__example__`) that allow object instances to have
certain operations performed on them without needing to do things like turn them into strings or
numbers first-- for example, is `obj1` greater than or equal to `obj2`?
Neither object is a number, but given `obj1.size == "small"` and `obj2.size == "large"`, how might one compare these two arbitrary English adjective strings to figure out which is greater than the other? By defining the `__ge__` (greater than or equal to) magic method on the object class in which you figure out which word has greater significance, perhaps through use of a mapping table (`{'small':0, 'large':10}`) or other lookup and comparing the numeric values of each.
Neither object is a number, but given `obj1.size == "small"` and `obj2.size == "large"`, how might
one compare these two arbitrary English adjective strings to figure out which is greater than the
other? By defining the `__ge__` (greater than or equal to) magic method on the object class in which
you figure out which word has greater significance, perhaps through use of a mapping table
(`{'small':0, 'large':10}`) or other lookup and comparing the numeric values of each.
Evennia extensively makes use of magic methods on typeclasses to do things like initialize objects, check object existence or iterate over objects in an inventory or container. If you override or interfere with the return values from the methods Evennia expects to be both present and working, it can result in very inconsistent and hard-to-diagnose errors.
Evennia extensively makes use of magic methods on typeclasses to do things like initialize objects,
check object existence or iterate over objects in an inventory or container. If you override or
interfere with the return values from the methods Evennia expects to be both present and working, it
can result in very inconsistent and hard-to-diagnose errors.
The moral of the story-- it can be dangerous to tinker with magic methods on typeclassed objects. Try to avoid doing so.
The moral of the story-- it can be dangerous to tinker with magic methods on typeclassed objects.
Try to avoid doing so.
### Known upstream bugs
- There is currently (Autumn 2017) a bug in the `zope.interface` installer on some Linux Ubuntu distributions (notably Ubuntu 16.04 LTS). Zope is a dependency of Twisted. The error manifests in the server not starting with an error that `zope.interface` is not found even though `pip list` shows it's installed. The reason is a missing empty `__init__.py` file at the root of the zope package. If the virtualenv is named "evenv" as suggested in the [Getting Started](Getting-Started) instructions, use the following command to fix it:
- There is currently (Autumn 2017) a bug in the `zope.interface` installer on some Linux Ubuntu
distributions (notably Ubuntu 16.04 LTS). Zope is a dependency of Twisted. The error manifests in
the server not starting with an error that `zope.interface` is not found even though `pip list`
shows it's installed. The reason is a missing empty `__init__.py` file at the root of the zope
package. If the virtualenv is named "evenv" as suggested in the [Getting Started](Getting-Started)
instructions, use the following command to fix it:
```shell
touch evenv/local/lib/python2.7/site-packages/zope/__init__.py

View file

@ -1,19 +1,27 @@
# RSS
[RSS](http://en.wikipedia.org/wiki/RSS) is a format for easily tracking updates on websites. The principle is simple - whenever a site is updated, a small text file is updated. An RSS reader can then regularly go online, check this file for updates and let the user know what's new.
[RSS](http://en.wikipedia.org/wiki/RSS) is a format for easily tracking updates on websites. The
principle is simple - whenever a site is updated, a small text file is updated. An RSS reader can
then regularly go online, check this file for updates and let the user know what's new.
Evennia allows for connecting any number of RSS feeds to any number of in-game channels. Updates to the feed will be conveniently echoed to the channel. There are many potential uses for this: For example the MUD might use a separate website to host its forums. Through RSS, the players can then be notified when new posts are made. Another example is to let everyone know you updated your dev blog. Admins might also want to track the latest Evennia updates through our own RSS feed [here](http://code.google.com/feeds/p/evennia/updates/basic).
Evennia allows for connecting any number of RSS feeds to any number of in-game channels. Updates to
the feed will be conveniently echoed to the channel. There are many potential uses for this: For
example the MUD might use a separate website to host its forums. Through RSS, the players can then
be notified when new posts are made. Another example is to let everyone know you updated your dev
blog. Admins might also want to track the latest Evennia updates through our own RSS feed
[here](http://code.google.com/feeds/p/evennia/updates/basic).
## Configuring RSS
To use RSS, you first need to install the [feedparser](http://code.google.com/p/feedparser/) python module.
To use RSS, you first need to install the [feedparser](http://code.google.com/p/feedparser/) python
module.
pip install feedparser
Next you activate RSS support in your config file by settting `RSS_ENABLED=True`.
Start/reload Evennia as a privileged user. You should now have a new command available, `@rss2chan`:
Start/reload Evennia as a privileged user. You should now have a new command available, `@rss2chan`:
@rss2chan <evennia_channel> = <rss_url>
@ -23,12 +31,17 @@ You can connect RSS to any Evennia channel, but for testing, let's set up a new
@ccreate rss = RSS feeds are echoed to this channel!
Let's connect Evennia's code-update feed to this channel. The RSS url for evennia updates is `https://github.com/evennia/evennia/commits/master.atom`, so let's add that:
Let's connect Evennia's code-update feed to this channel. The RSS url for evennia updates is
`https://github.com/evennia/evennia/commits/master.atom`, so let's add that:
@rss2chan rss = https://github.com/evennia/evennia/commits/master.atom
That's it, really. New Evennia updates will now show up as a one-line title and link in the channel. Give the `@rss2chan` command on its own to show all connections. To remove a feed from a channel, you specify the connection again (use the command to see it in the list) but add the `/delete` switch:
That's it, really. New Evennia updates will now show up as a one-line title and link in the channel.
Give the `@rss2chan` command on its own to show all connections. To remove a feed from a channel,
you specify the connection again (use the command to see it in the list) but add the `/delete`
switch:
@rss2chan/delete rss = https://github.com/evennia/evennia/commits/master.atom
You can connect any number of RSS feeds to a channel this way. You could also connect them to the same channels as [IRC](IRC) to have the feed echo to external chat channels as well.
You can connect any number of RSS feeds to a channel this way. You could also connect them to the
same channels as [IRC](IRC) to have the feed echo to external chat channels as well.

View file

@ -1,4 +1,5 @@
# Roadmap
*As of Autumn 2016, Evennia's development roadmap is tracked through the [Evennia Projects Page](https://github.com/evennia/evennia/projects).*
*As of Autumn 2016, Evennia's development roadmap is tracked through the [Evennia Projects
Page](https://github.com/evennia/evennia/projects).*

Some files were not shown because too many files have changed in this diff Show more