mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 13:26:30 +01:00
307 lines
13 KiB
Markdown
307 lines
13 KiB
Markdown
# Starting to code Evennia
|
|
|
|
[prev lesson](Tutorial-World-Introduction) | [next lesson](Directory-Overview)
|
|
|
|
Time to dip our toe into some coding! Evennia is written and extended in [Python](http://python.org), which
|
|
is a mature and professional programming language that is very fast to work with.
|
|
|
|
That said, even though Python is widely considered easy to learn, we can only cover the most immediately
|
|
important aspects of Python in this series of starting tutorials. Hopefully we can get you started
|
|
but then you'll need to continue learning from there. See our [link section](../../Links) for finding
|
|
more reference material and dedicated Python tutorials.
|
|
|
|
> While this will be quite basic if you are an experienced developer, you may want to at least
|
|
> stay around for the first few sections where we cover how to run Python from inside Evennia.
|
|
|
|
First, if you were quelling yourself to play the tutorial world, make sure to get your
|
|
superuser powers back:
|
|
|
|
unquell
|
|
|
|
|
|
### Evennia Hello world
|
|
|
|
The `py` Command (or `!`, which is an alias) allows you as a superuser to execute raw Python from in-
|
|
game. This is useful for quick testing. From the game's input line, enter the following:
|
|
|
|
> py print("Hello World!")
|
|
|
|
|
|
```sidebar:: Command input
|
|
|
|
The lines with a `>` indicate input to enter in-game, while the lines below are the
|
|
expected return from that input.
|
|
```
|
|
|
|
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. We are sending "Hello World" as an _argument_ to this function. The quotes `"..."`
|
|
mean that you are inputting a *string* (i.e. text). You could also have used single-quotes `'...'`,
|
|
Python accepts both.
|
|
|
|
The `print` command is a standard Python structure. We can use that here in the `py` command since
|
|
we can se the output. 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 a shortcut to 'us', the one running the `py` command. It is not some special
|
|
Python thing, but something Evennia just makes available in the `py` command for convenience
|
|
(`self` is an alias).
|
|
|
|
The `me` is an example of an *Object instance*. Objects are fundamental in Python and Evennia.
|
|
The `me` object also contains a lot of useful resources for doing
|
|
things with that object. We access those resources with '`.`'.
|
|
|
|
One such resource is `msg`, which 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`.
|
|
|
|
```sidebar:: Functions and Methods
|
|
|
|
Function:
|
|
Stand-alone in a python module, like `print()`
|
|
Method:
|
|
Something that sits "on" an object, like `.msg()`
|
|
```
|
|
|
|
For now, `print` and `me.msg` behaves the same, just remember that `print` is mainly used for
|
|
debugging and `.msg()` will be more useful for you in the future.
|
|
|
|
For fun, try printing other things. Also try this:
|
|
|
|
> py self.msg("|rThis is red text!")
|
|
|
|
Adding that `|r` at the start will turn our output red. Enter the command `color ansi` or `color xterm`
|
|
to see which colors are available.
|
|
|
|
### Importing code from other modules
|
|
|
|
As we saw in the previous section, we could use `me.msg` to access the `msg` method on `me`. This
|
|
use of the full-stop character is used to access all sorts of resources, including that in other
|
|
Python modules. If you've been following along, this is what we've referred to as the "Python path".
|
|
|
|
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/
|
|
world/
|
|
test.py
|
|
```
|
|
|
|
For now, only add one line to `test.py`:
|
|
|
|
```python
|
|
print("Hello World!")
|
|
```
|
|
|
|
```sidebar:: Python module
|
|
|
|
This is a text file with the `.py` file ending. A module
|
|
contains Python source code and from within Python one can
|
|
access its contents by importing it via its python-path.
|
|
|
|
```
|
|
|
|
Don't forget to _save_ the file. We just created our first Python _module_!
|
|
To use this in-game we have to *import* it. Try this:
|
|
|
|
> py import world.test
|
|
Hello World
|
|
|
|
If you make some error (we'll cover how to handle errors below), fix the error in the module and
|
|
run the `reload` command in-game 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.
|
|
|
|
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.
|
|
|
|
Now try to run this a second time:
|
|
|
|
> 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.
|
|
|
|
Try this:
|
|
|
|
> reload
|
|
|
|
And then
|
|
|
|
> py import world.test
|
|
Hello World!
|
|
|
|
Now we see it again. The `reload` wiped the server's memory of what was imported, so it had to
|
|
import it anew. You'd have to do this every time you wanted the print to show though, which is
|
|
not very useful.
|
|
|
|
> We'll get back to more advanced ways to import code in later tutorial sections - this is an
|
|
> important topic. But for now, let's press on and resolve this particular problem.
|
|
|
|
### Parsing Python errors
|
|
|
|
Running print only on import is not too helpful. We want to print whenever we like to!
|
|
Go back to your `test.py` file and erase the single `print` statement you had. Replace
|
|
it with this instead:
|
|
|
|
```python
|
|
me.msg("Hello World!")
|
|
```
|
|
|
|
As you recall we used this with `py` earlier - it echoed "Hello World!" in-game.
|
|
Save your file and `reload` your server - this makes sure Evennia knows to re-import
|
|
it (with our new, fresh code this time).
|
|
|
|
To test it, import it using `py` as before
|
|
|
|
> py import world.test
|
|
|
|
No go - this time you get an error!
|
|
|
|
```python
|
|
File "./world/test.py", line 1, in <module>
|
|
me.msg("Hello world!")
|
|
NameError: name 'me' is not defined
|
|
```
|
|
|
|
```sidebar:: Errors in the logs
|
|
|
|
In regular use, tracebacks will often appear in the log rather than
|
|
in the game. Use `evennia --log` to view the log in the terminal. Make
|
|
sure to scroll back if you expect an error and don't see it. Use
|
|
`Ctrl-C` (or `Cmd-C` on Mac) to exit the log-view.
|
|
|
|
```
|
|
|
|
This is called a *traceback*. Python's errors are very friendly and will most of the time tell you
|
|
exactly what and where things go wrong. It's important that you learn to parse tracebacks so you
|
|
know how to fix your code.
|
|
|
|
A traceback is to be read from the _bottom up_:
|
|
|
|
- (line 3) An error of type `NameError` is the problem ...
|
|
- (line 3) ... more specifically it is due to the variable `me` not being defined.
|
|
- (line 2) This happened on the line `me.msg("Hello world!")` ...
|
|
- (line 1) ... 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 the program 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 mentioned, it's just something Evennia came up with for convenience in the `py`
|
|
command). As far as the module is concerned `me` is an unfamiliar name, appearing out of nowhere.
|
|
Hence the `NameError`.
|
|
|
|
### Our first own 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. We know this 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:
|
|
|
|
```python
|
|
def hello_world(who):
|
|
who.msg("Hello World!")
|
|
```
|
|
|
|
As we are moving to 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`.
|
|
- 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, for your own sanity's sake,
|
|
set up your editor to always indent *4 spaces* (**not** a single tab-character) when you press the TAB key.
|
|
|
|
So about that function. Line 1:
|
|
|
|
- `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. We recommend you do the same.
|
|
- `who` is what we call the *argument* to our function. Arguments are variables _passed_ 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 line 1 indicates that the header of the function is complete.
|
|
|
|
Line 2:
|
|
|
|
- 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
|
|
start at least 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.
|
|
|
|
Now let's try this out. First `reload` your game to have it pick up
|
|
our updated Python module when we reload.
|
|
|
|
> 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. So we need to first import the module and then access the
|
|
function within:
|
|
|
|
> py import world.test ; world.test.hello_world(me)
|
|
Hello world!
|
|
|
|
There is our "Hello World"! Using `;` is the way to put multiple Python-statements on one line.
|
|
|
|
```warning:: MUD clients and semi-colon
|
|
|
|
A common issue is that some MUD clients use the semi-colon `;` for their own purposes,
|
|
namely to separate client-inputs. If so, you'll get a `NameError` above, stating that
|
|
`world` is not defined. Check so you understand why this is! Most clients allow you to
|
|
remap to use some other separator than `;`. You can use the Evennia web client if this
|
|
is problem remains.
|
|
```
|
|
|
|
So what happened there? First we imported `world.test` as usual. But this time we continued and
|
|
accessed the `hello_world` function _inside_ the newly imported module.
|
|
|
|
We *call* the function with `me`, which becomes the `who` variable we use inside
|
|
the `hello_function` (they are be the same object). And since `me.msg` works, so does `who.msg`
|
|
inside the function.
|
|
|
|
> **Extra Credit:** As an exercise, try to pass something else into `hello_world`. Try for example
|
|
>to pass the number `5` or the string `"foo"`. You'll get errors telling you that they don't have
|
|
>the attribute `msg`. They don't care about `me` itself not being a string or a number. If you are
|
|
>familiar with other programming languages (especially C/Java) you may be tempted to start *validating*
|
|
>`who` to make sure it's of the right type before you send it. This is usually not recommended in Python.
|
|
>Python philosophy is to [handle](https://docs.python.org/2/tutorial/errors.html) the error if it happens
|
|
>rather than to add a lot of code to prevent it from happening. See [duck typing](https://en.wikipedia.org/wiki/Duck_typing)
|
|
>and the concept of _Leap before you Look_.
|
|
|
|
|
|
This gives you some initial feeling for how to run Python and import Python modules. We also
|
|
tried to put a module in `module/world/`. Now let's look at the rest of the stuff you've got
|
|
inside that `mygame/` folder ...
|
|
|
|
[prev lesson](Tutorial-World-Introduction) | [next lesson](Directory-Overview)
|