Continued with adding commands

This commit is contained in:
Griatch 2020-07-03 01:00:13 +02:00
parent 131a9f89aa
commit 830f793aa4
5 changed files with 843 additions and 627 deletions

View file

@ -1,171 +0,0 @@
# Adding Command Tutorial
This is a quick first-time tutorial expanding on the [Commands](../../Component/Commands) documentation.
Let's assume you have just downloaded Evennia, installed it and created your game folder (let's call
it just `mygame` here). Now you want to try to add a new command. This is the fastest way to do it.
## Step 1: Creating a custom command
1. Open `mygame/commands/command.py` in a text editor. This is just one place commands could be
placed but you get it setup from the onset as an easy place to start. It also already contains some
example code.
1. Create a new class in `command.py` inheriting from `default_cmds.MuxCommand`. Let's call it
`CmdEcho` in this example.
1. Set the class variable `key` to a good command name, like `echo`.
1. Give your class a useful _docstring_. A docstring is the string at the very top of a class or
function/method. The docstring at the top of the command class is read by Evennia to become the help
entry for the Command (see
[Command Auto-help](../../Component/Help-System#command-auto-help-system)).
1. Define a class method `func(self)` that echoes your input back to you.
Below is an example how this all could look for the echo command:
```python
# file mygame/commands/command.py
#[...]
from evennia import default_cmds
class CmdEcho(default_cmds.MuxCommand):
"""
Simple command example
Usage:
echo [text]
This command simply echoes text back to the caller.
"""
key = "echo"
def func(self):
"This actually does things"
if not self.args:
self.caller.msg("You didn't enter anything!")
else:
self.caller.msg("You gave the string: '%s'" % self.args)
```
## Step 2: Adding the Command to a default Cmdset
The command is not available to use until it is part of a [Command Set](../../Component/Command-Sets). In this
example we will go the easiest route and add it to the default Character commandset that already
exists.
1. Edit `mygame/commands/default_cmdsets.py`
1. Import your new command with `from commands.command import CmdEcho`.
1. Add a line `self.add(CmdEcho())` to `CharacterCmdSet`, in the `at_cmdset_creation` method (the
template tells you where).
This is approximately how it should look at this point:
```python
# file mygame/commands/default_cmdsets.py
#[...]
from commands.command import CmdEcho
#[...]
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
# this first adds all default commands
super(DefaultSet, self).at_cmdset_creation()
# all commands added after this point will extend or
# overwrite the default commands.
self.add(CmdEcho())
```
Next, run the `@reload` command. You should now be able to use your new `echo` command from inside
the game. Use `help echo` to see the documentation for the command.
If you have trouble, make sure to check the log for error messages (probably due to syntax errors in
your command definition).
> Note: Typing `echotest` will also work. It will be handled as the command `echo` directly followed
by
its argument `test` (which will end up in `self.args). To change this behavior, you can add the
`arg_regex` property alongside `key`, `help_category` etc. [See the arg_regex
documentation](Commands#on-arg_regex) for more info.
If you want to overload existing default commands (such as `look` or `get`), just add your new
command with the same key as the old one - it will then replace it. Just remember that you must use
`@reload` to see any changes.
See [Commands](../../Component/Commands) for many more details and possibilities when defining Commands and using
Cmdsets in various ways.
## Adding the command to specific object types
Adding your Command to the `CharacterCmdSet` is just one easy exapmple. The cmdset system is very
generic. You can create your own cmdsets (let's say in a module `mycmdsets.py`) and add them to
objects as you please (how to control their merging is described in detail in the [Command Set
documentation](Command-Sets)).
```python
# file mygame/commands/mycmdsets.py
#[...]
from commands.command import CmdEcho
from evennia import CmdSet
#[...]
class MyCmdSet(CmdSet):
key = "MyCmdSet"
def at_cmdset_creation(self):
self.add(CmdEcho())
```
Now you just need to add this to an object. To test things (as superuser) you can do
@py self.cmdset.add("mycmdsets.MyCmdSet")
This will add this cmdset (along with its echo command) to yourself so you can test it. Note that
you cannot add a single Command to an object on its own, it must be part of a CommandSet in order to
do so.
The Command you added is not there permanently at this point. If you do a `@reload` the merger will
be gone. You *could* add the `permanent=True` keyword to the `cmdset.add` call. This will however
only make the new merged cmdset permanent on that *single* object. Often you want *all* objects of
this particular class to have this cmdset.
To make sure all new created objects get your new merged set, put the `cmdset.add` call in your
custom [Typeclasses](../../Component/Typeclasses)' `at_object_creation` method:
```python
# e.g. in mygame/typeclasses/objects.py
from evennia import DefaultObject
class MyObject(DefaultObject):
def at_object_creation(self):
"called when the object is first created"
self.cmdset.add("mycmdset.MyCmdSet", permanent=True)
```
All new objects of this typeclass will now start with this cmdset and it will survive a `@reload`.
*Note:* An important caveat with this is that `at_object_creation` is only called *once*, when the
object is first created. This means that if you already have existing objects in your databases
using that typeclass, they will not have been initiated the same way. There are many ways to update
them; since it's a one-time update you can usually just simply loop through them. As superuser, try
the following:
@py from typeclasses.objects import MyObject; [o.cmdset.add("mycmdset.MyCmdSet") for o in
MyObject.objects.all()]
This goes through all objects in your database having the right typeclass, adding the new cmdset to
each. The good news is that you only have to do this if you want to post-add *cmdsets*. If you just
want to add a new *command*, you can simply add that command to the cmdset's `at_cmdset_creation`
and `@reload` to make the Command immediately available.
## Change where Evennia looks for command sets
Evennia uses settings variables to know where to look for its default command sets. These are
normally not changed unless you want to re-organize your game folder in some way. For example, the
default character cmdset defaults to being defined as
CMDSET_CHARACTER="commands.default_cmdset.CharacterCmdSet"
See `evennia/settings_default.py` for the other settings.

View file

@ -0,0 +1,643 @@
# Adding Command Tutorial
[prev lesson](Python-classes-and-objects) | [next lesson]()
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
what is in it.
In Evennia, a Command is a Python _class_. If you are unsure about what a class is, review the
previous lesson. 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.
Command-Sets are then associated with objects. 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
- Putting a CmdSet on an object makes all commands in it available to the object
## Creating a custom command
Open `mygame/commands/command.py`:
```python
"""
(module docstring)
"""
from evennia import Command as BaseCommand
# from evennia import default_cmds
class Command(BaseCommand):
"""
(class docstring)
"""
pass
# (lots of commented-out stuff)
# ...
```
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.
> 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:
```python
from commands.command import Command
class CmdEcho(Command):
key = "echo"
```
This is the simplest form of command you can imagine. It just gives itself a name, "echo". This is
what you will use to call this command later.
Next we need to put this in a CmdSet. It will be a one-command CmdSet for now! Change your file as such:
```python
from commands.command import Command
from evennia import CmdSet
class CmdEcho(Command):
key = "echo"
class MyCmdSet(CmdSet):
def at_cmdset_creation(self):
self.add(CmdEcho)
```
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")
Now try
> echo
Command echo has no defined `func()` - showing on-command variables:
...
...
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
lockhandler (<class 'evennia.locks.lockhandler.LockHandler'>): cmd:all()
caller (<class 'typeclasses.characters.Character'>): YourName
cmdname (<class 'str'>): echo
raw_cmdname (<class 'str'>): echo
cmdstring (<class 'str'>): echo
args (<class 'str'>):
cmdset (<class 'evennia.commands.cmdset.CmdSet'>): @mail, about, access, accounts, addcom, alias, allcom, ban, batchcode, batchcommands, boot, cboot, ccreate,
cdesc, cdestroy, cemit, channels, charcreate, chardelete, checklockstring, clientwidth, clock, cmdbare, cmdsets, color, copy, cpattr, create, cwho, delcom,
desc, destroy, dig, dolphin, drop, echo, emit, examine, find, force, get, give, grapevine2chan, help, home, ic, inventory, irc2chan, ircstatus, link, lock,
look, menutest, mudinfo, mvattr, name, nick, objects, ooc, open, option, page, password, perm, pose, public, py, quell, quit, reload, reset, rss2chan, say,
script, scripts, server, service, sessions, set, setdesc, sethelp, sethome, shutdown, spawn, style, tag, tel, test2010, test2028, testrename, testtable,
tickers, time, tunnel, typeclass, unban, unlink, up, up, userpassword, wall, whisper, who, wipe
session (<class 'evennia.server.serversession.ServerSession'>): Griatch(#1)@1:2:7:.:0:.:0:.:1
account (<class 'typeclasses.accounts.Account'>): Griatch(account 1)
raw_string (<class 'str'>): echo
--------------------------------------------------
echo - Command variables from evennia:
--------------------------------------------------
name of cmd (self.key): echo
cmd aliases (self.aliases): []
cmd locks (self.locks): cmd:all();
help category (self.help_category): General
object calling (self.caller): Griatch
object storing cmdset (self.obj): Griatch
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:
- `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"`.
- `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:
```python
# ...
class CmdEcho(Command):
"""
A simple echo command
Usage:
echo <something>
"""
key = "echo"
def func(self):
self.caller.msg(f"Echo: '{self.args}'")
# ...
```
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](Python-basic-introduction), 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")
> echo
Echo: ''
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:
> 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:
```python
# ...
class CmdEcho(Command):
"""
A simple echo command
Usage:
echo <something>
"""
key = "echo"
def func(self):
self.caller.msg(f"Echo: '{self.args.strip()}'")
# ...
```
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")
> echo Woo Tang!
Echo: 'Woo Tang!'
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.
### Making our cmdset persistent
It's getting a little annoying to have to re-add our cmdset every time we reload, right? It's simple
enough to make `echo` a _permanent_ change though:
> py self.cmdset.add("commands.mycommands.MyCmdSet", permanent=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
> py self.cmdset.remove("commands.mycommands.MyCmdSet")
But for now, keep it around, we'll expand it with some more examples.
### Figuring out who to hit
Let's try something a little more exciting than just echo. Let's make a `hit` command, for punching
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
You got hit by <hitter> with full force!
Here, `<hitter>` would be the one using the `hit` command and `<target>` is the one doing the punching.
Still in `mygame/commands/mycommands.py`, add a new class, between `CmdEcho` and `MyCmdSet`.
```python
# ...
class CmdHit(Command):
"""
Hit a target.
Usage:
hit <target>
"""
key = "hit"
def func(self):
args = self.args.strip()
if not args:
self.caller.msg("Who do you want to hit?")
return
target = self.caller.search(args)
if not target:
return
self.caller.msg(f"You hit {target.key} with full force!")
target.msg(f"You got hit by {self.caller.key} with full force!")
# ...
```
A lot of things to dissect here:
- **Line 4**: The normal `class` header. We inherit from `Command` which we imported at the top of this file.
- **Lines 5**-11: The docstring and help-entry for the command. You could expand on this as much as you wanted.
- **Line 12**: We want to write `hit` to use this command.
- **Line 15**: We strip the whitespace from the argument like before. Since we don't want to have to do
`self.args.strip()` over and over, we store the stripped version
in a _local variable_ `args`. Note that we don't modify `self.args` by doing this, `self.args` will still
have the whitespace and is not the same as `args` in this example.
```sidebar:: if-statements
The full form of the if statement is
if condition:
...
elif othercondition:
...
else:
...
There can be any number of `elifs` to mark when different branches of the code should run. If
the `else` condition is given, it will run if none of the other conditions was truthy. In Python
the `if..elif..else` structure also serves the same function as `case` in some other languages.
```
- **Line 16** 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`
- The empty string `""` or `''` or `""""""` or `''''''`
- 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:
- **Lines 17-18**: This code will only run if the `if` statement is truthy, in this case if `args` is the empty string.
- **Line 18**: `return` is a reserved Python word that exits `func` immediately.
- **Line 19**: We use `self.caller.search` to look for the target in the current location.
- **Lines 20-21**: 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 22-23**: 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 permanent earlier.
```python
# ...
class MyCmdSet(CmdSet):
def at_cmdset_creation(self):
self.add(CmdEcho)
self.add(CmdHit)
```
```sidebar:: Errors in your code
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.
```
Next we reload to let Evennia know of these code changes and try it out:
> reload
hit
Who do you want to hit?
hit me
You hit YourName with full force!
You got hit by YourName with full force!
Lacking a target, we hit ourselves. If you have one of the dragons still around from the previous lesson
you could try to hit it (if you dare):
hit smaug
You hit Smaug with full force!
You won't see the second string. Only Smaug sees that (and is not amused).
## More advanced parsing
Let's expand our simple `hit` command to accept a little more complex input:
hit <target> [[with] <weapon>]
That is, we want to support all of these forms
hit target
hit target weapon
hit target with weapon
If you don't specify a weapon you'll use your fists. It's also nice to be able to skip "with" if
you are in a hurry. Time to modify `mygame/commands/mycommands.py` again. Let us break out the parsing
a little, in a new method `parse`:
```python
#...
class CmdHit(Command):
"""
Hit a target.
Usage:
hit <target>
"""
key = "hit"
def parse(self):
self.args = self.args.strip()
target, *weapon = self.args.split(" with ", 1)
if not weapon:
target, *weapon = target.split(" ", 1)
self.target = target.strip()
if weapon:
self.weapon = weapon.strip()
else:
self.weapon = ""
def func(self):
if not self.args:
self.caller.msg("Who do you want to hit?")
return
# get the target for the hit
target = self.caller.search(self.target)
if not target:
return
# get and handle the weapon
weapon = None
if self.weapon:
weapon = self.caller.search(self.weapon)
if weapon:
weaponstr = f"{weapon.key}"
else:
weaponstr = "bare fists"
self.caller.msg(f"You hit {target.key} with {weaponstr}!")
target.msg(f"You got hit by {self.caller.key} with {weaponstr}!")
# ...
```
The `parse` method is called before `func` and has access to all the same on-command variables as in `func`. Using
`parse` not only makes things a little easier to read, it also means you can easily let other Commands _inherit_
your parsing - if you wanted some other Command to also understand input on the form `<arg> with <arg>` you'd inherit
from this class and just implement the `func` needed for that command without implementing `parse` anew.
```sidebar:: Tuples and Lists
- A `list` is written as `[a, b, c, d, ...]`. You can add and grow/shrink a list after it was first created.
- A `tuple` is written as `(a, b, c, d, ...)`. A tuple cannot be modified once it is created.
```
- **Line 14** - We do the stripping of `self.args` once and for all here. We also store the stripped version back
into `self.args`, overwriting it. So there is no way to get back the non-stripped version from here on, which is fine
for this command.
- **Line 15** - This makes use of the `.split` method of strings. `.split` will, well, split the string by some criterion.
`.split(" with ", 1)` means "split the string once, around the substring `" with "` if it exists". The result
of this split is a _list_. Just how that list looks depends on the string we are trying to split:
1. If we entered just `hit smaug`, we'd be splitting just `"smaug"` which would give the result `["smaug"]`.
2. `hit smaug sword` gives `["smaug sword"]`
3. `hit smaug with sword` gives `["smaug", "sword"]`
So we get a list of 1 or 2 elements. We assign it to two variables like this, `target, *weapon = `. That
asterisk in `*weapon` is a nifty trick - it will automatically become a list of _0 or more_ values. It sorts of
"soaks" up everything left over.
1. `target` becomes `"smaug"` and `weapon` becomes `[]`
2. `target` becomes `"smaug sword"` and `weapon` becomes `[]`
3. `target` becomes `"smaug"` and `weapon` becomes `sword`
- **Lines 16-17** - In this `if` condition we check if `weapon` is falsy (that is, the empty list). This can happen
under two conditions (from the example above):
1. `target` is simply `smaug`
2. `target` is `smaug sword`
To separate these cases we split `target` once again, this time by empty space `" "`. Again we store the
result back with `target, *weapon =`. The result will be one of the following:
1. `target` remains `smaug` and `weapon` remains `[]`
2. `target` becomes `smaug` and `weapon` becomes `sword`
- **Lines 18-22** - We now store `target` and `weapon` into `self.target` and `self.weapon`. We must do this in order
for these local variables to made available in `func` later. Note how we need to check so `weapon` is not falsy
before running `strip()` on it. This is because we know that if it's falsy, it's an empty list `[]` and lists
don't have the `.strip()` method on them (so if we tried to use it, we'd get an error).
Now onto the `func` method. The main difference is we now have `self.target` and `self.weapon` available for
convenient use.
- **Lines 29 and 35** - We make use of the previously parsed search terms for the target and weapon to find the
respective resource.
- **Lines 34-39** - Since the weapon is optional, we need to supply a default (use our fists!) if it's not set. We
use this to create a `weaponstr` that is different depending on if we have a weapon or not.
- **Lines 41-42** - We merge the `weaponstr` with our attack text.
Let's try it out!
> reload
> hit smaug with sword
Could not find 'sword'.
You hit smaug with bare fists!
Oops, our `self.caller.search(self.weapon)` is telling us that it found no sword. Since we are not `return`ing
in this situation (like we do if failing to find `target`) we still continue fighting with our bare hands.
This won't do. Let's make ourselves a sword.
> create sword
Since we didn't specify `/drop`, the sword will end up in our inventory and can seen with the `i` or
`inventory` command. The `.search` helper will still find it there. There is no need to reload to see this
change (no code changed, only stuff in the database).
> hit smaug with sword
You hit smaug with sword!
## Adding the Command to a default Cmdset
For now, let's drop MyCmdSet:
> py self.cmdset.remove("commands.mycommands.MyCmdSet")
The command is not available to use until it is part of a [Command Set](../../../Component/Command-Sets). In this
example we will go the easiest route and add it to the default Character commandset that already
exists.
1. Edit `mygame/commands/default_cmdsets.py`
1. Import your new command with `from commands.command import CmdEcho`.
1. Add a line `self.add(CmdEcho())` to `CharacterCmdSet`, in the `at_cmdset_creation` method (the
template tells you where).
This is approximately how it should look at this point:
```python
# file mygame/commands/default_cmdsets.py
#[...]
from commands.command import CmdEcho
#[...]
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
# this first adds all default commands
super(DefaultSet, self).at_cmdset_creation()
# all commands added after this point will extend or
# overwrite the default commands.
self.add(CmdEcho())
```
Next, run the `@reload` command. You should now be able to use your new `echo` command from inside
the game. Use `help echo` to see the documentation for the command.
If you have trouble, make sure to check the log for error messages (probably due to syntax errors in
your command definition).
> Note: Typing `echotest` will also work. It will be handled as the command `echo` directly followed
by
its argument `test` (which will end up in `self.args). To change this behavior, you can add the
`arg_regex` property alongside `key`, `help_category` etc. [See the arg_regex
documentation](Commands#on-arg_regex) for more info.
If you want to overload existing default commands (such as `look` or `get`), just add your new
command with the same key as the old one - it will then replace it. Just remember that you must use
`@reload` to see any changes.
See [Commands](../../../Component/Commands) for many more details and possibilities when defining Commands and using
Cmdsets in various ways.
## Adding the command to specific object types
Adding your Command to the `CharacterCmdSet` is just one easy exapmple. The cmdset system is very
generic. You can create your own cmdsets (let's say in a module `mycmdsets.py`) and add them to
objects as you please (how to control their merging is described in detail in the [Command Set
documentation](Command-Sets)).
```python
# file mygame/commands/mycmdsets.py
#[...]
from commands.command import CmdEcho
from evennia import CmdSet
#[...]
class MyCmdSet(CmdSet):
key = "MyCmdSet"
def at_cmdset_creation(self):
self.add(CmdEcho())
```
Now you just need to add this to an object. To test things (as superuser) you can do
@py self.cmdset.add("mycmdsets.MyCmdSet")
This will add this cmdset (along with its echo command) to yourself so you can test it. Note that
you cannot add a single Command to an object on its own, it must be part of a CommandSet in order to
do so.
The Command you added is not there permanently at this point. If you do a `@reload` the merger will
be gone. You *could* add the `permanent=True` keyword to the `cmdset.add` call. This will however
only make the new merged cmdset permanent on that *single* object. Often you want *all* objects of
this particular class to have this cmdset.
To make sure all new created objects get your new merged set, put the `cmdset.add` call in your
custom [Typeclasses](../../../Component/Typeclasses)' `at_object_creation` method:
```python
# e.g. in mygame/typeclasses/objects.py
from evennia import DefaultObject
class MyObject(DefaultObject):
def at_object_creation(self):
"called when the object is first created"
self.cmdset.add("mycmdset.MyCmdSet", permanent=True)
```
All new objects of this typeclass will now start with this cmdset and it will survive a `@reload`.
*Note:* An important caveat with this is that `at_object_creation` is only called *once*, when the
object is first created. This means that if you already have existing objects in your databases
using that typeclass, they will not have been initiated the same way. There are many ways to update
them; since it's a one-time update you can usually just simply loop through them. As superuser, try
the following:
@py from typeclasses.objects import MyObject; [o.cmdset.add("mycmdset.MyCmdSet") for o in
MyObject.objects.all()]
This goes through all objects in your database having the right typeclass, adding the new cmdset to
each. The good news is that you only have to do this if you want to post-add *cmdsets*. If you just
want to add a new *command*, you can simply add that command to the cmdset's `at_cmdset_creation`
and `@reload` to make the Command immediately available.
## Change where Evennia looks for command sets
Evennia uses settings variables to know where to look for its default command sets. These are
normally not changed unless you want to re-organize your game folder in some way. For example, the
default character cmdset defaults to being defined as
CMDSET_CHARACTER="commands.default_cmdset.CharacterCmdSet"
[prev lesson](Python-classes-and-objects) | [next lesson]()

View file

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