mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Update and clean up docs
This commit is contained in:
parent
b84fab9300
commit
615e828abf
9 changed files with 380 additions and 478 deletions
|
|
@ -16,26 +16,52 @@ So a good rule of thumb is to use colour to enhance your game but don't *rely* o
|
|||
critical information. If you are coding the game, you can add functionality to let users disable
|
||||
colours as they please, as described [here](../Howtos/Manually-Configuring-Color.md).
|
||||
|
||||
To see which colours your client support, use the default `@color` command. This will list all
|
||||
available colours for ANSI and Xterm256 along with the codes you use for them. You can find a list
|
||||
of all the parsed `ANSI`-colour codes in `evennia/utils/ansi.py`.
|
||||
Evennia supports two color standards:
|
||||
|
||||
- `ANSI` - 16 foreground colors + 8 background colors. Widely supported.
|
||||
- `Xterm256` - 128 RGB colors, 32 greyscales. Not always supported in old clients.
|
||||
|
||||
To see which colours your client support, use the default `color` command. This will list all
|
||||
available colours for ANSI and Xterm256 along with the codes you use for them. The
|
||||
central ansi/xterm256 parser is located in [evennia/utils/ansi.py](api:evennia.utils.ansi).
|
||||
|
||||
## ANSI colours
|
||||
|
||||
Evennia supports the `ANSI` standard for text. This is by far the most supported MUD-color standard,
|
||||
available in all but the most ancient mud clients. The ANSI colours are **r**ed, **g**reen,
|
||||
**y**ellow, **b**lue, **m**agenta, **c**yan, **w**hite and black. They are abbreviated by their
|
||||
first letter except for black which is abbreviated with the letter **x**. In ANSI there are "bright"
|
||||
and "normal" (darker) versions of each color, adding up to a total of 16 colours to use for
|
||||
foreground text. There are also 8 "background" colours. These have no bright alternative in ANSI
|
||||
(but Evennia uses the [Xterm256](#xterm256-colours) extension behind the scenes to offer
|
||||
them anyway).
|
||||
Evennia supports the `ANSI` standard for text. This is by far the most supported MUD-color standard, available in all but the most ancient mud clients.
|
||||
|
||||
To colour your text you put special tags in it. Evennia will parse these and convert them to the
|
||||
correct markup for the client used. If the user's client/console/display supports ANSI colour, they
|
||||
will see the text in the specified colour, otherwise the tags will be stripped (uncolored text).
|
||||
This works also for non-terminal clients, such as the webclient. For the webclient, Evennia will
|
||||
translate the codes to HTML RGB colors.
|
||||
|
||||
For the webclient, Evennia will translate the codes to CSS tags.
|
||||
|
||||
| Tag | Effect |
|
||||
| ---- | ----- |
|
||||
| \|n | end all color formatting, including background colors. |
|
||||
|\|r | bright red foreground color |
|
||||
|\|g | bright green foreground color |
|
||||
|\|y | bright yellow foreground color |
|
||||
|\|b | bright blue foreground color |
|
||||
|\|m | bright magentaforeground color |
|
||||
|\|c | bright cyan foreground color |
|
||||
|\|w | bright white foreground color |
|
||||
|\|x | bright black (dark grey) foreground color |
|
||||
|\|R | normal red foreground color |
|
||||
|\|G | normal green foreground color |
|
||||
|\|Y | normal yellow foreground color |
|
||||
|\|B | normal blue foreground color |
|
||||
|\|M | normal magentaforeground color |
|
||||
|\|C | normal cyan foreground color |
|
||||
|\|W | normal white (light grey) foreground color |
|
||||
|\|X | normal black foreground color |
|
||||
| \|\[# | background colours, e.g. \|\[c for bright cyan background and \|\[C a normal cyan background. |
|
||||
| \|!# | foreground color that inherits brightness from previous tags. Always uppcase, like \|!R |
|
||||
| \|h | make any following foreground ANSI colors bright (no effect on Xterm colors). Use with \|!#. Technically, \|h\|G == \|g. |
|
||||
| \|H | negates the effects of \|h, return foreground to normal (no effect on Xterm colors) |
|
||||
| \|/ | line break. Use instead of Python \\n when adding strings from in-game. |
|
||||
| \|- | tab character when adding strings in-game. Can vay per client, so usually better with spaces. |
|
||||
| \|_ | a space. Only needed to avoid auto-cropping at the end of a in-game input |
|
||||
| \|* | invert the current text/background colours, like a marker. See note below. |
|
||||
|
||||
Here is an example of the tags in action:
|
||||
|
||||
|
|
@ -44,35 +70,11 @@ Here is an example of the tags in action:
|
|||
|[rThis text has red background.|n This is normal text.
|
||||
|b|[yThis is bright blue text on yellow background.|n This is normal text.
|
||||
|
||||
- `|n` - this tag will turn off all color formatting, including background colors.
|
||||
- `|#`- markup marks the start of foreground color. The case defines if the text is "bright" or
|
||||
"normal". So `|g` is a bright green and `|G` is "normal" (darker) green.
|
||||
- `|[#` is used to add a background colour to the text. The case again specifies if it is "bright"
|
||||
or "normal", so `|[c` starts a bright cyan background and `|[C` a darker cyan background.
|
||||
- `|!#` is used to add foreground color without any enforced brightness/normal information.
|
||||
These are normal-intensity and are thus always given as uppercase, such as
|
||||
`|!R` for red. The difference between e.g. `|!R` and `|R` is that
|
||||
`|!R` will "inherit" the brightness setting from previously set color tags, whereas `|R` will
|
||||
always reset to the normal-intensity red. The `|#` format contains an implicit `|h`/`|H` tag in it:
|
||||
disabling highlighting when switching to a normal color, and enabling it for bright ones. So `|btest
|
||||
|!Rtest2` will result in a bright red `test2` since the brightness setting from `|b` "bleeds over".
|
||||
You could use this to for example quickly switch the intensity of a multitude of color tags. There
|
||||
is no background-color equivalent to `|!` style tags.
|
||||
- `|h` is used to make any following foreground ANSI colors bright (it has no effect on Xterm
|
||||
colors). This is only relevant to use with `|!` type tags and will be valid until the next `|n`,
|
||||
`|H` or normal (upper-case) `|#` tag. This tag will never affect background colors, those have to be
|
||||
set bright/normal explicitly. Technically, `|h|!G` is identical to `|g`.
|
||||
- `|H` negates the effects `|h` and returns all ANSI foreground colors (`|!` and `|` types) to
|
||||
'normal' intensity. It has no effect on background and Xterm colors.
|
||||
Note: The ANSI standard does not actually support bright backgrounds like `|[r` - the standard
|
||||
only supports "normal" intensity backgrounds. To get around this Evennia implements these as [Xterm256 colours](#xterm256-colours) behind the scenes. If the client does not support
|
||||
Xterm256 the ANSI colors will be used instead and there will be no visible difference between using upper- and lower-case background tags.
|
||||
|
||||
> Note: The ANSI standard does not actually support bright backgrounds like `|[r` - the standard
|
||||
only supports "normal" intensity backgrounds. To get around this Evennia instead implements these
|
||||
as [Xterm256 colours](#xterm256-colours) behind the scenes. If the client does not support
|
||||
Xterm256 the ANSI colors will be used instead and there will be no visible difference between using
|
||||
upper- and lower-case background tags.
|
||||
|
||||
If you want to display an ANSI marker as output text (without having any effect), you need to escape
|
||||
it by preceding its `|` with another `|`:
|
||||
If you want to display an ANSI marker as output text (without having any effect), you need to escape it by preceding its `|` with another `|`:
|
||||
|
||||
```
|
||||
say The ||r ANSI marker changes text color to bright red.
|
||||
|
|
@ -83,22 +85,12 @@ ansi art that uses `|` with a letter directly following it.
|
|||
|
||||
Use the command
|
||||
|
||||
@color ansi
|
||||
color ansi
|
||||
|
||||
to get a list of all supported ANSI colours and the tags used to produce them.
|
||||
|
||||
A few additional ANSI codes are supported:
|
||||
|
||||
- `|/` A line break. You cannot put the normal Python `\n` line breaks in text entered inside the
|
||||
game (Evennia will filter this for security reasons). This is what you use instead: use the `|/`
|
||||
marker to format text with line breaks from the game command line.
|
||||
- `` This will translate into a `TAB` character. This will not always show (or show differently) to
|
||||
the client since it depends on their local settings. It's often better to use multiple spaces.
|
||||
- `|_` This is a space. You can usually use the normal space character, but if the space is *at the
|
||||
end of the line*, Evennia will likely crop it. This tag will not be cropped but always result in a
|
||||
space.
|
||||
- `|*` This will invert the current text/background colours. Can be useful to mark things (but see
|
||||
below).
|
||||
|
||||
### Caveats of `|*`
|
||||
|
||||
|
|
@ -140,44 +132,45 @@ manually instead.
|
|||
|
||||
### Xterm256 Colours
|
||||
|
||||
The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background.
|
||||
The _Xterm256_ standard is a colour scheme that supports 256 colours for text and/or background. It can be combined freely with ANSI colors (above), but some ANSI tags don't affect Xterm256 tags.
|
||||
|
||||
While this offers many more possibilities than traditional ANSI colours, be wary that too many text
|
||||
colors will be confusing to the eye. Also, not all clients support Xterm256 - these will instead see
|
||||
the closest equivalent ANSI color. You can mix Xterm256 tags with ANSI tags as you please.
|
||||
|
||||
|555 This is pure white text.|n This is normal text.
|
||||
|230 This is olive green text.
|
||||
|[300 This text has a dark red background.
|
||||
|005|[054 This is dark blue text on a bright cyan background.
|
||||
|=a This is a greyscale value, equal to black.
|
||||
|=m This is a greyscale value, midway between white and black.
|
||||
|=z This is a greyscale value, equal to white.
|
||||
|[=m This is a background greyscale value.
|
||||
| Tag | Effect |
|
||||
| ---- | ---- |
|
||||
| \|### | foreground RGB (red/green/blue), each from 0 to 5. |
|
||||
| \|\[### | background RGB |
|
||||
| \|=# | a-z foreground greyscale, where `a` is black and `z` is white. |
|
||||
| \|\[=#| a-z background greyscale
|
||||
|
||||
- `|###` - markup consists of three digits, each an integer from 0 to 5. The three digits describe
|
||||
the amount of **r**ed, **g**reen and **b**lue (RGB) components used in the colour. So `|500` means
|
||||
maximum red and none of the other colours - the result is a bright red. `|520` is red with a touch
|
||||
of green - the result is orange. As opposed to ANSI colors, Xterm256 syntax does not worry about
|
||||
bright/normal intensity, a brighter (lighter) color is just achieved by upping all RGB values with
|
||||
the same amount.
|
||||
- `|[###` - this works the same way but produces a coloured background.
|
||||
- `|=#` - markup produces the xterm256 gray scale tones, where `#` is a letter from `a` (black) to
|
||||
`z` (white). This offers many more nuances of gray than the normal `|###` markup (which only has
|
||||
four gray tones between solid black and white (`|000`, `|111`, `|222`, `|333` and `|444`)).
|
||||
- `|[=#` - this works in the same way but produces background gray scale tones.
|
||||
Some examples:
|
||||
|
||||
| Tag | Effect |
|
||||
| ---- | ---- |
|
||||
| \|500 | bright red |
|
||||
| \|050 | bright green |
|
||||
| \|005 | bright blue |
|
||||
| \|520 | red + a little green = orange |
|
||||
|\|555 | pure white foreground |
|
||||
|\|230 | olive green foreground |
|
||||
|\|\[300 | text with a dark red background |
|
||||
|\|005\|\[054 | dark blue text on a bright cyan background |
|
||||
|\|=a | greyscale foreground, equal to black |
|
||||
| \|=m | greyscale foreground, midway between white and black.
|
||||
| \|=z | greyscale foreground, equal to white |
|
||||
| \|\[=m | greyscale background |
|
||||
|
||||
Xterm256 don't use bright/normal intensity like ANSI does; intensity is just varied by increasing/decreasing all RGB values by the same amount.
|
||||
|
||||
If you have a client that supports Xterm256, you can use
|
||||
|
||||
@color xterm256
|
||||
color xterm256
|
||||
|
||||
to get a table of all the 256 colours and the codes that produce them. If the table looks broken up
|
||||
into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement.
|
||||
You can use the `@options` command to see if xterm256 is active for you. This depends on if your
|
||||
client told Evennia what it supports - if not, and you know what your client supports, you may have
|
||||
to activate some features manually.
|
||||
into a few blocks of colors, it means Xterm256 is not supported and ANSI are used as a replacement. You can use the `options` command to see if xterm256 is active for you. This depends on if your client told Evennia what it supports - if not, and you know what your client supports, you may have to activate some features manually.
|
||||
|
||||
## More reading
|
||||
|
||||
There is an [Understanding Color Tags](../Howtos/Understanding-Color-Tags.md) tutorial which expands on the
|
||||
use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.
|
||||
|
||||
There is an [Understanding Color Tags](../Howtos/Understanding-Color-Tags.md) tutorial which expands on the use of ANSI color tags and the pitfalls of mixing ANSI and Xterms256 color tags in the same context.
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# Adding custom commands
|
||||
|
||||
In this lesson we'll learn how to create our own Evennia _Commands_. If you are new to Python you'll also learn some more basics about how to manipulate strings and get information out of Evennia.
|
||||
In this lesson we'll learn how to create our own Evennia [Commands](../../../Components/Commands.md) If you are new to Python you'll also learn some more basics about how to manipulate strings and get information out of Evennia.
|
||||
|
||||
A Command is something that handles the input from a user and causes a result to happen.
|
||||
An example is `look`, which examines your current location and tells how it looks like and
|
||||
An example is `look`, which examines your current location and tells you what it looks like and
|
||||
what is in it.
|
||||
|
||||
```{sidebar} Commands are not typeclassed
|
||||
|
|
@ -14,20 +14,11 @@ database. They are "just" normal Python classes.
|
|||
```
|
||||
|
||||
In Evennia, a Command is a Python _class_. If you are unsure about what a class is, review the
|
||||
previous lessons! A Command inherits from `evennia.Command` or from one of the alternative command-
|
||||
classes, such as `MuxCommand` which is what most default commands use.
|
||||
[previous lesson about it](./Beginner-Tutorial-Python-classes-and-objects.md)! A Command inherits from `evennia.Command` or from one of the alternative command- classes, such as `MuxCommand` which is what most default commands use.
|
||||
|
||||
All Commands are in turn grouped in another class called a _Command Set_. Think of a Command Set
|
||||
as a bag holding many different commands. One CmdSet could for example hold all commands for
|
||||
combat, another for building etc. By default, Evennia groups all character-commands into one
|
||||
big cmdset.
|
||||
All Commands are grouped in another class called a _Command Set_. Think of a Command Set as a bag holding many different commands. One CmdSet could for example hold all commands for combat, another for building etc.
|
||||
|
||||
Command-Sets are then associated with objects, for example with your Character. Doing so makes the
|
||||
commands in that cmdset available to the object. So, to summarize:
|
||||
|
||||
- Commands are classes
|
||||
- A group of Commands is stored in a CmdSet
|
||||
- CmdSets are stored on objects - this defines which commands are available to that object.
|
||||
Command-Sets are then associated with objects, for example with your Character. Doing so makes the commands in that cmdset available to the object. By default, Evennia groups all character-commands into one big cmdset called the `CharacterCmdSet`. It sits on `DefaultCharacter` (and thus, through inheritance, on `typeclasses.characters.Character`).
|
||||
|
||||
## Creating a custom command
|
||||
|
||||
|
|
@ -53,18 +44,14 @@ class Command(BaseCommand):
|
|||
|
||||
Ignoring the docstrings (which you can read if you want), this is the only really active code in the module.
|
||||
|
||||
We can see that we import `Command` from `evennia` and use the `from ... import ... as ...` form to rename it
|
||||
to `BaseCommand`. This is so we can let our child class also be named `Command` for reference. The class
|
||||
itself doesn't do anything, it just has `pass`. So in the same way as `Object` in the previous lesson, this
|
||||
class is identical to its parent.
|
||||
We can see that we import `Command` from `evennia` and use the `from ... import ... as ...` form to rename it to `BaseCommand`. This is so we can let our child class also be named `Command` to make it easier to reference. The class itself doesn't do anything, it just has `pass`. So in the same way as `Object` and `Character` in the previous lessons, this class is identical to its parent.
|
||||
|
||||
> The commented out `default_cmds` gives us access to Evennia's default commands for easy overriding. We'll try
|
||||
> that a little later.
|
||||
> The commented out `default_cmds` gives us access to Evennia's default commands for easy overriding. We'll try that a little later.
|
||||
|
||||
We could modify this module directly, but to train imports we'll work in a separate module. Open a new file
|
||||
`mygame/commands/mycommands.py` and add the following code:
|
||||
We could modify this module directly, but let's work in a separate module just for the heck of it. Open a new file `mygame/commands/mycommands.py` and add the following code:
|
||||
|
||||
```python
|
||||
# in mygame/commands/mycommands.py
|
||||
|
||||
from commands.command import Command
|
||||
|
||||
|
|
@ -95,15 +82,13 @@ class MyCmdSet(CmdSet):
|
|||
|
||||
```
|
||||
|
||||
Our `EchoCmdSet` class must have an `at_cmdset_creation` method, named exactly
|
||||
like this - this is what Evennia will be looking for when setting up the cmdset later, so
|
||||
if you didn't set it up, it will use the parent's version, which is empty. Inside we add the
|
||||
command class to the cmdset by `self.add()`. If you wanted to add more commands to this CmdSet you
|
||||
could just add more lines of `self.add` after this.
|
||||
Our `EchoCmdSet` class must have an `at_cmdset_creation` method, named exactly like this - this is what Evennia will be looking for when setting up the cmdset later, so if you didn't set it up, it will use the parent's version, which is empty. Inside we add the command class to the cmdset by `self.add()`. If you wanted to add more commands to this CmdSet you could just add more lines of `self.add` after this.
|
||||
|
||||
Finally, let's add this command to ourselves so we can try it out. In-game you can experiment with `py` again:
|
||||
|
||||
> py self.cmdset.add("commands.mycommands.MyCmdSet")
|
||||
> py me.cmdset.add("commands.mycommands.MyCmdSet")
|
||||
|
||||
The `me.cmdset` is the store of all cmdsets stored on us. By giving the path to our CmdSet class, it will be added.
|
||||
|
||||
Now try
|
||||
|
||||
|
|
@ -112,9 +97,7 @@ Now try
|
|||
...
|
||||
...
|
||||
|
||||
You should be getting a long list of outputs. The reason for this is that your `echo` function is not really
|
||||
"doing" anything yet and the default function is then to show all useful resources available to you when you
|
||||
use your Command. Let's look at some of those listed:
|
||||
`echo` works! You should be getting a long list of outputs. The reason for this is that your `echo` function is not really "doing" anything yet and the default function is then to show all useful resources available to you when you use your Command. Let's look at some of those listed:
|
||||
|
||||
Command echo has no defined `func()` - showing on-command variables:
|
||||
obj (<class 'typeclasses.characters.Character'>): YourName
|
||||
|
|
@ -146,17 +129,13 @@ use your Command. Let's look at some of those listed:
|
|||
command string given (self.cmdstring): echo
|
||||
current cmdset (self.cmdset): ChannelCmdSet
|
||||
|
||||
These are all properties you can access with `.` on the Command instance, such as `.key`, `.args` and so on.
|
||||
Evennia makes these available to you and they will be different every time a command is run. The most
|
||||
important ones we will make use of now are:
|
||||
These are all properties you can access with `.` on the Command instance, such as `.key`, `.args` and so on. Evennia makes these available to you and they will be different every time a command is run. The most important ones we will make use of now are:
|
||||
|
||||
- `caller` - this is 'you', the person calling the command.
|
||||
- `args` - this is all arguments to the command. Now it's empty, but if you tried `echo foo bar` you'd find
|
||||
that this would be `" foo bar"`.
|
||||
- `args` - this is all arguments to the command. Now it's empty, but if you tried `echo foo bar` you'd find that this would be `" foo bar"`.
|
||||
- `obj` - this is object on which this Command (and CmdSet) "sits". So you, in this case.
|
||||
|
||||
The reason our command doesn't do anything yet is because it's missing a `func` method. This is what Evennia
|
||||
looks for to figure out what a Command actually does. Modify your `CmdEcho` class:
|
||||
The reason our command doesn't do anything yet is because it's missing a `func` method. This is what Evennia looks for to figure out what a Command actually does. Modify your `CmdEcho` class:
|
||||
|
||||
```python
|
||||
# ...
|
||||
|
|
@ -177,15 +156,15 @@ class CmdEcho(Command):
|
|||
# ...
|
||||
```
|
||||
|
||||
First we added a docstring. This is always a good thing to do in general, but for a Command class, it will also
|
||||
automatically become the in-game help entry! Next we add the `func` method. It has one active line where it
|
||||
makes use of some of those variables we found the Command offers to us. If you did the
|
||||
[basic Python tutorial](./Beginner-Tutorial-Python-basic-introduction.md), you will recognize `.msg` - this will send a message
|
||||
to the object it is attached to us - in this case `self.caller`, that is, us. We grab `self.args` and includes
|
||||
that in the message.
|
||||
First we added a docstring. This is always a good thing to do in general, but for a Command class, it will also automatically become the in-game help entry!
|
||||
|
||||
Since we haven't changed `MyCmdSet`, that will work as before. Reload and re-add this command to ourselves to
|
||||
try out the new version:
|
||||
```{sidebar} Use Command.msg
|
||||
In a Command class, the `self.msg()` acts as a convenient shortcut for `self.caller.msg()`. Not only is it shorter, it also has some advantages because the command can include more metadata with the message. So using `self.msg()` is usually better. For this tutorial though, `self.caller.msg()` is more explicit in showing what is going on.
|
||||
```
|
||||
|
||||
Next we add the `func` method. It has one active line where it makes use of some of those variables the Command class offers to us. If you did the [basic Python tutorial](./Beginner-Tutorial-Python-basic-introduction.md), you will recognize `.msg` - this will send a message to the object it is attached to us - in this case `self.caller`, that is, us. We grab `self.args` and includes that in the message.
|
||||
|
||||
Since we haven't changed `MyCmdSet`, that will work as before. Reload and re-add this command to ourselves to try out the new version:
|
||||
|
||||
> reload
|
||||
> py self.cmdset.add("commands.mycommands.MyCmdSet")
|
||||
|
|
@ -197,14 +176,12 @@ Try to pass an argument:
|
|||
> echo Woo Tang!
|
||||
Echo: ' Woo Tang!'
|
||||
|
||||
Note that there is an extra space before `Woo!`. That is because self.args contains the _everything_ after
|
||||
the command name, including spaces. Evennia will happily understand if you skip that space too:
|
||||
Note that there is an extra space before `Woo!`. That is because self.args contains _everything_ after the command name, including spaces. Evennia will happily understand if you skip that space too:
|
||||
|
||||
> echoWoo Tang!
|
||||
Echo: 'Woo Tang!'
|
||||
|
||||
There are ways to force Evennia to _require_ an initial space, but right now we want to just ignore it since
|
||||
it looks a bit weird for our echo example. Tweak the code:
|
||||
There are ways to force Evennia to _require_ an initial space, but right now we want to just ignore it since it looks a bit weird for our echo example. Tweak the code:
|
||||
|
||||
```python
|
||||
# ...
|
||||
|
|
@ -225,9 +202,7 @@ class CmdEcho(Command):
|
|||
# ...
|
||||
```
|
||||
|
||||
The only difference is that we called `.strip()` on `self.args`. This is a helper method available on all
|
||||
strings - it strips out all whitespace before and after the string. Now the Command-argument will no longer
|
||||
have any space in front of it.
|
||||
The only difference is that we called `.strip()` on `self.args`. This is a helper method available on all strings - it strips out all whitespace before and after the string. Now the Command-argument will no longer have any space in front of it.
|
||||
|
||||
> reload
|
||||
> py self.cmdset.add("commands.mycommands.MyCmdSet")
|
||||
|
|
@ -238,7 +213,7 @@ Don't forget to look at the help for the echo command:
|
|||
|
||||
> help echo
|
||||
|
||||
You will get the docstring you put in your Command-class.
|
||||
You will get the docstring you put in your Command-class!
|
||||
|
||||
### Making our cmdset persistent
|
||||
|
||||
|
|
@ -248,7 +223,7 @@ enough to make `echo` a _persistent_ change though:
|
|||
> py self.cmdset.add("commands.mycommands.MyCmdSet", persistent=True)
|
||||
|
||||
Now you can `reload` as much as you want and your code changes will be available directly without
|
||||
needing to re-add the MyCmdSet again. To remove the cmdset again, do
|
||||
needing to re-add the MyCmdSet again. To remove the cmdset again, you'd do
|
||||
|
||||
> py self.cmdset.remove("commands.mycommands.MyCmdSet")
|
||||
|
||||
|
|
@ -262,7 +237,7 @@ someone in the face! This is how we want it to work:
|
|||
> hit <target>
|
||||
You hit <target> with full force!
|
||||
|
||||
Not only that, we want the <target> to see
|
||||
Not only that, we want the `<target>` to see
|
||||
|
||||
You got hit by <hitter> with full force!
|
||||
|
||||
|
|
@ -324,9 +299,7 @@ the `else` condition is given, it will run if none of the other conditions was t
|
|||
the `if..elif..else` structure also serves the same function as `case` in some other languages.
|
||||
|
||||
```
|
||||
- **Line 15** has our first _conditional_, an `if` statement. This is written on the form `if <condition>:` and only
|
||||
if that condition is 'truthy' will the indented code block under the `if` statement run. To learn what is truthy in
|
||||
Python it's usually easier to learn what is "falsy":
|
||||
- **Line 15** has our first _conditional_, an `if` statement. This is written on the form `if <condition>:` and only if that condition is 'truthy' will the indented code block under the `if` statement run. To learn what is truthy in Python it's usually easier to learn what is "falsy":
|
||||
- `False` - this is a reserved boolean word in Python. The opposite is `True`.
|
||||
- `None` - another reserved word. This represents nothing, a null-result or value.
|
||||
- `0` or `0.0`
|
||||
|
|
@ -334,13 +307,11 @@ the `if..elif..else` structure also serves the same function as `case` in some o
|
|||
- Empty _iterables_ we haven't seen yet, like empty lists `[]`, empty tuples `()` and empty dicts `{}`.
|
||||
- Everything else is "truthy".
|
||||
|
||||
Line 16's condition is `not args`. The `not` _inverses_ the result, so if `args` is the empty string (falsy), the
|
||||
whole conditional becomes truthy. Let's continue in the code:
|
||||
- **Line 16**'s condition is `not args`. The `not` _inverses_ the result, so if `args` is the empty string (falsy), the whole conditional becomes truthy. Let's continue in the code:
|
||||
- **Lines 16-17**: This code will only run if the `if` statement is truthy, in this case if `args` is the empty string.
|
||||
- **Line 17**: `return` is a reserved Python word that exits `func` immediately.
|
||||
- **Line 18**: We use `self.caller.search` to look for the target in the current location.
|
||||
- **Lines 19-20**: A feature of `.search` is that it will already inform `self.caller` if it couldn't find the target.
|
||||
In that case, `target` will be `None` and we should just directly `return`.
|
||||
- **Lines 19-20**: A feature of `.search` is that it will already inform `self.caller` if it couldn't find the target. In that case, `target` will be `None` and we should just directly `return`.
|
||||
- **Lines 21-22**: At this point we have a suitable target and can send our punching strings to each.
|
||||
|
||||
Finally we must also add this to a CmdSet. Let's add it to `MyCmdSet` which we made persistent earlier.
|
||||
|
|
@ -361,10 +332,7 @@ class MyCmdSet(CmdSet):
|
|||
With longer code snippets to try, it gets more and more likely you'll
|
||||
make an error and get a `traceback` when you reload. This will either appear
|
||||
directly in-game or in your log (view it with `evennia -l` in a terminal).
|
||||
Don't panic; tracebacks are your friends - they are to be read bottom-up and usually describe
|
||||
exactly where your problem is. Refer to `The Python intro <Python-basic-introduction.html>`_ for
|
||||
more hints. If you get stuck, reach out to the Evennia community for help.
|
||||
|
||||
Don't panic; tracebacks are your friends - they are to be read bottom-up and usually describe exactly where your problem is. Refer to [The Python introduction lesson](./Beginner-Tutorial-Python-basic-introduction.md) for more hints. If you get stuck, reach out to the Evennia community for help.
|
||||
```
|
||||
|
||||
Next we reload to let Evennia know of these code changes and try it out:
|
||||
|
|
@ -387,8 +355,7 @@ You won't see the second string. Only Smaug sees that (and is not amused).
|
|||
|
||||
## Summary
|
||||
|
||||
In this lesson we learned how to create our own Command, add it to a CmdSet and then to ourselves.
|
||||
We also upset a dragon.
|
||||
In this lesson we learned how to create our own Command, add it to a CmdSet and then to ourselves. We also upset a dragon.
|
||||
|
||||
In the next lesson we'll learn how to hit Smaug with different weapons. We'll also
|
||||
get into how we replace and extend Evennia's default Commands.
|
||||
|
|
|
|||
|
|
@ -4,18 +4,11 @@
|
|||
|
||||
API stands for `Application Programming Interface`, a description for how to access the resources of a program or library.
|
||||
```
|
||||
A good place to start exploring Evennia is the [Evenia-API frontpage](../../../Evennia-API.md).
|
||||
This page sums up the main components of Evennia with a short description of each. Try clicking through
|
||||
to a few entries - once you get deep enough you'll see full descriptions
|
||||
of each component along with their documentation. You can also click `[source]` to see the full Python source
|
||||
for each thing.
|
||||
|
||||
You can also browse [the evennia repository on github](https://github.com/evennia/evennia). This is exactly
|
||||
what you can download from us. The github repo is also searchable.
|
||||
|
||||
Finally, you can clone the evennia repo to your own computer and read the sources locally. This is necessary
|
||||
if you want to help with Evennia's development itself. See the
|
||||
[extended install instructions](../../../Setup/Installation-Git.md) if you want to do this.
|
||||
There are several good ways to explore the Evennia library.
|
||||
- This documentation contains the [Evennia-API docs](../../../Evennia-API.md), generated automatically from sources. Try clicking through to a few entries - once you get deep enough you'll see full descriptions of each component along with their documentation. You can also click `[source]` to see the full Python source code for each thing.
|
||||
- There are [separate doc pages for each component](../../../Components/Components-Overview.md) if you want more detailed explanations.
|
||||
- You can browse [the evennia repository on github](https://github.com/evennia/evennia). This is exactly what you can download from us.
|
||||
- Finally, you can clone the evennia repo to your own computer and read the sources. This is necessary if you want to *really* understand what's going on, or help with Evennia's development. See the [extended install instructions](../../../Setup/Installation-Git.md) if you want to do this.
|
||||
|
||||
## Where is it?
|
||||
|
||||
|
|
@ -27,10 +20,9 @@ If Evennia is installed, you can import from it simply with
|
|||
|
||||
and so on.
|
||||
|
||||
If you installed Evennia with `pip install`, the library folder will be installed deep inside your Python
|
||||
installation. If you cloned the repo there will be a folder `evennia` on your hard drive there.
|
||||
If you installed Evennia with `pip install`, the library folder will be installed deep inside your Python installation; you are better off [looking at it on github](github:evennia). If you cloned it, you should have an `evennia` folder to look into.
|
||||
|
||||
If you cloned the repo or read the code on `github` you'll find this being the outermost structure:
|
||||
You'll find this being the outermost structure:
|
||||
|
||||
evennia/
|
||||
bin/
|
||||
|
|
@ -46,7 +38,7 @@ the _actual_ library, the thing covered by the API auto-docs and what you get wh
|
|||
> The `evennia/docs/` folder contains the sources for this documentation. See
|
||||
> [contributing to the docs](../../../Contributing-Docs.md) if you want to learn more about how this works.
|
||||
|
||||
This the the structure of the Evennia library:
|
||||
This is the structure of the Evennia library:
|
||||
|
||||
- evennia
|
||||
- [`__init__.py`](../../../Evennia-API.md#shortcuts) - The "flat API" of Evennia resides here.
|
||||
|
|
@ -76,22 +68,32 @@ The `__init__.py` file is a special Python filename used to represent a Python '
|
|||
|
||||
```
|
||||
|
||||
While all the actual Evennia code is found in the various folders, the `__init__.py` represents the entire
|
||||
package `evennia`. It contains "shortcuts" to code that is actually located elsewhere. Most of these shortcuts
|
||||
are listed if you [scroll down a bit](../../../Evennia-API.md) on the Evennia-API page.
|
||||
While all the actual Evennia code is found in the various folders, the `__init__.py` represents the entire package `evennia`. It contains "shortcuts" to code that is actually located elsewhere. Most of these shortcuts are listed if you [scroll down a bit](../../../Evennia-API.md) on the Evennia-API page.
|
||||
|
||||
## An example of exploring the library
|
||||
|
||||
In the previous lesson we took a brief look at `mygame/typeclasses/objects` as an example of a Python module. Let's
|
||||
open it again. Inside is the `Object` class, which inherits from `DefaultObject`.
|
||||
Near the top of the module is this line:
|
||||
In the [previous lesson](./Beginner-Tutorial-Python-classes-and-objects.md#on-classes-and-objects) we took a brief look at `mygame/typeclasses/objects` as an example of a Python module. Let's open it again.
|
||||
|
||||
```python
|
||||
"""
|
||||
module docstring
|
||||
"""
|
||||
from evennia import DefaultObject
|
||||
|
||||
class Object(DefaultObject):
|
||||
"""
|
||||
class docstring
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
We have the `Object` class, which inherits from `DefaultObject`. Near the top of the module is this line:
|
||||
|
||||
from evennia import DefaultObject
|
||||
|
||||
We want to figure out just what this DefaultObject offers. Since this is imported directly from `evennia`, we
|
||||
are actually importing from `evennia/__init__.py`.
|
||||
We want to figure out just what this DefaultObject offers. Since this is imported directly from `evennia`, we are actually importing from `evennia/__init__.py`.
|
||||
|
||||
[Look at Line 159](github:evennia/__init__.py#159) of `evennia/__init__.py` and you'll find this line:
|
||||
[Look at Line 160](github:evennia/__init__.py#L160) of `evennia/__init__.py` and you'll find this line:
|
||||
|
||||
from .objects.objects import DefaultObject
|
||||
|
||||
|
|
@ -100,17 +102,19 @@ are actually importing from `evennia/__init__.py`.
|
|||
The first full-stop in `from .objects.objects ...` means that we are importing from the current location. This is called a `relative import`. By comparison, `from evennia.objects.objects` is an `absolute import`. In this particular case, the two would give the same result.
|
||||
```
|
||||
|
||||
> You can also look at [the right section of the API frontpage](../../../Evennia-API.md#typeclasses) and click through
|
||||
> to the code that way.
|
||||
> You can also look at [the right section of the API frontpage](../../../Evennia-API.md#typeclasses) and click through to the code that way.
|
||||
|
||||
The fact that `DefaultObject` is imported into `__init__.py` here is what makes it possible to also import
|
||||
it as `from evennia import DefaultObject` even though the code for the class is not actually here.
|
||||
The fact that `DefaultObject` is imported into `__init__.py` here is what makes it possible to also import it as `from evennia import DefaultObject` even though the code for the class is not actually here.
|
||||
|
||||
So to find the code for `DefaultObject` we need to look in `evennia/objects/objects.py`. Here's how
|
||||
to look it up in the docs:
|
||||
So to find the code for `DefaultObject` we need to look in `evennia/objects/objects.py`. Here's how to look it up in the docs:
|
||||
|
||||
1. Open the [API frontpage](../../../Evennia-API.md)
|
||||
2. Locate the link to [evennia.objects.objects](../../../api/evennia.objects.objects.md) and click on it.
|
||||
3 You are now in the python module. Scroll down (or search in your web browser) to find the `DefaultObject` class.
|
||||
4 You can now read what this does and what methods are on it. If you want to see the full source, click the
|
||||
\[source\] link next to it.
|
||||
3. You are now in the python module. Scroll down (or search in your web browser) to find the `DefaultObject` class.
|
||||
4. You can now read what this does and what methods are on it. If you want to see the full source, click the \[source\] link next to it.
|
||||
|
||||
## Conclusions
|
||||
|
||||
This is an important lesson. It teaches you how to find information for yourself. Knowing how to follow the class inheritance tree and navigate to things you need is a big part in learning a new library like Evennia.
|
||||
|
||||
Next we'll start to make use of what we have learned so far and combine it with the building blocks provided by Evennia.
|
||||
|
|
@ -1,31 +1,25 @@
|
|||
# Overview of your new Game Dir
|
||||
|
||||
Next we will take a little detour to look at the _Tutorial World_. This is a little solo adventure
|
||||
that comes with Evennia, a showcase for some of the things that are possible.
|
||||
Until now we have 'run the game' a bit and started playing with Python inside Evennia.
|
||||
It is time to start to look at how things look 'outside of the game'.
|
||||
|
||||
Now we have 'run the game' a bit and started with our forays into Python from inside Evennia.
|
||||
It is time to start to look at how things look 'outside of the game'. Let's do a tour of your game-dir
|
||||
Like everywhere in the docs we'll assume it's called `mygame`.
|
||||
Let's do a tour of your game-dir (we assume it's called `mygame`).
|
||||
|
||||
> When looking through files, ignore files ending with `.pyc` and the
|
||||
`__pycache__` folder if it exists. This is internal Python compilation files that you should never
|
||||
> need to touch. Files `__init__.py` is also often empty and can be ignored (they have to do with
|
||||
> Python package management).
|
||||
|
||||
You may have noticed when we were building things in-game that we would often refer to code through
|
||||
"python paths", such as
|
||||
You may have noticed when we were building things in-game that we would often refer to code through "python paths", such as
|
||||
|
||||
create/drop button:tutorial_examples.red_button.RedButton
|
||||
|
||||
This is a fundamental aspect of coding Evennia - _you create code and then you tell Evennia where that code is and when it should be used_. Above we told it to create a red button by pulling from specific code in the `contrib/` folder. The same principle is true everywhere. So it's important to know where code is and how you point to it correctly.
|
||||
|
||||
```{sidebar} Python-paths
|
||||
|
||||
A 'python path' uses '.' instead of '/' or '`\\`' and skips the `.py` ending of files. It can also point to the code contents of python files. Since Evennia is already looking for code in your game dir, your python paths can start from there. So a path `/home/foo/devel/mygame/commands/command.py` would translate to a Python-path `commands.command`.
|
||||
```
|
||||
|
||||
create/drop button:tutorial_examples.red_button.RedButton
|
||||
|
||||
This is a fundamental aspect of coding Evennia - _you create code and then you tell Evennia where that
|
||||
code is and when it should be used_. Above we told it to create a red button by pulling from specific code
|
||||
in the `contribs/` folder but the same principle is true everywhere. So it's important to know where code is
|
||||
and how you point to it correctly.
|
||||
|
||||
- `mygame/`
|
||||
- `commands/` - This holds all your custom commands (user-input handlers). You both add your own
|
||||
|
|
@ -42,11 +36,8 @@ and how you point to it correctly.
|
|||
- `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.
|
||||
|
||||
> The `server/` subfolder should remain the way it is - Evennia expects this. But you could in
|
||||
> principle change the structure of the rest of your game dir as best fits your preference.
|
||||
> Maybe you don't need a world/ folder but prefer many folders with different aspects of your world?
|
||||
> Or a new folder 'rules' for your RPG rules? This is fine. If you move things around you just need
|
||||
> to update Evennia's default settings to point to the right places in the new structure.
|
||||
> The `server/` subfolder should remain the way it is - Evennia expects this. But you can change the structure of the rest of your game dir as best fits your preferences.
|
||||
> Maybe you don't want a single world/ folder but prefer many folders with different aspects of your world? A new folder 'rules' for your RPG rules? Group your commands with your objects instead of having them separate? This is fine. If you move things around you just need to update Evennia's default settings to point to the right places in the new structure.
|
||||
|
||||
## commands/
|
||||
|
||||
|
|
@ -91,9 +82,8 @@ Common for the settings is that you generally will never them directly via their
|
|||
knows where they are and will read them to configure itself at startup.
|
||||
|
||||
- `settings.py` - this is by far the most important file. It's nearly empty by default, rather you
|
||||
are expected to copy&paste the changes you need from [evennia/default_settings.py](github:evennia/default_settings.py).
|
||||
The default settings file is extensively documented. Importing/accessing the values in the settings
|
||||
file is done in a special way, like this:
|
||||
are expected to copy&paste the changes you need from [evennia/default_settings.py](../../../Setup/Settings-Default.md).
|
||||
The default settings file is extensively documented. Importing/accessing the values in the settings file is done in a special way, like this:
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
|
@ -101,8 +91,7 @@ knows where they are and will read them to configure itself at startup.
|
|||
|
||||
telnet_port = settings.TELNET_PORT
|
||||
|
||||
You cannot assign to the settings file dynamically; you must change the `settings.py` file directly to
|
||||
change a setting.
|
||||
You cannot assign to the settings file dynamically; you must change the `settings.py` file directly to change a setting. See [Settings](../../../Setup/Settings.md) documentation for more details.
|
||||
- `secret_settings.py` - If you are making your code effort public, you may not want to share all settings online.
|
||||
There may be server-specific secrets or just fine-tuning for your game systems that you prefer be kept secret
|
||||
from the players. Put such settings in here, it will override values in `settings.py` and not be included in
|
||||
|
|
@ -162,13 +151,11 @@ be the same after a server reboot.
|
|||
[Exits](../../../Components/Objects.md#exits) is another subclass of Object. Exits link one Room to another.
|
||||
- [scripts.py](github:evennia/game_template/typeclasses/scripts.py) (Python-path: `typeclasses.scripts`) -
|
||||
[Scripts](../../../Components/Scripts.md) are 'out-of-character' objects. They have no location in-game and can serve as basis for
|
||||
anything that needs database persistence, such as combat, weather, or economic systems. They also
|
||||
have the ability to execute code repeatedly, on a timer.
|
||||
anything that needs database persistence, such as combat, weather, or economic systems. They also have the ability to execute code repeatedly, on a timer.
|
||||
|
||||
### web/
|
||||
|
||||
This folder contains folders for overriding the default web-presence of Evennia with your own designs.
|
||||
Most of these folders are empty except for a README file or a subset of other empty folders.
|
||||
This folder contains folders for overriding the default web-presence of Evennia with your own designs. Most of these folders are empty except for a README file or a subset of other empty folders. See [the Web overview](../../../Components/Components-Overview.md#web-components) for more details (we'll also get back to the web later in this beginner tutorial).
|
||||
|
||||
- `media/` - this empty folder is where you can place your own images or other media files you want the
|
||||
web server to serve. If you are releasing your game with a lot of media (especially if you want videos) you
|
||||
|
|
@ -192,8 +179,5 @@ people change and re-structure this in various ways to better fit their ideas.
|
|||
- [batch_cmds.ev](github:evennia/game_template/world/batch_cmds.ev) - This is an `.ev` file, which is essentially
|
||||
just a list of Evennia commands to execute in sequence. This one is empty and ready to expand on. The
|
||||
[Tutorial World](./Beginner-Tutorial-Tutorial-World.md) was built with such a batch-file.
|
||||
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Prototypes.md) is a way
|
||||
to easily vary objects without changing their base typeclass. For example, one could use prototypes to
|
||||
tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different
|
||||
equipment, stats and looks.
|
||||
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Prototypes.md) is a way to easily vary objects without changing their base typeclass. For example, one could use prototypes to tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different equipment, stats and looks.
|
||||
|
||||
|
|
|
|||
|
|
@ -51,25 +51,25 @@ module docstring
|
|||
"""
|
||||
from evennia import DefaultObject
|
||||
|
||||
class Object(DefaultObject):
|
||||
class ObjectParent:
|
||||
"""
|
||||
class docstring
|
||||
"""
|
||||
pass
|
||||
|
||||
class Object(ObjectParent, DefaultObject):
|
||||
"""
|
||||
class docstring
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
So we have a class `Object` that _inherits_ from `DefaultObject`, which we have imported from Evennia.
|
||||
The class itself doesn't do anything (it just `pass`es) but that doesn't mean it's useless. As we've seen,
|
||||
it inherits all the functionality of its parent. It's in fact an _exact replica_ of `DefaultObject` right now.
|
||||
If we knew what kind of methods and resources were available on `DefaultObject` we could add our own and
|
||||
change the way it works!
|
||||
So we have a class `Object` that _inherits_ from `ObjectParent` (which is empty) and `DefaultObject`, which we have imported from Evennia. The `ObjectParent` acts as a place to put code you want all
|
||||
of your `Objects` to have. We'll focus on `Object` and `DefaultObject` for now.
|
||||
|
||||
> Hint: We will get back to this, but to learn what resources an Evennia parent like `DefaultObject` offers,
|
||||
> easiest is to peek at its [API documentation](evennia.objects.objects.DefaultObject). The docstring for
|
||||
> the `Object` class can also help.
|
||||
The class itself doesn't do anything (it just `pass`es) but that doesn't mean it's useless. As we've seen, it inherits all the functionality of its parent. It's in fact an _exact replica_ of `DefaultObject` right now. Once we know what kind of methods and resources are available on `DefaultObject` we could add our own and change the way it works!
|
||||
|
||||
One thing that Evennia classes offers and which you don't get with vanilla Python classes is _persistence_. As
|
||||
you've found, Fluffy, Cuddly and Smaug are gone once we reload the server. Let's see if we can fix this.
|
||||
One thing that Evennia classes offers and which you don't get with vanilla Python classes is _persistence_ - they survive a server reload since they are stored in the database.
|
||||
|
||||
Go back to `mygame/typeclasses/monsters.py`. Change it as follows:
|
||||
|
||||
|
|
@ -102,9 +102,7 @@ class Dragon(Monster):
|
|||
|
||||
```
|
||||
|
||||
Don't forget to save. We removed `Monster.__init__` and made `Monster` inherit from Evennia's `Object` (which in turn
|
||||
inherits from Evennia's `DefaultObject`, as we saw). By extension, this means that `Dragon` also inherits
|
||||
from `DefaultObject`, just from further away!
|
||||
Don't forget to save. We removed `Monster.__init__` and made `Monster` inherit from Evennia's `Object` (which in turn inherits from Evennia's `DefaultObject`, as we saw). By extension, this means that `Dragon` also inherits from `DefaultObject`, just from further away!
|
||||
|
||||
### Making a new object by calling the class
|
||||
|
||||
|
|
@ -112,7 +110,7 @@ First reload the server as usual. We will need to create the dragon a little dif
|
|||
|
||||
```{sidebar} Keyword arguments
|
||||
|
||||
Keyword arguments (like `db_key="Smaug"`) is a way to name the input arguments to a function or method. They make things easier to read but also allows for conveniently setting defaults for values not given explicitly.
|
||||
_Keyword arguments_ (like `db_key="Smaug"`) is a way to name the input arguments to a function or method. They make things easier to read but also allows for conveniently setting defaults for values not given explicitly. We saw them previously in use for `.format()`.
|
||||
|
||||
```
|
||||
> py
|
||||
|
|
@ -126,6 +124,10 @@ Keyword arguments (like `db_key="Smaug"`) is a way to name the input arguments
|
|||
Smaug works the same as before, but we created him differently: first we used
|
||||
`Dragon(db_key="Smaug", db_location=here)` to create the object, and then we used `smaug.save()` afterwards.
|
||||
|
||||
```{sidebar} here
|
||||
The `here` used in `db_location=here` is a shortcut for your current location. This `here` (similar to `me`) is _only_ available to use in the `py` command; you can't use it in other Python code you write unless you define it yourself.
|
||||
```
|
||||
|
||||
> quit()
|
||||
Python Console is closing.
|
||||
> look
|
||||
|
|
@ -135,9 +137,7 @@ You should now see that Smaug _is in the room with you_. Woah!
|
|||
> reload
|
||||
> look
|
||||
|
||||
_He's still there_... What we just did was to create a new entry in the database for Smaug. We gave the object
|
||||
its name (key) and set its location to our current location (remember that `here` is just something available
|
||||
in the `py` command, you can't use it elsewhere).
|
||||
_He's still there_... What we just did was to create a new entry in the database for Smaug. We gave the object its name (key) and set its location to our current location.
|
||||
|
||||
To make use of Smaug in code we must first find him in the database. For an object in the current
|
||||
location we can easily do this in `py` by using `me.search()`:
|
||||
|
|
@ -150,78 +150,79 @@ location we can easily do this in `py` by using `me.search()`:
|
|||
Creating Smaug like we did above is nice because it's similar to how we created non-database
|
||||
bound Python instances before. But you need to use `db_key` instead of `key` and you also have to
|
||||
remember to call `.save()` afterwards. Evennia has a helper function that is more common to use,
|
||||
called `create_object`:
|
||||
called `create_object`. Let's recreate Cuddly this time:
|
||||
|
||||
> py fluffy = evennia.create_object('typeclases.monster.Monster', key="Fluffy", location=here)
|
||||
> py evennia.create_object('typeclasses.monster.Monster', key="Cuddly", location=here)
|
||||
> look
|
||||
|
||||
Boom, Fluffy should now be in the room with you, a little less scary than Smaug. You specify the
|
||||
python-path to the code you want and then set the key and location. Evennia sets things up and saves for you.
|
||||
Boom, Cuddly should now be in the room with you, a little less scary than Smaug. You specify the
|
||||
python-path to the code you want and then set the key and location (if you had the `Monster` class already imported, you could have passed that too). Evennia sets things up and saves for you.
|
||||
|
||||
If you want to find Fluffy from anywhere, you can use Evennia's `search_object` helper:
|
||||
If you want to find Smaug from anywhere (not just in the same room), you can use Evennia's `search_object` function:
|
||||
|
||||
> fluffy = evennia.search_object("Fluffy")[0] ; fluffy.move_around()
|
||||
Fluffy is moving!
|
||||
> cuddly = evennia.search_object("Cuddly")[0] ; cuddly.move_around()
|
||||
Cuddly is moving!
|
||||
|
||||
> The `[0]` is because `search_object` always returns a _list_ of zero, one or more found objects. The `[0]`
|
||||
means that we want the first element of this list (counting in Python always starts from 0). If there were
|
||||
multiple Fluffies we could get the second one with `[1]`.
|
||||
> The `[0]` is because `search_object` always returns a _list_ of zero, one or more found objects. The `[0]` means that we want the first element of this list (counting in Python always starts from 0). If there were multiple Cuddlies we could get the second one with `[1]`.
|
||||
|
||||
### Creating using create-command
|
||||
|
||||
Finally, you can also create a new Dragon using the familiar builder-commands we explored a few lessons ago:
|
||||
Finally, you can also create a new dragon using the familiar builder-commands we explored a few lessons ago:
|
||||
|
||||
> create/drop Cuddly:typeclasses.monsters.Monster
|
||||
> create/drop Fluffy:typeclasses.monsters.Dragon
|
||||
|
||||
Cuddly is now in the room. After learning about how objects are created you'll realize that all this command really
|
||||
does is to parse your input, figure out that `/drop` means to "give the object the same location as the caller",
|
||||
and then do a call akin to
|
||||
Fluffy is now in the room. After learning about how objects are created you'll realize that all this command really does is to parse your input, figure out that `/drop` means to "give the object the same location as the caller", and then do a call very similar to
|
||||
|
||||
evennia.create_object("typeclasses.monsters.Monster", key="Cuddly", location=here)
|
||||
evennia.create_object("typeclasses.monsters.Dragon", key="Cuddly", location=here)
|
||||
|
||||
That's pretty much all there is to the mighty `create` command! The rest is just parsing for the command
|
||||
to understand just what the user wants to create.
|
||||
That's pretty much all there is to the mighty `create` command! The rest is just parsing for the command to understand just what the user wants to create.
|
||||
|
||||
## Typeclasses
|
||||
|
||||
The `Object` (and `DefafultObject` class we inherited from above is what we refer to as a _Typeclass_. This
|
||||
is an Evennia thing. The instance of a typeclass saves itself to the database when it is created, and after
|
||||
that you can just search for it to get it back. We use the term _typeclass_ or _typeclassed_ to differentiate
|
||||
these types of classes and objects from the normal Python classes, whose instances go away on a reload.
|
||||
The `Object` (and `DefafultObject` class we inherited from above is what we refer to as a _Typeclass_. This is an Evennia thing. The instance of a typeclass saves itself to the database when it is created, and after that you can just search for it to get it back.
|
||||
|
||||
We use the term _typeclass_ or _typeclassed_ to differentiate these types of classes and objects from the normal Python classes, whose instances go away on a reload.
|
||||
|
||||
The number of typeclasses in Evennia are so few they can be learned by heart:
|
||||
|
||||
- `evennia.DefaultObject`: This is the parent of all in-game entities - everything with a location. Evennia makes
|
||||
a few very useful child classes of this class:
|
||||
- `evennia.DefaultCharacter`: The default entity represening a player avatar in-game.
|
||||
- `evennia.DefaultRoom`: A location in the game world.
|
||||
- `evennia.DefaultExit`: A link between locations.
|
||||
- `evennia.DefaultAccount`: The OOC representation of a player, holds password and account info.
|
||||
- `evennia.DefaultChannel`: In-game channels. These could be used for all sorts of in-game communication.
|
||||
- `evennia.DefaultScript`: Out-of-game objects, with no presence in the game world. Anything you want to create that
|
||||
needs to be persistent can be stored with these entities, such as combat state, economic systems or what have you.
|
||||
|
||||
If you take a look in `mygame/typeclasses/` you'll find modules for each of these. Each contains an empty child
|
||||
class ready that already inherits from the right parent, ready for you to modify or build from:
|
||||
| Evennia base typeclass | mygame.typeclasses child | description |
|
||||
| --------------- | --------------| ------------- |
|
||||
| `evennia.DefaultObject` | `typeclasses.objects.Object` | Everything with a location |
|
||||
| `evennia.DefaultCharacter` (child of `DefaultObject`) | `typeclasses.characters.Character` | Player avatars |
|
||||
| `evennia.DefaultRoom` (child of `DefaultObject`) | `typeclasses.rooms.Room` | In-game locations |
|
||||
| `evennia.DefaultExit` (chld of `DefaultObject`) | `typeclasses.exits.Exit` | Links between rooms |
|
||||
| `evennia.DefaultAccount` | `typeclasses.accounts.Account` | A player account |
|
||||
| `evennia.DefaultChannel` | `typeclasses.channels.Channel` | In-game comms |
|
||||
| `evennia.DefaultScript` | `typeclasses.scripts.Script` | Entities with no location |
|
||||
|
||||
- `mygame/typeclasses/objects.py` has `class Object(DefaultObject)`, a class directly inheriting the basic in-game entity, this
|
||||
works as a base for any object.
|
||||
- `mygame/typeclasses/characters.py` has `class Character(DefaultCharacter)`
|
||||
- `mygame/typeclasses/rooms.py` has `class Room(DefaultRoom)`
|
||||
- `mygame/typeclasses/exits.py` has `class Exit(DefaultExit)`
|
||||
- `mygame/typeclasses/accounts.py` has `class Account(DefaultAccount)`
|
||||
- `mygame/typeclasses/channels.py` has `class Channel(DefaultChannel)`
|
||||
- `mygame/typeclasses/scripts.py` has `class Script(DefaultScript)`
|
||||
The child classes under `mygame/typeclasses/` are meant for you to conveniently modify and
|
||||
work with. Every class inheriting (at any distance) from a Evennia base typeclass is also considered a typeclass.
|
||||
|
||||
> Notice that the classes in `mygame/typeclasses/` are _not inheriting from each other_. For example,
|
||||
> `Character` is inheriting from `evennia.DefaultCharacter` and not from `typeclasses.objects.Object`.
|
||||
> So if you change `Object` you will not cause any change in the `Character` class. If you want that you
|
||||
> can easily just change the child classes to inherit in that way instead; Evennia doesn't care.
|
||||
```
|
||||
from somewhere import Something
|
||||
from evennia import DefaultScript
|
||||
|
||||
As seen with our `Dragon` example, you don't _have_ to modify these modules directly. You can just make your
|
||||
own modules and import the base class.
|
||||
class MyOwnClass(Something):
|
||||
# not inheriting from an Evennia core typeclass, so this
|
||||
# is just a 'normal' Python class inheriting from somewhere
|
||||
pass
|
||||
|
||||
### Examining and defaults
|
||||
class MyOwnClass2(DefaultScript):
|
||||
# inherits from one of the core Evennia typeclasses, so
|
||||
# this is also considered a 'typeclass'.
|
||||
pass
|
||||
|
||||
```
|
||||
|
||||
```{sidebar} Why invent the name 'typeclass'?
|
||||
We separate 'regular classes' from 'typeclasses' because while typeclasses act _almost_ like normal Python classes, [there are some differences](../../../Components/Typeclasses.md). We will gloss over those differences for now, but they are worth to read up on when you want to do more advanced things later.
|
||||
```
|
||||
|
||||
Notice that the classes in `mygame/typeclasses/` are _not inheriting from each other_. For example, `Character` is inheriting from `evennia.DefaultCharacter` and not from `typeclasses.objects.Object`. So if you change `Object` you will not cause any change in the `Character` class. If you want that you can easily just change the child classes to inherit in that way instead; Evennia doesn't care.
|
||||
|
||||
As seen with our `Dragon` example, you don't _have_ to modify these modules directly. You can just make your own modules and import the base class.
|
||||
|
||||
### Examining objects
|
||||
|
||||
When you do
|
||||
|
||||
|
|
@ -253,20 +254,18 @@ may be more useful to us:
|
|||
- **Name/key** - The name of this thing. The value `(#14)` is probably different for you. This is the
|
||||
unique 'primary key' or _dbref_ for this entity in the database.
|
||||
- **Typeclass**: This show the typeclass we specified, and the path to it.
|
||||
- **Location**: We are in Limbo. If you moved elsewhere you'll see that instead. Also the `#dbref` is shown.
|
||||
- **Permissions**: _Permissions_ are like the inverse to _Locks_ - they are like keys to unlock access to other things.
|
||||
The giantess have no such keys (maybe fortunately).
|
||||
- **Locks**: Locks are the inverse of _Permissions_ - specify what criterion _other_ objects must fulfill in order to
|
||||
access the `giantess` object. This uses a very flexible mini-language. For examine, the line `examine:perm(Builders)`
|
||||
is read as "Only those with permission _Builder_ or higher can _examine_ this object". Since we are the superuser
|
||||
we pass (even bypass) such locks with ease.
|
||||
- **Persistent attributes**: This allows for storing arbitrary, persistent data on the typeclassed entity. We'll get
|
||||
to those in the next section.
|
||||
- **Location**: We are in Limbo. If you moved elsewhere you'll see that instead. Also the `#dbref` of Limbo is shown.
|
||||
- **Home**: All objects with a location (inheriting from `DefaultObject`) must have a home location. This is a backup to move the object to if its current location is deleted.
|
||||
- **Permissions**: _Permissions_ are like the inverse to _Locks_ - they are like keys to unlock access to other things. The giantess have no such keys (maybe fortunately). The [Permissions](../../../Components/Permissions.md) has more info.
|
||||
- **Locks**: Locks are the inverse of _Permissions_ - specify what criterion _other_ objects must fulfill in order to access the `giantess` object. This uses a very flexible mini-language. For examine, the line `examine:perm(Builders)` is read as "Only those with permission _Builder_ or higher can _examine_ this object". Since we are the superuser we pass (even bypass) such locks with ease. See the [Locks](../../../Components/Locks.md) documentation for more info.
|
||||
- **Persistent attributes**: This allows for storing arbitrary, persistent data on the typeclassed entity. We'll get to those in the next section.
|
||||
|
||||
Note how the **Typeclass** line describes exactly where to find the code of this object? This is very useful for
|
||||
understanding how any object in Evennia works.
|
||||
Note how the **Typeclass** line describes exactly where to find the code of this object? This is very useful for understanding how any object in Evennia works.
|
||||
|
||||
What happens if we _don't_ specify the typeclass though?
|
||||
|
||||
### Default typeclasses
|
||||
|
||||
What happens if we create an object and _don't_ specify its typeclass though?
|
||||
|
||||
> create/drop box
|
||||
You create a new Object: box.
|
||||
|
|
@ -283,23 +282,17 @@ You will find that the **Typeclass** line now reads
|
|||
|
||||
Typeclass: Object (typeclasses.objects.Object)
|
||||
|
||||
So when you didn't specify a typeclass, Evennia used a default, more specifically the (so far) empty `Object` class in
|
||||
`mygame/typeclasses/objects.py`. This is usually what you want, especially since you can tweak that class as much
|
||||
as you like.
|
||||
So when you didn't specify a typeclass, Evennia used a default, more specifically the (so far) empty `Object` class in `mygame/typeclasses/objects.py`. This is usually what you want, especially since you can tweak that class as much as you like.
|
||||
|
||||
But the reason Evennia knows to fall back to this class is not hard-coded - it's a setting. The default is
|
||||
in [evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py#L465),
|
||||
with the name `BASE_OBJECT_TYPECLASS`, which is set to `typeclasses.objects.Object`.
|
||||
But the reason Evennia knows to fall back to this class is not hard-coded - it's a setting. The default is in [evennia/settings_default.py](../../../Setup/Settings-Default.md), with the name `BASE_OBJECT_TYPECLASS`, which is set to `typeclasses.objects.Object`.
|
||||
|
||||
```{sidebar} Changing things
|
||||
|
||||
While it's tempting to change folders around to your liking, this can make it harder to follow tutorials and may confuse if you are asking others for help. So don't overdo it unless you really know what you are doing.
|
||||
While it's tempting to change folders around to your liking, this can make it harder to follow tutorials and may confuse if you are asking others for help. So don't overdo it unless you really know what you are doing.
|
||||
```
|
||||
|
||||
So if you wanted the creation commands and methods to default to some other class you could
|
||||
add your own `BASE_OBJECT_TYPECLASS` line to `mygame/server/conf/settings.py`. The same is true for all the other
|
||||
typeclasseses, like characters, rooms and accounts. This way you can change the
|
||||
layout of your game dir considerably if you wanted. You just need to tell Evennia where everything is.
|
||||
add your own `BASE_OBJECT_TYPECLASS` line to `mygame/server/conf/settings.py`. The same is true for all the other typeclasseses, like characters, rooms and accounts. This way you can change the layout of your game dir considerably if you wanted. You just need to tell Evennia where everything is.
|
||||
|
||||
## Modifying ourselves
|
||||
|
||||
|
|
@ -318,8 +311,8 @@ class Character(DefaultCharacter):
|
|||
pass
|
||||
```
|
||||
|
||||
This looks quite familiar now - an empty class inheriting from the Evennia base typeclass. As you would expect,
|
||||
this is also the default typeclass used for creating Characters if you don't specify it. You can verify it:
|
||||
This looks quite familiar now - an empty class inheriting from the Evennia base typeclass (it's even easier than `Object` since there is no equvalent `ParentObject` mixin class here). As you would expect, this is also the default typeclass used for creating Characters if you don't specify it. You can verify it:
|
||||
|
||||
|
||||
> examine me
|
||||
------------------------------------------------------------------------------
|
||||
|
|
@ -347,16 +340,15 @@ this is also the default typeclass used for creating Characters if you don't spe
|
|||
last_cmd = None
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
You got a lot longer output this time. You have a lot more going on than a simple Object. Here are some new fields of note:
|
||||
Yes, the `examine` command understands `me`. You got a lot longer output this time. You have a lot more going on than a simple Object. Here are some new fields of note:
|
||||
|
||||
- **Session id(s)**: This identifies the _Session_ (that is, the individual connection to a player's game client).
|
||||
- **Account** shows, well the `Account` object associated with this Character and Session.
|
||||
- **Stored/Merged Cmdsets** and **Commands available** is related to which _Commands_ are stored on you. We will
|
||||
get to them in the [next lesson](./Beginner-Tutorial-Adding-Commands.md). For now it's enough to know these consitute all the
|
||||
- **Stored/Merged Cmdsets** and **Commands available** is related to which _Commands_ are stored on you. We will get to them in the [next lesson](./Beginner-Tutorial-Adding-Commands.md). For now it's enough to know these consitute all the
|
||||
commands available to you at a given moment.
|
||||
- **Non-Persistent attributes** are Attributes that are only stored temporarily and will go away on next reload.
|
||||
|
||||
Look at the **Typeclass** field and you'll find that it points to `typeclasses.character.Character` as expected.
|
||||
So if we modify this class we'll also modify ourselves.
|
||||
Look at the **Typeclass** field and you'll find that it points to `typeclasses.character.Character` as expected. So if we modify this class we'll also modify ourselves.
|
||||
|
||||
### A method on ourselves
|
||||
|
||||
|
|
@ -369,15 +361,15 @@ class Character(DefaultCharacter):
|
|||
(class docstring)
|
||||
"""
|
||||
|
||||
str = 10
|
||||
dex = 12
|
||||
int = 15
|
||||
strength = 10
|
||||
dexterity = 12
|
||||
intelligence = 15
|
||||
|
||||
def get_stats(self):
|
||||
"""
|
||||
Get the main stats of this character
|
||||
"""
|
||||
return self.str, self.dex, self.int
|
||||
return self.strength, self.dexterity, self.intelligence
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -390,9 +382,7 @@ class Character(DefaultCharacter):
|
|||
- A `list` is written `[a, b, c, d, ...]`. It can be modified after creation.
|
||||
- A `tuple` is written `(a, b, c, ...)`. It cannot be modified once created.
|
||||
```
|
||||
We made a new method, gave it a docstring and had it `return` the RP-esque values we set. It comes back as a
|
||||
_tuple_ `(10, 12, 15)`. To get a specific value you could specify the _index_ of the value you want,
|
||||
starting from zero:
|
||||
We made a new method, gave it a docstring and had it `return` the RP-esque values we set. It comes back as a _tuple_ `(10, 12, 15)`. To get a specific value you could specify the _index_ of the value you want, starting from zero:
|
||||
|
||||
> py stats = self.get_stats() ; print(f"Strength is {stats[0]}.")
|
||||
Strength is 10.
|
||||
|
|
@ -401,15 +391,14 @@ starting from zero:
|
|||
|
||||
So what happens when we increase our strength? This would be one way:
|
||||
|
||||
> py self.str = self.str + 1
|
||||
> py self.str
|
||||
> py self.strength = self.str + 1
|
||||
> py self.strength
|
||||
11
|
||||
|
||||
Here we set the strength equal to its previous value + 1. A shorter way to write this is to use Python's `+=`
|
||||
operator:
|
||||
Here we set the strength equal to its previous value + 1. A shorter way to write this is to use Python's `+=` operator:
|
||||
|
||||
> py self.str += 1
|
||||
> py self.str
|
||||
> py self.strength += 1
|
||||
> py self.strength
|
||||
12
|
||||
> py self.get_stats()
|
||||
(12, 12, 15)
|
||||
|
|
@ -420,13 +409,9 @@ This looks correct! Try to change the values for dex and int too; it works fine.
|
|||
> py self.get_stats()
|
||||
(10, 12, 15)
|
||||
|
||||
After a reload all our changes were forgotten. When we change properties like this, it only changes in memory,
|
||||
not in the database (nor do we modify the python module's code). So when we reloaded, the 'fresh' `Character`
|
||||
class was loaded, and it still has the original stats we wrote to it.
|
||||
After a reload all our changes were forgotten. When we change properties like this, it only changes in memory, not in the database (nor do we modify the python module's code). So when we reloaded, the 'fresh' `Character` class was loaded, and it still has the original stats we wrote in it.
|
||||
|
||||
In principle we could change the python code. But we don't want to do that manually every time. And more importantly
|
||||
since we have the stats hardcoded in the class, _every_ character instance in the game will have exactly the
|
||||
same `str`, `dex` and `int` now! This is clearly not what we want.
|
||||
In principle we could change the python code. But we don't want to do that manually every time. And more importantly since we have the stats hardcoded in the class, _every_ character instance in the game will have exactly the same `str`, `dex` and `int` now! This is clearly not what we want.
|
||||
|
||||
Evennia offers a special, persistent type of property for this, called an `Attribute`. Rework your
|
||||
`mygame/typeclasses/characters.py` like this:
|
||||
|
|
@ -442,67 +427,56 @@ class Character(DefaultCharacter):
|
|||
"""
|
||||
Get the main stats of this character
|
||||
"""
|
||||
return self.db.str, self.db.dex, self.db.int
|
||||
return self.db.strength, self.db.dexterity, self.db.intelligence
|
||||
```
|
||||
|
||||
```{sidebar} Spaces in Attribute name?
|
||||
|
||||
What if you want spaces in your Attribute name? Or you want to assign the name of the Attribute on-the fly? Then you can use `.attributes.add(name, value)` instead, for example `self.attributes.add("str", 10)`.
|
||||
What if you want spaces in your Attribute name? Or you want to assign the name of the Attribute on-the fly? Then you can use `.attributes.add(name, value)` instead, for example `self.attributes.add("emotional intelligence", 10)`. You read it out again with `self.attributes.get("emotional intelligence"`.
|
||||
|
||||
```
|
||||
|
||||
We removed the hard-coded stats and added added `.db` for every stat. The `.db` handler makes the stat
|
||||
into an an Evennia `Attribute`.
|
||||
We removed the hard-coded stats and added added `.db` for every stat. The `.db` handler makes the stat into an an Evennia [Attribute](../../../Components/Attributes.md).
|
||||
|
||||
> reload
|
||||
> py self.get_stats()
|
||||
(None, None, None)
|
||||
|
||||
Since we removed the hard-coded values, Evennia don't know what they should be (yet). So all we get back
|
||||
is `None`, which is a Python reserved word to represent nothing, a no-value. This is different from a normal python
|
||||
property:
|
||||
Since we removed the hard-coded values, Evennia don't know what they should be (yet). So all we get back is `None`, which is a Python reserved word to represent nothing, a no-value. This is different from a normal python property:
|
||||
|
||||
> py self.str
|
||||
AttributeError: 'Character' object has no attribute 'str'
|
||||
> py self.db.str
|
||||
> py me.strength
|
||||
AttributeError: 'Character' object has no attribute 'strength'
|
||||
> py me.db.strength
|
||||
(nothing will be displayed, because it's None)
|
||||
|
||||
Trying to get an unknown normal Python property will give an error. Getting an unknown Evennia `Attribute` will
|
||||
never give an error, but only result in `None` being returned. This is often very practical.
|
||||
Trying to get an unknown normal Python property will give an error. Getting an unknown Evennia `Attribute` will never give an error, but only result in `None` being returned. This is often very practical.
|
||||
|
||||
> py self.db.str, self.db.dex, self.db.int = 10, 12, 15
|
||||
> py self.get_stats()
|
||||
Next, let us test out assigning those Attributes
|
||||
|
||||
> py me.db.strength, me.db.dexterity, me.db.intelligence = 10, 12, 15
|
||||
> py me.get_stats()
|
||||
(10, 12, 15)
|
||||
> reload
|
||||
> py self.get_stats()
|
||||
> py me.get_stats()
|
||||
(10, 12, 15)
|
||||
|
||||
Now we set the Attributes to the right values. We can see that things work the same as before, also after a
|
||||
server reload. Let's modify the strength:
|
||||
Now we set the Attributes to the right values, and they survive a server reload! Let's modify the strength:
|
||||
|
||||
> py self.db.str += 2
|
||||
> py self.db.strength += 2
|
||||
> py self.get_stats()
|
||||
(12, 12, 15)
|
||||
> reload
|
||||
> py self.get_stats()
|
||||
(12, 12, 15)
|
||||
|
||||
Our change now survives a reload since Evennia automatically saves the Attribute to the database for us.
|
||||
Also our change now survives a reload since Evennia automatically saves the Attribute to the database for us.
|
||||
|
||||
### Setting things on new Characters
|
||||
|
||||
Things a looking better, but one thing remains strange - the stats start out with a value `None` and we
|
||||
have to manually set them to something reasonable. In a later lesson we will investigate character-creation
|
||||
in more detail. For now, let's give every new character some random stats to start with.
|
||||
Things are looking better, but one thing remains strange - the stats start out with a value `None` and we have to manually set them to something reasonable. In a later lesson we will investigate character-creation in more detail. For now, let's give every new character some random stats to start with.
|
||||
|
||||
We want those stats to be set only once, when the object is first created. For the Character, this method
|
||||
is called `at_object_creation`.
|
||||
We want those stats to be set only once, when the object is first created. For the Character, this method is called `at_object_creation`.
|
||||
|
||||
```{sidebar} __init__ vs at_object_creation
|
||||
|
||||
For the `Monster` class we used `__init__` to set up the class. We can't use this for a typeclass because it will be called more than once, at the very least after every reload and maybe more depending on caching. Even if you are familiar with Python, avoid touching `__init__` for typeclasses, the results will not be what you expect.
|
||||
|
||||
```
|
||||
|
||||
```python
|
||||
# up by the other imports
|
||||
|
|
@ -514,15 +488,15 @@ class Character(DefaultCharacter):
|
|||
"""
|
||||
|
||||
def at_object_creation(self):
|
||||
self.db.str = random.randint(3, 18)
|
||||
self.db.dex = random.randint(3, 18)
|
||||
self.db.int = random.randint(3, 18)
|
||||
self.db.strength = random.randint(3, 18)
|
||||
self.db.dexterity = random.randint(3, 18)
|
||||
self.db.intelligence = random.randint(3, 18)
|
||||
|
||||
def get_stats(self):
|
||||
"""
|
||||
Get the main stats of this character
|
||||
"""
|
||||
return self.db.str, self.db.dex, self.db.int
|
||||
return self.db.strength, self.db.dexterity, self.db.intelligence
|
||||
```
|
||||
|
||||
We imported a new module, `random`. This is part of Python's standard library. We used `random.randint` to
|
||||
|
|
@ -532,9 +506,12 @@ set a random value from 3 to 18 to each stat. Simple, but for some classical RPG
|
|||
> py self.get_stats()
|
||||
(12, 12, 15)
|
||||
|
||||
Hm, this is the same values we set before. They are not random. The reason for this is of course that, as said,
|
||||
`at_object_creation` only runs _once_, the very first time a character is created. Our character object was already
|
||||
created long before, so it will not be called again.
|
||||
```{sidebar} __init__ vs at_object_creation
|
||||
|
||||
For the `Monster` class we used `__init__` to set up the class. We can't use this for a typeclass because it will be called more than once, at the very least after every reload and maybe more depending on caching. Even if you are familiar with Python, avoid touching `__init__` for typeclasses, the results will not be what you expect.
|
||||
|
||||
```
|
||||
Hm, this is the same values we set before. They are not random. The reason for this is of course that, as said, `at_object_creation` only runs _once_, the very first time a character is created. Our character object was already created long before, so it will not be called again.
|
||||
|
||||
It's simple enough to run it manually though:
|
||||
|
||||
|
|
@ -548,25 +525,28 @@ Lady luck didn't smile on us for this example; maybe you'll fare better. Evennia
|
|||
> update self
|
||||
> py self.get_stats()
|
||||
(8, 16, 14)
|
||||
|
||||
|
||||
### Updating all Characters in a loop
|
||||
|
||||
Needless to say, for your game you are wise to have a feel for what you want to go into the `at_object_creation` hook
|
||||
before you create a lot of objects (characters in this case). But should it come to that you don't want to have to
|
||||
go around and re-run the method on everyone manually. For the Python beginner, doing this will also give a chance to
|
||||
try out Python _loops_. We try them out in multi-line Python mode:
|
||||
```{sidebar} AttributeProperties
|
||||
There is another way to define Attributes on a class, known as [AttributeProperties](../../../Components/Attributes.md#using-attributeproperty). They can make it easier to maintain static default Attribute values on a typeclass. We will show them off when we make our game later in this tutorial series.
|
||||
```
|
||||
|
||||
Needless to say, you are wise to have a feel for what you want to go into the `at_object_creation` hook _before_ you create a lot of objects (characters in this case).
|
||||
|
||||
Luckily you only need to update objects once, and you don't have to go around and re-run the `at_object_creation` method on everyone manually. For this we'll try out a Python _loop_. Let's go into multi-line Python mode:
|
||||
|
||||
> py
|
||||
> for a in [1, 2, "foo"]: > print(a)
|
||||
> for a in [1, 2, "foo"]:
|
||||
> print(a)
|
||||
1
|
||||
2
|
||||
foo
|
||||
|
||||
A python _for-loop_ allows us to loop over something. Above, we made a _list_ of two numbers and a string. In
|
||||
every iteration of the loop, the variable `a` becomes one element in turn, and we print that.
|
||||
A python _for-loop_ allows us to loop over something. Above, we made a _list_ of two numbers and a string. In every iteration of the loop, the variable `a` becomes one element in turn, and we print that.
|
||||
|
||||
For our list, we want to loop over all Characters, and want to call `.at_object_creation` on each. This is how
|
||||
this is done (still in python multi-line mode):
|
||||
For our list, we want to loop over all Characters, and want to call `.at_object_creation` on each. This is how this is done (still in python multi-line mode):
|
||||
|
||||
> from typeclasses.characters import Character
|
||||
> for char in Character.objects.all()
|
||||
|
|
@ -574,7 +554,7 @@ this is done (still in python multi-line mode):
|
|||
|
||||
```{sidebar} Database queries
|
||||
|
||||
`Character.objects.all()` is an example of a database query expressed in Python. This will be converted into a database query under the hood. This syntax is part of [Django's query language](https://docs.djangoproject.com/en/4.1/topics/db/queries/). You don't need to know Django to use Evennia, but if you ever need more specific database queries, this is always available when you need it.
|
||||
`Character.objects.all()` is an example of a database query expressed in Python. This will be converted into a database query under the hood. This syntax is part of [Django's query language](https://docs.djangoproject.com/en/4.1/topics/db/queries/). You don't need to know Django to use Evennia, but if you ever need more specific database queries, this is always available when you need it. We'll get back to database queries in a later lesson.
|
||||
```
|
||||
We import the `Character` class and then we use `.objects.all()` to get all `Character` instances. Simplified,
|
||||
`.objects` is a resource from which one can _query_ for all `Characters`. Using `.all()` gets us a listing
|
||||
|
|
@ -587,19 +567,13 @@ of all of them that we then immediately loop over. Boom, we just updated all Cha
|
|||
|
||||
## Extra Credits
|
||||
|
||||
This principle is the same for other typeclasses. So using the tools explored in this lesson, try to expand
|
||||
the default room with an `is_dark` flag. It can be either `True` or `False`.
|
||||
Have all new rooms start with `is_dark = False` and make it so that once you change it, it survives a reload.
|
||||
This principle is the same for other typeclasses. So using the tools explored in this lesson, try to expand the default room with an `is_dark` flag. It can be either `True` or `False`. Have all new rooms start with `is_dark = False` and make it so that once you change it, it survives a reload.
|
||||
Oh, and if you created any other rooms before, make sure they get the new flag too!
|
||||
|
||||
## Conclusions
|
||||
|
||||
In this lesson we created database-persistent dragons by having their classes inherit from one `Object`, one
|
||||
of Evennia's _typeclasses_. We explored where Evennia looks for typeclasses if we don't specify the path
|
||||
explicitly. We then modified ourselves - via the `Character` class - to give us some simple RPG stats. This
|
||||
led to the need to use Evennia's _Attributes_, settable via `.db` and to use a for-loop to update ourselves.
|
||||
In this lesson we created database-persistent dragons by having their classes inherit from one `Object`, one of Evennia's _typeclasses_. We explored where Evennia looks for typeclasses if we don't specify the path explicitly. We then modified ourselves - via the `Character` class - to give us some simple RPG stats. This led to the need to use Evennia's _Attributes_, settable via `.db` and to use a for-loop to update ourselves.
|
||||
|
||||
Typeclasses are a fundamental part of Evennia and we will see a lot of more uses of them in the course of
|
||||
this tutorial. But that's enough of them for now. It's time to take some action. Let's learn about _Commands_.
|
||||
Typeclasses are a fundamental part of Evennia and we will see a lot of more uses of them in the course of this tutorial. But that's enough of them for now. It's time to take some action. Let's learn about _Commands_.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ is to use the `.format` _method_ of the string:
|
|||
This is a good idea!
|
||||
|
||||
```{sidebar} Functions and Methods
|
||||
- Function: Something that performs and action when you `call` it with zero or more `arguments`. A function is stand-alone in a python module, like `print()`
|
||||
- Function: Something that performs and action when you call it with zero or more `arguments`. A function is stand-alone in a python module, like `print()`
|
||||
- Method: A function that sits "on" an object, like `obj.msg()`.
|
||||
|
||||
```
|
||||
|
|
@ -119,8 +119,7 @@ trouble, use the Evennia web client.
|
|||
|
||||
What happened here was that we _assigned_ the string `"awesome sauce"` to a _variable_ we chose
|
||||
to name `a`. In the next statement, Python remembered what `a` was and we passed that into `format()`
|
||||
to get the output. If you replaced the value of `a` with something else in between, _that_ would be printed
|
||||
instead.
|
||||
to get the output. If you replaced the value of `a` with something else in between, _that_ would be printed instead.
|
||||
|
||||
Here's the stat-example again, moving the stats to variables (here we just set them, but in a real
|
||||
game they may be changed over time, or modified by circumstance):
|
||||
|
|
@ -128,13 +127,18 @@ game they may be changed over time, or modified by circumstance):
|
|||
> py stren, dext, intel = 13, 14, 8 ; print("STR: {}, DEX: {}, INT: {}".format(stren, dext, intel))
|
||||
STR: 13, DEX: 14, INT: 8
|
||||
|
||||
The point is that even if the values of the stats change, the print() statement would not change - it just keeps
|
||||
pretty-printing whatever is given to it.
|
||||
The point is that even if the values of the stats change, the print() statement would not change - it just keeps pretty-printing whatever is given to it.
|
||||
|
||||
You can also use named markers, like this:
|
||||
|
||||
> py print("STR: {stren}, INT: {intel}, STR again: {stren}".format(dext=10, intel=18, stren=9))
|
||||
STR: 9, INT: 18, Str again: 9
|
||||
|
||||
the `key=value` pairs we add are called _keyword arguments_ for the `format()` method. Each named argument will go to the matching `{key}` in the string. When using keywords, the order we add them doesn't matter. We have no `{dext}` and two `{stren}` in the string, and that works fine.
|
||||
|
||||
### f-strings
|
||||
|
||||
Using `.format()` is convenient (and there is a [lot more](https://www.w3schools.com/python/ref_string_format.asp)
|
||||
you can do with it). But the _f-string_ can be even more convenient. An
|
||||
Using `.format()` is convenient (and there is a [lot more](https://www.w3schools.com/python/ref_string_format.asp) you can do with it). But the _f-string_ can be even more convenient. An
|
||||
f-string looks like a normal string ... except there is an `f` front of it, like this:
|
||||
|
||||
f"this is now an f-string."
|
||||
|
|
@ -144,7 +148,7 @@ An f-string on its own is just like any other string. But let's redo the example
|
|||
> py a = "awesome sauce" ; print(f"This is {a}!")
|
||||
This is awesome sauce!
|
||||
|
||||
We could just insert that `a` variable directly into the f-string using `{a}`. Fewer parentheses to
|
||||
We insert that `a` variable directly into the f-string using `{a}`. Fewer parentheses to
|
||||
remember and arguable easier to read as well.
|
||||
|
||||
> py stren, dext, intel = 13, 14, 8 ; print(f"STR: {stren}, DEX: {dext}, INT: {intel}")
|
||||
|
|
@ -168,7 +172,7 @@ gives the normal text color. You can also use RGB (Red-Green-Blue) values from 0
|
|||
> If you don't see the expected color, your client or terminal may not support Xterm256 (or
|
||||
color at all). Use the Evennia webclient.
|
||||
|
||||
Use the commands `color ansi` or `color xterm` to see which colors are available. Experiment!
|
||||
Use the commands `color ansi` or `color xterm` to see which colors are available. Experiment! You can also read a lot more in the [Colors](../../../Concepts/Colors.md) documentation.
|
||||
|
||||
## Importing code from other modules
|
||||
|
||||
|
|
@ -209,11 +213,9 @@ If you make some error (we'll cover how to handle errors below), fix the error i
|
|||
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.
|
||||
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 should not 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".
|
||||
|
|
@ -222,10 +224,7 @@ 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 of how Python importing works - it stores all imported modules and will
|
||||
avoid importing them more than once. So your `print` will only run the first time, when the module
|
||||
is first imported.
|
||||
You will *not* see any output this or any subsequent times! This is not a bug. Rather it is because of how Python importing works - it stores all imported modules and will avoid importing them more than once. So your `print` will only run the first time, when the module is first imported.
|
||||
|
||||
Try this:
|
||||
|
||||
|
|
@ -240,8 +239,7 @@ Now we see it again. The `reload` wiped the server's memory of what was imported
|
|||
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.
|
||||
> We'll get back to more advanced ways to import code in [a later lesson](./Beginner-Tutorial-Python-classes-and-objects.md#importing-things) - this is an important topic. But for now, let's press on and resolve this particular problem.
|
||||
|
||||
|
||||
### Our first own function
|
||||
|
|
@ -434,8 +432,8 @@ On the game command-line, let's create a mirror:
|
|||
```{sidebar} Creating objects
|
||||
|
||||
The `create` command was first used to create boxes in the
|
||||
`Building Stuff <Building-Quickstart>`_ tutorial. Note how it
|
||||
uses a "python-path" to describe where to load the mirror's code from.
|
||||
[Building Stuff](./Beginner-Tutorial-Building-Quickstart.md) tutorial. You should now recognize
|
||||
that it uses a "python-path" to tell Evennia where to load the mirror's code from.
|
||||
```
|
||||
|
||||
A mirror should appear in your location.
|
||||
|
|
@ -498,7 +496,7 @@ inside Evennia.
|
|||
|
||||
> py
|
||||
Evennia Interactive Python mode
|
||||
Python 3.7.1 (default, Oct 22 2018, 11:21:55)
|
||||
Python 3.11.0 (default, Nov 22 2022, 11:21:55)
|
||||
[GCC 8.2.0] on Linux
|
||||
[py mode - quit() to exit]
|
||||
|
||||
|
|
@ -512,15 +510,13 @@ commands).
|
|||
Hello World
|
||||
[py mode - quit() to exit]
|
||||
|
||||
Note that we didn't need to put `py` in front now. The system will also echo your input (that's the bit after
|
||||
the `>>>`). For brevity in this tutorual we'll turn the echo off. First exit `py` and then start again with the
|
||||
`/noecho` flag.
|
||||
Note that we didn't need to put `py` in front now. The system will also echo your input (that's the bit after the `>>>`). For brevity in this tutorual we'll turn the echo off. First exit `py` and then start again with the `/noecho` flag.
|
||||
|
||||
> quit()
|
||||
Closing the Python console.
|
||||
> py/noecho
|
||||
Evennia Interactive Python mode (no echoing of prompts)
|
||||
Python 3.7.1 (default, Oct 22 2018, 11:21:55)
|
||||
Python 3.11.0 (default, Nov 22 2022, 11:21:56)
|
||||
[GCC 8.2.0] on Linux
|
||||
[py mode - quit() to exit]
|
||||
|
||||
|
|
@ -528,14 +524,14 @@ the `>>>`). For brevity in this tutorual we'll turn the echo off. First exit `py
|
|||
|
||||
- Start with `py`.
|
||||
- Use `py/noecho` if you don't want your input to be echoed for every line.
|
||||
- All your inputs will now be interpreted as Python code.
|
||||
- _All_ your inputs will now be interpreted as Python code.
|
||||
- Exit with `quit()`.
|
||||
```
|
||||
|
||||
We can now enter multi-line Python code:
|
||||
|
||||
> a = "Test"
|
||||
> print(f"This is a {a}."}
|
||||
> print(f"This is a {a}.")
|
||||
This is a Test.
|
||||
|
||||
Let's try to define a function:
|
||||
|
|
@ -554,8 +550,7 @@ Some important things above:
|
|||
for Python to know it's part of the function body.
|
||||
- We expand the `hello_world` function with another argument `txt`. This allows us to send any text, not just
|
||||
"Hello World" over and over.
|
||||
- To tell `py` that no more lines will be added to the function body, we end with an empty input. When
|
||||
the normal prompt on how to exit returns, we know we are done.
|
||||
- To tell `py` that no more lines will be added to the function body, we end with an empty input. When the normal prompt returns, we know we are done.
|
||||
|
||||
Now we have defined a new function. Let's try it out:
|
||||
|
||||
|
|
@ -645,5 +640,4 @@ first function, fixed an error and even searched and talked to a mirror! Being a
|
|||
python inside and outside of the game is an important skill for testing and debugging, but in
|
||||
practice you will be writing most your code in Python modules.
|
||||
|
||||
To that end we also created a first new Python module in the `mygame/` game dir, then imported and used it.
|
||||
Now let's look at the rest of the stuff you've got going on inside that `mygame/` folder ...
|
||||
To that end we also created a first new Python module in the `mygame/` game dir, then imported and used it. Now let's look at the rest of the stuff you've got going on inside that `mygame/` folder ...
|
||||
|
|
@ -5,16 +5,16 @@ We have also taken a look at what our game dir looks and what is where. Now we'l
|
|||
|
||||
## Importing things
|
||||
|
||||
No one writes something as big as an online game in one single huge file. Instead one breaks up the
|
||||
code into separate files (modules). Each module is dedicated to different purposes. Not only does
|
||||
it make things cleaner, organized and easier to understand. It also makes it easier to re-use code -
|
||||
you just import the resources you need and know you only get just what you requested. This makes
|
||||
it much easier to find errors and to know what code is good and which has issues.
|
||||
In a [previous lesson](./Beginner-Tutorial-Python-basic-introduction.md#importing-code-from-other-modules) we already learned how to import resources into our code. Now we'll dive a little deeper.
|
||||
|
||||
No one writes something as big as an online game in one single huge file. Instead one breaks up the code into separate files (modules). Each module is dedicated to different purposes. Not only does it make things cleaner, organized and easier to understand.
|
||||
|
||||
Splitting code also makes it easier to re-use - you just import the resources you need and know you only get just what you requested. This makes it easier to spot errors and to know what code is good and which has issues.
|
||||
|
||||
> Evennia itself uses your code in the same way - you just tell it where a particular type of code is,
|
||||
and it will import and use it (often instead of its defaults).
|
||||
|
||||
We have already successfully imported things, for example:
|
||||
Here's a familiar example:
|
||||
|
||||
> py import world.test ; world.test.hello_world(me)
|
||||
Hello World!
|
||||
|
|
@ -44,10 +44,7 @@ def hello_world(who):
|
|||
- Anything on a line after a `#` is a `comment`, ignored by Python
|
||||
```
|
||||
|
||||
The _python_path_ describes the relation between Python resources, both between and inside
|
||||
Python _modules_ (that is, files ending with .py). A python-path separates each part of the
|
||||
path `.` and always skips the `.py` file endings. Also, Evennia already knows to start looking
|
||||
for python resources inside `mygame/` so this should never be specified. Hence
|
||||
To reiterate, the _python_path_ describes the relation between Python resources, both between and inside Python _modules_ (that is, files ending with .py). Paths use `.` and always skips the `.py` file endings. Also, Evennia already knows to start looking for python resources inside `mygame/` so this should never be included.
|
||||
|
||||
import world.test
|
||||
|
||||
|
|
@ -57,7 +54,7 @@ this module to get to the function you want:
|
|||
world.test.hello_world(me)
|
||||
|
||||
Using `import` like this means that you have to specify the full `world.test` every time you want
|
||||
to get to your function. Here's a more powerful form of import:
|
||||
to get to your function. Here's an alternative:
|
||||
|
||||
from world.test import hello_world
|
||||
|
||||
|
|
@ -127,6 +124,12 @@ module docstring
|
|||
"""
|
||||
from evennia import DefaultObject
|
||||
|
||||
class ObjectParent:
|
||||
"""
|
||||
class docstring
|
||||
"""
|
||||
pass
|
||||
|
||||
class Object(DefaultObject):
|
||||
"""
|
||||
class docstring
|
||||
|
|
@ -136,7 +139,7 @@ class Object(DefaultObject):
|
|||
|
||||
```{sidebar} Docstrings vs Comments
|
||||
|
||||
A docstring is not the same as a comment (created by `#`). A docstring is not ignored by Python but is an integral part of the thing it is documenting (the module and the class in this case).
|
||||
A docstring is not the same as a comment (created by `#`). A docstring is not ignored by Python but is an integral part of the thing it is documenting (the module and the class in this case). For example, we read docstrings to help text for [API documentation](../../../Evennia-API.md); we could not do that with comments.
|
||||
```
|
||||
The real file is much longer but we can ignore the multi-line strings (`""" ... """`). These serve
|
||||
as documentation-strings, or _docstrings_ for the module (at the top) and the `class` below.
|
||||
|
|
@ -145,13 +148,12 @@ Below the module doc string we have the import. In this case we are importing a
|
|||
from the core `evennia` library itself. We will dive into this later, for now we just treat this
|
||||
as a black box.
|
||||
|
||||
Next we have a `class` named `Object`, which _inherits_ from `DefaultObject`. This class doesn't
|
||||
actually do anything on its own, its only code (except the docstring) is `pass` which means,
|
||||
well, to pass and don't do anything.
|
||||
Next we have an empty `class` named `ObjectParent`. It doesn't do anything, its only code (except the docstring) is `pass` which means, well, to pass and don't do anything. Since it also doesn't _inherit_ from anything, it's just an empty container. We will not concern ourselves with it for this tutorial.
|
||||
|
||||
We will get back to this module in the [next lesson](./Beginner-Tutorial-Learning-Typeclasses.md). First we need to do a
|
||||
little detour to understand what a 'class', an 'object' or 'instance' is. These are fundamental
|
||||
things to understand before you can use Evennia efficiently.
|
||||
The `class` named `Object`_ inherits_ from `ObjectParent` and `DefaultObject`. Since we see that `ObjectParent` is empty, what is interesting is `DefaultObject`. Again, the `Object` class doesn't
|
||||
actually do anything on its own right now, but because of it being a child of `DefaultObject`, it's actually providing a lot of functionality! If this is confusing, read on.
|
||||
|
||||
We will get back to this module in the [next lesson](./Beginner-Tutorial-Learning-Typeclasses.md). First we need to do a little detour to understand what a 'class', an 'object' or 'instance' is. These are fundamental things to understand before you can use Evennia efficiently.
|
||||
```{sidebar} OOP
|
||||
|
||||
Classes, objects, instances and inheritance are fundamental to Python. This and some other concepts are often clumped together under the term Object-Oriented-Programming (OOP).
|
||||
|
|
@ -159,9 +161,7 @@ Classes, objects, instances and inheritance are fundamental to Python. This and
|
|||
|
||||
### Classes and instances
|
||||
|
||||
A 'class' can be seen as a 'template' for a 'type' of object. The class describes the basic functionality
|
||||
of everyone of that class. For example, we could have a class `Monster` which has resources for moving itself
|
||||
from room to room.
|
||||
A 'class' can be seen as a 'template' for a 'type' of object. The class describes the basic functionality of everyone of that class. For example, we could have a class `Monster` which has resources for moving itself from room to room.
|
||||
|
||||
Open a new file `mygame/typeclasses/monsters.py`. Add the following simple class:
|
||||
|
||||
|
|
@ -185,11 +185,11 @@ back to the `key` on the class.
|
|||
```{sidebar} Terms
|
||||
|
||||
- A `class` is a code template describing a 'type' of something
|
||||
- An `object` is an `instance` of a `class`. Like using a mold to cast in soldiers, one class can be `instantiated` into any number of object-instances.
|
||||
- An `object` is an `instance` of a `class`. Like using a mold to cast tin soldiers, one class can be `instantiated` into any number of object-instances. Each instance does not need to be identical (much like each tin soldier can be painted differently).
|
||||
|
||||
```
|
||||
A class is just a template. Before it can be used, we must create an _instance_ of the class. If
|
||||
`Monster` is a class, then an instance is Fluffy, the individual red dragon. You instantiate
|
||||
`Monster` is a class, then an instance is `Fluffy`, a specific dragon individual. You instantiate
|
||||
by _calling_ the class, much like you would a function:
|
||||
|
||||
fluffy = Monster()
|
||||
|
|
@ -216,8 +216,7 @@ Let's create the sibling of Fluffy, Cuddly:
|
|||
Monster is moving!
|
||||
|
||||
We now have two dragons and they'll hang around until with call `quit()` to exit this Python
|
||||
instance. We can have them move as many times as we want. But no matter how many dragons we
|
||||
create, they will all show the same printout since `key` is always fixed as "Monster".
|
||||
instance. We can have them move as many times as we want. But no matter how many dragons we create, they will all show the same printout since `key` is always fixed as "Monster".
|
||||
|
||||
Let's make the class a little more flexible:
|
||||
|
||||
|
|
@ -259,13 +258,11 @@ Either way you'll need to go into `py` again:
|
|||
fluffy.move_around()
|
||||
Fluffy is moving!
|
||||
|
||||
Now we passed `"Fluffy"` as an argument to the class. This went into `__init__` and set `self.key`, which we
|
||||
later used to print with the right name! Again, note that we didn't include `self` when calling.
|
||||
Now we passed `"Fluffy"` as an argument to the class. This went into `__init__` and set `self.key`, which we later used to print with the right name!
|
||||
|
||||
### What's so good about objects?
|
||||
|
||||
So far all we've seen a class do is to behave our first `hello_world` function but more complex. We
|
||||
could just have made a function:
|
||||
So far all we've seen a class do is to behave like our first `hello_world` function but being more complex. We could just have made a function:
|
||||
|
||||
```python
|
||||
def monster_move_around(key):
|
||||
|
|
@ -276,13 +273,12 @@ The difference between the function and an instance of a class (the object), is
|
|||
object retains _state_. Once you called the function it forgets everything about what you called
|
||||
it with last time. The object, on the other hand, remembers changes:
|
||||
|
||||
> fluffy.key = "Cuddly"
|
||||
> fluffy.key = "Fluffy, the red dragon"
|
||||
> fluffy.move_around()
|
||||
Cuddly is moving!
|
||||
Fluffy, the red dragon is moving!
|
||||
|
||||
The `fluffy` object's `key` was changed to "Cuddly" for as long as it's around. This makes objects
|
||||
extremely useful for representing and remembering collections of data - some of which can be other
|
||||
objects in turn:
|
||||
The `fluffy` object's `key` was changed for as long as it's around. This makes objects extremely useful for representing and remembering collections of data - some of which can be other
|
||||
objects in turn. Some examples:
|
||||
|
||||
- A player character with all its stats
|
||||
- A monster with HP
|
||||
|
|
@ -295,8 +291,7 @@ objects in turn:
|
|||
|
||||
### Classes can have children
|
||||
|
||||
Classes can _inherit_ from each other. A "child" class will inherit everything from its "parent" class. But if
|
||||
the child adds something with the same name as its parent, it will _override_ whatever it got from its parent.
|
||||
Classes can _inherit_ from each other. A "child" class will inherit everything from its "parent" class. But if the child adds something with the same name as its parent, it will _override_ whatever it got from its parent.
|
||||
|
||||
Let's expand `mygame/typeclasses/monsters.py` with another class:
|
||||
|
||||
|
|
@ -316,7 +311,7 @@ class Monster:
|
|||
|
||||
class Dragon(Monster):
|
||||
"""
|
||||
This is a dragon-specific monster.
|
||||
This is a dragon monster.
|
||||
"""
|
||||
|
||||
def move_around(self):
|
||||
|
|
@ -330,8 +325,7 @@ class Dragon(Monster):
|
|||
|
||||
```
|
||||
|
||||
We added some docstrings for clarity. It's always a good idea to add doc strings; you can do so also for methods,
|
||||
as exemplified for the new `firebreath` method.
|
||||
We added some docstrings for clarity. It's always a good idea to add doc strings; you can do so also for methods, as exemplified for the new `firebreath` method.
|
||||
|
||||
We created the new class `Dragon` but we also specified that `Monster` is the _parent_ of `Dragon` but adding
|
||||
the parent in parenthesis. `class Classname(Parent)` is the way to do this.
|
||||
|
|
@ -342,7 +336,7 @@ It's possible to add more comma-separated parents to a class. You should usually
|
|||
|
||||
```
|
||||
|
||||
Let's try out our new class. First `reload` the server and the do
|
||||
Let's try out our new class. First `reload` the server and then:
|
||||
|
||||
> py
|
||||
> from typeclasses.monsters import Dragon
|
||||
|
|
@ -352,13 +346,9 @@ Let's try out our new class. First `reload` the server and the do
|
|||
> smaug.firebreath()
|
||||
Smaug breathes fire!
|
||||
|
||||
Because we didn't implement `__init__` in `Dragon`, we got the one from `Monster` instead. But since we
|
||||
implemented our own `move_around` in `Dragon`, it _overrides_ the one in `Monster`. And `firebreath` is only
|
||||
available for `Dragon`s of course. Having that on `Monster` would not have made much sense, since not every monster
|
||||
can breathe fire.
|
||||
Because we didn't (re)implement `__init__` in `Dragon`, we got the one from `Monster`. We did implement our own `move_around` in `Dragon`, so it _overrides_ the one in `Monster`. And `firebreath` is only available for `Dragon`s. Having that on `Monster` would not have made much sense, since not every monster can breathe fire.
|
||||
|
||||
One can also force a class to use resources from the parent even if you are overriding some of it. This is done
|
||||
with the `super()` method. Modify your `Dragon` class as follows:
|
||||
One can also force a class to use resources from the parent even if you are overriding some of it. This is done with the `super()` method. Modify your `Dragon` class as follows:
|
||||
|
||||
|
||||
```python
|
||||
|
|
@ -372,13 +362,12 @@ class Dragon(Monster):
|
|||
|
||||
# ...
|
||||
```
|
||||
> Keep `Monster` and the `firebreath` method, `# ...` indicates the rest of the code is untouched.
|
||||
>
|
||||
|
||||
The `super().move_around()` line means that we are calling `move_around()` on the parent of the class. So in this
|
||||
case, we will call `Monster.move_around` first, before doing our own thing.
|
||||
> Keep `Monster` and the `firebreath` method. The `# ...` above indicates the rest of the code is unchanged.
|
||||
|
||||
Now `reload` the server and then:
|
||||
The `super().move_around()` line means that we are calling `move_around()` on the parent of the class. So in this case, we will call `Monster.move_around` first, before doing our own thing.
|
||||
|
||||
To see, `reload` the server and then:
|
||||
|
||||
> py
|
||||
> from typeclasses.monsters import Dragon
|
||||
|
|
@ -387,18 +376,13 @@ Now `reload` the server and then:
|
|||
Smaug is moving!
|
||||
The world trembles.
|
||||
|
||||
We can see that `Monster.move_around()` is calls first and prints "Smaug is moving!", followed by the extra bit
|
||||
about the trembling world we added in the `Dragon` class.
|
||||
We can see that `Monster.move_around()` is called first and prints "Smaug is moving!", followed by the extra bit about the trembling world from the `Dragon` class.
|
||||
|
||||
Inheritance is very powerful because it allows you to organize and re-use code while only adding the special things
|
||||
you want to change. Evennia uses this concept a lot.
|
||||
Inheritance is a powerful concept. It allows you to organize and re-use code while only adding the special things you want to change. Evennia uses this a lot.
|
||||
|
||||
## Summary
|
||||
|
||||
We have created our first dragons from classes. We have learned a little about how you _instantiate_ a class
|
||||
into an _object_. We have seen some examples of _inheritance_ and we tested to _override_ a method in the parent
|
||||
with one in the child class. We also used `super()` to good effect.
|
||||
We have created our first dragons from classes. We have learned a little about how you _instantiate_ a class into an _object_. We have seen some examples of _inheritance_ and we tested to _override_ a method in the parent with one in the child class. We also used `super()` to good effect.
|
||||
|
||||
We have used pretty much raw Python so far. In the coming lessons we'll start to look at the extra bits that Evennia
|
||||
provides. But first we need to learn just where to find everything.
|
||||
We have used pretty much raw Python so far. In the coming lessons we'll start to look at the extra bits that Evennia provides. But first we need to learn just where to find everything.
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ Only LOSERS and QUITTERS use the `give up` command.
|
|||
## Gameplay
|
||||
|
||||

|
||||
(image by Griatch)
|
||||
|
||||
*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
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ evennia.contrib.rpg
|
|||
:maxdepth: 6
|
||||
|
||||
evennia.contrib.rpg.buffs
|
||||
evennia.contrib.rpg.character_creator
|
||||
evennia.contrib.rpg.dice
|
||||
evennia.contrib.rpg.health_bar
|
||||
evennia.contrib.rpg.rpsystem
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue