mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Resync files from wiki
This commit is contained in:
parent
b1dd8a38a3
commit
1e56967a7c
13 changed files with 223 additions and 72 deletions
|
|
@ -15,6 +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](#locking-and-checking-attributes).**
|
||||
|
||||
## The .db and .ndb shortcuts
|
||||
|
||||
To save persistent data on a Typeclassed object you normally use the `db` (DataBase) operator. Let's
|
||||
|
|
|
|||
|
|
@ -72,7 +72,11 @@ Think thís default error message looks dull? The `get` command looks for an [At
|
|||
|
||||
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.
|
||||
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.
|
||||
|
||||
You create new Commands (or modify existing ones) in Python outside the game. See the [Adding Commands tutorial](https://github.com/evennia/evennia/wiki/Adding%20Command%20Tutorial) for help with creating your first own Command.
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ Command sets are often added to an object in its `at_object_creation` method. Fo
|
|||
|
||||
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](https://github.com/evennia/evennia/wiki/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*".
|
||||
- `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](#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`
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ class CmdSmile(Command):
|
|||
|
||||
def parse(self):
|
||||
"Very trivial parser"
|
||||
target = self.args.strip()
|
||||
self.target = self.args.strip()
|
||||
|
||||
def func(self):
|
||||
"This actually does things"
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@
|
|||
- [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
|
||||
|
||||
### Credits
|
||||
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ What we showed above is by far the simplest and probably cheapest option: Run Ev
|
|||
- 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.
|
||||
|
||||
#### Setting up your own machine as a server
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ This is the first part of our beginner's guide to the basics of using Python wit
|
|||
- [Importing modules](#importing-modules)
|
||||
- [Parsing Python errors](#parsing-python-errors)
|
||||
- [Our first function](#our-first-function)
|
||||
- [Looking at the log](#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).
|
||||
|
|
@ -15,53 +16,39 @@ 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:
|
||||
|
||||
```python
|
||||
> @py print("Hello World!")
|
||||
> py print("Hello World!")
|
||||
```
|
||||
|
||||
You won't see any return in Evennia though, instead you will just see:
|
||||
You will see
|
||||
|
||||
```
|
||||
>>> print("Hello world!")
|
||||
None
|
||||
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.
|
||||
|
||||
The first return line (with `>>>`) is just `@py` echoing what you input (we won't include that in the examples henceforth) and the last line just says the `@py` command finished.
|
||||
The first return line (with `>>>`) is just `py` echoing what you input (we won't include that in the examples henceforth).
|
||||
|
||||
Where is our hello world? In Evennia, `print` does not print in-game. Instead it prints to 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
|
||||
> 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`.
|
||||
|
||||
```
|
||||
evennia -l
|
||||
```
|
||||
|
||||
to start tailing the log in the terminal (use `Ctrl-C` if you need to exit later). If you look towards the end you will find the print output next to the log time stamp:
|
||||
|
||||
|
||||
```
|
||||
2017-05-07T20:20:16+0000 [stdout#info] Hello world!
|
||||
```
|
||||
|
||||
As a game dev it is important to look at the console 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.
|
||||
|
||||
To show the greeting in-game, try the following instead:
|
||||
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:
|
||||
|
||||
```python
|
||||
> @py me.msg("Hello world!")
|
||||
> py me.msg("Hello world!")
|
||||
Hello world!
|
||||
None
|
||||
```
|
||||
|
||||
Ignore the last `None`, that's just a return from the `@py` command itself we don't need to care about for now. 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 instead of to the console log.
|
||||
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.
|
||||
|
||||
> Important: something like `print(...)` we refer to as a *function*, while `msg(...)` which sits on an object is called a *method*.
|
||||
|
||||
You can 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
|
||||
|
||||
|
|
@ -83,22 +70,22 @@ Don't forget to save the file. A file with the ending `.py` is referred to as a
|
|||
|
||||
```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.
|
||||
|
||||
Think of the period `.` as replacing `/` (or `\` for Windows) in your path. The `.py` ending of `test.py` is 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" to the console window.
|
||||
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:
|
||||
|
||||
```python
|
||||
> @py import world.test
|
||||
> py import world.test
|
||||
```
|
||||
You will *not* see any output in the log 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. 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.
|
||||
|
||||
|
|
@ -110,10 +97,11 @@ Next, erase the single `print` statement you had in `test.py` and replace it wit
|
|||
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 in-game - this makes sure Evennia sees the new version of your code. Try to import it from `@py` in the same way as earlier:
|
||||
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:
|
||||
|
||||
```python
|
||||
> @py import world.test
|
||||
> py import world.test
|
||||
```
|
||||
|
||||
No go - this time you get an error!
|
||||
|
|
@ -124,7 +112,7 @@ 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.
|
||||
|
|
@ -137,7 +125,7 @@ The `NameError` we see here is due to a module being its own isolated thing. It
|
|||
|
||||
### 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:
|
||||
|
||||
|
|
@ -157,20 +145,18 @@ Now that we are moving onto multi-line Python code, there are some important thi
|
|||
- 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.
|
||||
|
||||
```python
|
||||
> @reload
|
||||
> @py import world.test
|
||||
Done (use self.msg() if you want to catch output)
|
||||
> reload
|
||||
> py import world.test
|
||||
```
|
||||
|
||||
Nothing happened except the normal output from `@py`. 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.
|
||||
|
||||
```python
|
||||
> @py import world.test ; world.test.hello_world(me)
|
||||
Hello world!
|
||||
Done (use self.msg() if you want to catch output)
|
||||
```
|
||||
|
||||
There is our "Hello World"! The `;` is the way to put multiple Python-statements on one line.
|
||||
|
|
@ -181,4 +167,17 @@ In the second statement we access the module path we imported (`world.test`) and
|
|||
|
||||
> 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_.
|
||||
|
||||
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 tutorial is continued in [Part 2](Python-basic-tutorial-part-two), where we'll start learning about objects and to explore the Evennia library.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
In the first part of the tutorial we did things like
|
||||
|
||||
```python
|
||||
> @py me.msg("Hello World!")
|
||||
> 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.
|
||||
|
|
@ -22,29 +22,29 @@ To learn about functions and imports we also passed that `me` on to a function `
|
|||
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".
|
||||
|
||||
```python
|
||||
> @py me
|
||||
> py me
|
||||
Christine
|
||||
> @py me.key
|
||||
> py me.key
|
||||
Christine
|
||||
```
|
||||
|
||||
These returns look the same at first glance, but not if we examine them more closely:
|
||||
|
||||
```python
|
||||
> @py type(me)
|
||||
> py type(me)
|
||||
<class 'typeclasses.characters.Character'>
|
||||
> @py type(me.key)
|
||||
> 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, such as `<...>`, through `@py`.
|
||||
> 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.
|
||||
|
||||
|
|
@ -52,13 +52,13 @@ A *class* is like a "factory" or blueprint. From a class you then create individ
|
|||
> * 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.
|
||||
>
|
||||
> 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. It exactly describes 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`
|
||||
|
||||
|
|
@ -252,7 +252,7 @@ Note that we don't send `self` but only the `message` argument. Python will auto
|
|||
|
||||
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. To understand the meaning of `self`, and [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:
|
||||
|
||||
|
|
@ -263,12 +263,12 @@ class Character(DefaultCharacter):
|
|||
"""
|
||||
def at_before_say(self, message, **kwargs):
|
||||
"Called before say, allows for tweaking message"
|
||||
return "{} ...".format(message)
|
||||
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.
|
||||
|
||||
The `format` is a standard method on Python strings. It looks for placeholders `{}` in the string and replaces that placeholder with its arguments in the same order. In this case we are inserting the original `message` into the place of the `{}` and follow it with `...`. Python has very powerful [string formatting](https://docs.python.org/2/library/string.html#format-specification-mini-language) and you are wise to learn those well.
|
||||
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.
|
||||
|
||||
|
|
@ -280,13 +280,13 @@ In-game, now try
|
|||
|
||||
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/2/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 inserted Python 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.
|
||||
|
||||
First `cd` to your game 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
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ The [`IPython`](https://en.wikipedia.org/wiki/IPython) program is just a nicer i
|
|||
|
||||
> evennia shell
|
||||
|
||||
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 ...
|
||||
...
|
||||
|
|
@ -307,7 +307,7 @@ IPython has some very nice ways to explore what Evennia has to offer.
|
|||
> 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:
|
||||
|
||||
```python
|
||||
> from evennia import DefaultCharacter
|
||||
|
|
@ -316,7 +316,7 @@ That is, write `evennia.` and press the Tab key. You will be presented with a li
|
|||
|
||||
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 must start `IPython` 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:
|
||||
|
||||
```python
|
||||
> from typeclasses.characters import Character
|
||||
|
|
@ -333,4 +333,4 @@ We have touched upon many of the concepts here but to use Evennia and to be able
|
|||
|
||||
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](https://github.com/evennia/evennia/wiki/Tutorials) to gradually build up your understanding.
|
||||
|
||||
Good luck!
|
||||
Good luck!
|
||||
|
|
@ -10,12 +10,16 @@ In Evennia, Tags are technically also used to implement `Aliases` (alternative n
|
|||
|
||||
## Properties of Tags (and Aliases and Permissions)
|
||||
|
||||
Tags are *unique*. This means that there is only ever one Tag object with a given key and category. When Tags are assigned to game entities, these entities are actually sharing the same Tag. This means that Tags are not suitable for storing information about a single object - use an [Attribute](Attributes) for this instead. Tags are a lot more limited than Attributes but this also makes them very quick to lookup in the database - this is the whole point.
|
||||
Tags are *unique*. This means that there is only ever one Tag object with a given key and category.
|
||||
|
||||
> Not specifying a category (default) gives the tag a category of `None`, which is also considered a unique key + category combination.
|
||||
|
||||
When Tags are assigned to game entities, these entities are actually sharing the same Tag. This means that Tags are not suitable for storing information about a single object - use an [Attribute](Attributes) for this instead. Tags are a lot more limited than Attributes but this also makes them very quick to lookup in the database - this is the whole point.
|
||||
|
||||
Tags have the following properties, stored in the database:
|
||||
|
||||
- **key** - the name of the Tag. This is the main property to search for when looking up a Tag.
|
||||
- **category** - this category allows for retrieving only specific subsets of tags used for different purposes. You could have one category of tags for "zones", another for "outdoor locations", for example.
|
||||
- **category** - this category allows for retrieving only specific subsets of tags used for different purposes. You could have one category of tags for "zones", another for "outdoor locations", for example. If not given, the category will be `None`, which is also considered a separate, default, category.
|
||||
- **data** - this is an optional text field with information about the tag. Remember that Tags are shared between entities, so this field cannot hold any object-specific information. Usually it would be used to hold info about the group of entities the Tag is tagging - possibly used for contextual help like a tool tip. It is not used by default.
|
||||
|
||||
There are also two special properties. These should usually not need to be changed or set, it is used internally by Evennia to implement various other uses it makes of the `Tag` object:
|
||||
|
|
@ -28,6 +32,7 @@ You can tag any *typeclassed* object, namely [Objects](Objects), [Accounts](Acco
|
|||
|
||||
```python
|
||||
mychair.tags.add("furniture")
|
||||
mychair.tags.add("furniture", category="luxurious")
|
||||
myroom.tags.add("dungeon#01")
|
||||
myscript.tags.add("weather", category="climate")
|
||||
myaccount.tags.add("guestaccount")
|
||||
|
|
@ -37,10 +42,14 @@ You can tag any *typeclassed* object, namely [Objects](Objects), [Accounts](Acco
|
|||
mychair.tags.clear()
|
||||
```
|
||||
|
||||
Adding a new tag will either create a new Tag or re-use an already existing one. When using `remove`, the `Tag` is not deleted but are just disconnected from the tagged object. This makes for very quick operations. The `clear` method removes (disconnects) all Tags from the object. You can also use the default `@tag` command:
|
||||
Adding a new tag will either create a new Tag or re-use an already existing one. Note that there are _two_ "furniture" tags, one with a `None` category, and one with the "luxurious" category.
|
||||
|
||||
When using `remove`, the `Tag` is not deleted but are just disconnected from the tagged object. This makes for very quick operations. The `clear` method removes (disconnects) all Tags from the object. You can also use the default `@tag` command:
|
||||
|
||||
@tag mychair = furniture
|
||||
|
||||
This tags the chair with a 'furniture' Tag (the one with a `None` category).
|
||||
|
||||
## Searching for objects with a given tag
|
||||
|
||||
Usually tags are used as a quick way to find tagged database entities. You can retrieve all objects with a given Tag like this in code:
|
||||
|
|
@ -52,6 +61,7 @@ Usually tags are used as a quick way to find tagged database entities. You can r
|
|||
|
||||
# search for objects
|
||||
objs = evennia.search_tag("furniture")
|
||||
objs2 = evennia.search_tag("furniture", category="luxurious")
|
||||
dungeon = evennia.search_tag("dungeon#01")
|
||||
forest_rooms = evennia.search_tag(category="forest")
|
||||
forest_meadows = evennia.search_tag("meadow", category="forest")
|
||||
|
|
@ -65,6 +75,9 @@ Usually tags are used as a quick way to find tagged database entities. You can r
|
|||
accounts = evennia.search_tag_account("guestaccount")
|
||||
```
|
||||
|
||||
> Note that searching for just "furniture" will only return the objects tagged with the "furniture" tag that
|
||||
has a category of `None`. We must explicitly give the category to get the "luxurious" furniture.
|
||||
|
||||
Using any of the `search_tag` variants will all return [Django Querysets](https://docs.djangoproject.com/en/2.1/ref/models/querysets/), including if you only have one match. You can treat querysets as lists and iterate over them, or continue building search queries with them.
|
||||
|
||||
Remember when searching that not setting a category means setting it to `None` - this does *not* mean that category is undefined, rather `None` is considered the default, unnamed category.
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ To play the tutorial "correctly", you should *not* do so as superuser. The reas
|
|||
|
||||
## Gameplay
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
*To get into the mood of this miniature quest, imagine you are an adventurer out to find fame and fortune. You have heard rumours of an old castle ruin by the coast. In its depth a warrior princess was buried together with her powerful magical weapon - a valuable prize, if it's true. Of course this is a chance to adventure that you cannot turn down!*
|
||||
|
|
|
|||
|
|
@ -149,10 +149,11 @@ In the future you can add any number of commands to this cmdset, to expand your
|
|||
We will create a simple Room typeclass to act as a template for all our Chargen areas. Edit `mygame/typeclasses/rooms.py` next:
|
||||
|
||||
```python
|
||||
# end of rooms.py
|
||||
|
||||
from commands.default_cmdsets import ChargenCmdset
|
||||
|
||||
# ...
|
||||
# down at the end of rooms.py
|
||||
|
||||
class ChargenRoom(Room):
|
||||
"""
|
||||
This room class is used by character-generation rooms. It makes
|
||||
|
|
@ -200,6 +201,8 @@ Go back to `mygame/commands/command.py` and add the command to the end like this
|
|||
```python
|
||||
import random
|
||||
|
||||
# ...
|
||||
|
||||
class CmdAttack(Command):
|
||||
"""
|
||||
issues an attack
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ Evennia's test suite makes use of Django unit test system, which in turn relies
|
|||
|
||||
To make the test runner find the tests, they must be put in a module named `test*.py` (so `test.py`, `tests.py` etc). Such a test module will be found wherever it is in the package. It can be a good idea to look at some of Evennia's `tests.py` modules to see how they look.
|
||||
|
||||
Inside a testing file, a `unittest.TestCase` class is used to test a single aspect or component in various ways. Each test case contains one ore more *test methods* - these define the actual tests to run. You can name the test methods anything you want as long as the name starts with "`test_`". Your `TestCase` class can also have a method `setUp()`. This is run before each test, setting up and storing whatever preparations the test methods need. Conversely, a `tearDown()` method can optionally do cleanup after each test.
|
||||
Inside a testing file, a `unittest.TestCase` class is used to test a single aspect or component in various ways. Each test case contains one or more *test methods* - these define the actual tests to run. You can name the test methods anything you want as long as the name starts with "`test_`". Your `TestCase` class can also have a method `setUp()`. This is run before each test, setting up and storing whatever preparations the test methods need. Conversely, a `tearDown()` method can optionally do cleanup after each test.
|
||||
|
||||
To test the results, you use special methods of the `TestCase` class. Many of those start with "`assert`", such as `assertEqual` or `assertTrue`.
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ The server-side webclient protocols are found in `evennia/server/portal/webclien
|
|||
|
||||
## Customizing the web client
|
||||
|
||||
Like was the case for the website, you override the webclient from your game directory. You need to add/modify a file in the matching directory location within one of the _overrides directories.
|
||||
Like was the case for the website, you override the webclient from your game directory. You need to add/modify a file in the matching directory location within one of the _overrides directories. These _override directories are NOT directly used by the web server when the game is running, the server copies everything web related in the Evennia folder over to `mygame/web/static/` and then copies in all of your _overrides. This can cause some cases were you edit a file, but it doesn't seem to make any difference in the servers behavior. **Before doing anything else, try shutting down the game and running `evennia collectstatic` from the command line then start it back up, clear your browser cache, and see if your edit shows up.**
|
||||
|
||||
Example: To change the utilized plugin list, you need to override base.html by copying
|
||||
`evennia/web/webclient/templates/webclient/base.html` to `mygame/web/template_overrides/webclient/base.html` and editing it to add your new plugin.
|
||||
|
|
@ -53,19 +53,146 @@ Example: To change the utilized plugin list, you need to override base.html by c
|
|||
The order of the plugins defined in `base.html` is important. All the callbacks for each plugin will be executed in that order. Functions marked "boolean" above must return true/false. Returning true will short-circuit the execution, so no other plugins lower in the base.html list will have their callback for this event called. This enables things like the up/down arrow keys for the history.js plugin to always occur before the default_in.js plugin adds that key to the current input buffer.
|
||||
|
||||
# Example/Default Plugins (plugins/*.js)
|
||||
* `clienthelp.js` Defines onOptionsUI from the options2 plugin. This is a mostly empty plugin to add some "How To" information for your game.
|
||||
* `default_in.js` Defines onKeydown. <enter> key or mouse clicking the arrow will send the currently typed text.
|
||||
* `default_out.js` Defines onText, onPrompt, and onUnknownCmd. Generates HTML output for the user.
|
||||
* `default_unload.js` Defines onBeforeUnload. Prompts the user to confirm that they meant to leave/close the game.
|
||||
* `font.js` Defines onOptionsUI. The plugin adds the ability to select your font and font size.
|
||||
* `goldenlayout_default_config.js` Not actually a plugin, defines a global variable that goldenlayout uses to determine its window layout, known tag routing, etc.
|
||||
* `goldenlayout.js` Defines onKeydown, onText and custom functions. A very powerful "tabbed" window manager for drag-n-drop windows, text routing and more.
|
||||
* `history.js` Defines onKeydown and onSend. Creates a history of past sent commands, and uses arrow keys to peruse.
|
||||
* `notifications.js` Defines onText. Generates browser notification events for each new message while the tab is hidden.
|
||||
* `hotbuttons.js` Defines onGotOptions. A Disabled-by-default plugin that defines a button bar with user-assignable commands.
|
||||
* `iframe.js` Defines onOptionsUI. A goldenlayout-only plugin to create a restricted browsing sub-window for a side-by-side web/text interface, mostly an example of how to build new HTML "components" for goldenlayout.
|
||||
* `message_routing.js` Defines onOptionsUI, onText, onKeydown. This goldenlayout-only plugin implements regex matching to allow users to "tag" arbitrary text that matches, so that it gets routed to proper windows. Similar to "Spawn" functions for other clients.
|
||||
* `multimedia.js` An basic plugin to allow the client to handle "image" "audio" and "video" messages from the server and display them as inline HTML.
|
||||
* `notifications.js` Defines onText. Generates browser notification events for each new message while the tab is hidden.
|
||||
* `oob.js` Defines onSend. Allows the user to test/send Out Of Band json messages to the server.
|
||||
* `options.js` Defines most callbacks. Provides a popup-based UI to coordinate options settings with the server.
|
||||
* `options2.js` Defines most callbacks. Provides a goldenlayout-based version of the options/settings tab. Integrates with other plugins via the custom onOptionsUI callback.
|
||||
* `popups.js` Provides default popups/Dialog UI for other plugins to use.
|
||||
* `splithandler.js` Defines onText. Provides a powerful multi-window UI extension to automatically separate out screen real-estate by type of message.
|
||||
* `splithandler.js` Defines onText. Provides an older, less-flexible alternative to goldenlayout for multi-window UI to automatically separate out screen real-estate by type of message.
|
||||
|
||||
# Writing your own Plugins
|
||||
|
||||
So, you love the functionality of the webclient, but your game has specific types of text that need to be separated out into its own space, visually. The splithandler.js plugin provides a means to do this, but you don't want to have to force every player to set up their own layout every time they use the client.
|
||||
So, you love the functionality of the webclient, but your game has specific types of text that need to be separated out into their own space, visually. There are two plugins to help with this. The Goldenlayout plugin framework, and the older Splithandler framework.
|
||||
|
||||
## GoldenLayout
|
||||
|
||||
GoldenLayout is a web framework that allows web developers and their users to create their own tabbed/windowed layouts. Windows/tabs can be click-and-dragged from location to location by clicking on their titlebar and dragging until the "frame lines" appear. Dragging a window onto another window's titlebar will create a tabbed "Stack". The Evennia goldenlayout plugin defines 3 basic types of window: The Main window, input windows and non-main text output windows. The Main window and the first input window are unique in that they can't be "closed".
|
||||
|
||||
The most basic customization is to provide your users with a default layout other than just one Main output and the one starting input window. This is done by modifying your server's goldenlayout_default_config.js.
|
||||
|
||||
Start by creating a new `mygame/web/static_overrides/webclient/js/plugins/goldenlayout_default_config.js` file, and adding the following JSON variable:
|
||||
|
||||
```
|
||||
var goldenlayout_config = {
|
||||
content: [{
|
||||
type: 'column',
|
||||
content: [{
|
||||
type: 'row',
|
||||
content: [{
|
||||
type: 'column',
|
||||
content: [{
|
||||
type: 'component',
|
||||
componentName: 'Main',
|
||||
isClosable: false,
|
||||
tooltip: 'Main - drag to desired position.',
|
||||
componentState: {
|
||||
cssClass: 'content',
|
||||
types: 'untagged',
|
||||
updateMethod: 'newlines',
|
||||
},
|
||||
}, {
|
||||
type: 'component',
|
||||
componentName: 'input',
|
||||
id: 'inputComponent',
|
||||
height: 10,
|
||||
tooltip: 'Input - The last input in the layout is always the default.',
|
||||
}, {
|
||||
type: 'component',
|
||||
componentName: 'input',
|
||||
id: 'inputComponent',
|
||||
height: 10,
|
||||
isClosable: false,
|
||||
tooltip: 'Input - The last input in the layout is always the default.',
|
||||
}]
|
||||
},{
|
||||
type: 'column',
|
||||
content: [{
|
||||
type: 'component',
|
||||
componentName: 'evennia',
|
||||
componentId: 'evennia',
|
||||
title: 'example',
|
||||
height: 60,
|
||||
isClosable: false,
|
||||
componentState: {
|
||||
types: 'some-tag-here',
|
||||
updateMethod: 'newlines',
|
||||
},
|
||||
}, {
|
||||
type: 'component',
|
||||
componentName: 'evennia',
|
||||
componentId: 'evennia',
|
||||
title: 'sheet',
|
||||
isClosable: false,
|
||||
componentState: {
|
||||
types: 'sheet',
|
||||
updateMethod: 'replace',
|
||||
},
|
||||
}],
|
||||
}],
|
||||
}]
|
||||
}]
|
||||
};
|
||||
```
|
||||
This is a bit ugly, but hopefully, from the indentation, you can see that it creates a side-by-side (2-column) interface with 3 windows down the left side (The Main and 2 inputs) and a pair of windows on the right side for extra outputs. Any text tagged with "some-tag-here" will flow to the bottom of the "example" window, and any text tagged "sheet" will replace the text already in the "sheet" window.
|
||||
|
||||
Note: GoldenLayout gets VERY confused and will break if you create two windows with the "Main" componentName.
|
||||
|
||||
Now, let's say you want to display text on each window using different CSS. This is where new goldenlayout "components" come in. Each component is like a blueprint that gets stamped out when you create a new instance of that component, once it is defined, it won't be easily altered. You will need to define a new component, preferably in a new plugin file, and then add that into your page (either dynamically to the DOM via javascript, or by including the new plugin file into the base.html).
|
||||
|
||||
First up, follow the directions in Customizing the Web Client section above to override the base.html.
|
||||
|
||||
Next, add the new plugin to your copy of base.html:
|
||||
```
|
||||
<script src={% static "webclient/js/plugins/myplugin.js" %} language="javascript" type="text/javascript"></script>
|
||||
```
|
||||
Remember, plugins are load-order dependent, so make sure the new `<script>` tag comes before the goldenlayout.js
|
||||
|
||||
Next, create a new plugin file `mygame/web/static_overrides/webclient/js/plugins/myplugin.js` and edit it.
|
||||
|
||||
```
|
||||
let myplugin = (function () {
|
||||
//
|
||||
//
|
||||
var postInit = function() {
|
||||
var myLayout = window.plugins['goldenlayout'].getGL();
|
||||
|
||||
// register our component and replace the default messagewindow
|
||||
myLayout.registerComponent( 'mycomponent', function (container, componentState) {
|
||||
let mycssdiv = $('<div>').addClass('myCSS');
|
||||
mycssdiv.attr('types', 'mytag');
|
||||
mycssdiv.attr('update_method', 'newlines');
|
||||
mycssdiv.appendTo( container.getElement() );
|
||||
});
|
||||
|
||||
console.log("MyPlugin Initialized.");
|
||||
}
|
||||
|
||||
return {
|
||||
init: function () {},
|
||||
postInit: postInit,
|
||||
}
|
||||
})();
|
||||
window.plugin_handler.add("myplugin", myplugin);
|
||||
```
|
||||
You can then add "mycomponent" to an item's componentName in your goldenlayout_default_config.js.
|
||||
|
||||
Make sure to stop your server, evennia collectstatic, and restart your server. Then make sure to clear your browser cache before loading the webclient page.
|
||||
|
||||
|
||||
## Older Splithandler
|
||||
The splithandler.js plugin provides a means to do this, but you don't want to have to force every player to set up their own layout every time they use the client.
|
||||
|
||||
Let's create a `mygame/web/static_overrides/webclient/js/plugins/layout.js` plugin!
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue