2022-02-06 19:27:15 +01:00
|
|
|
# Parsing Command input
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
In this lesson we learn some basics about parsing the input of Commands. We will
|
|
|
|
|
also learn how to add, modify and extend Evennia's default commands.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
## More advanced parsing
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
In the last lesson we made a `hit` Command and hit a dragon with it. You should have the code
|
|
|
|
|
from that still around.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
Let's expand our simple `hit` command to accept a little more complex input:
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
hit <target> [[with] <weapon>]
|
2021-08-06 17:16:44 +02:00
|
|
|
|
2020-07-03 22:32:50 +02:00
|
|
|
That is, we want to support all of these forms
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
hit target
|
2020-07-03 22:32:50 +02:00
|
|
|
hit target weapon
|
|
|
|
|
hit target with weapon
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
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
|
2020-07-03 22:32:50 +02:00
|
|
|
a little, in a new method `parse`:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
#...
|
|
|
|
|
|
|
|
|
|
class CmdHit(Command):
|
|
|
|
|
"""
|
|
|
|
|
Hit a target.
|
2021-08-06 17:16:44 +02:00
|
|
|
|
2020-07-03 22:32:50 +02:00
|
|
|
Usage:
|
|
|
|
|
hit <target>
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
key = "hit"
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
def parse(self):
|
2020-07-03 22:32:50 +02:00
|
|
|
self.args = self.args.strip()
|
|
|
|
|
target, *weapon = self.args.split(" with ", 1)
|
|
|
|
|
if not weapon:
|
2021-08-06 17:16:44 +02:00
|
|
|
target, *weapon = target.split(" ", 1)
|
|
|
|
|
self.target = target.strip()
|
2020-07-03 22:32:50 +02:00
|
|
|
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?")
|
2021-08-06 17:16:44 +02:00
|
|
|
return
|
2020-07-03 22:32:50 +02:00
|
|
|
# get the target for the hit
|
2021-08-06 17:16:44 +02:00
|
|
|
target = self.caller.search(self.target)
|
2020-07-03 22:32:50 +02:00
|
|
|
if not target:
|
2021-08-06 17:16:44 +02:00
|
|
|
return
|
|
|
|
|
# get and handle the weapon
|
2020-07-03 22:32:50 +02:00
|
|
|
weapon = None
|
|
|
|
|
if self.weapon:
|
|
|
|
|
weapon = self.caller.search(self.weapon)
|
2021-08-06 17:16:44 +02:00
|
|
|
if weapon:
|
2020-07-03 22:32:50 +02:00
|
|
|
weaponstr = f"{weapon.key}"
|
|
|
|
|
else:
|
|
|
|
|
weaponstr = "bare fists"
|
2021-08-06 17:16:44 +02:00
|
|
|
|
|
|
|
|
self.caller.msg(f"You hit {target.key} with {weaponstr}!")
|
2020-07-03 22:32:50 +02:00
|
|
|
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
|
2021-08-06 17:16:44 +02:00
|
|
|
`parse` not only makes things a little easier to read, it also means you can easily let other Commands _inherit_
|
2020-07-03 22:32:50 +02:00
|
|
|
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.
|
|
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
```{sidebar} Tuples and Lists
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
- 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.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
```
|
2021-08-06 17:16:44 +02:00
|
|
|
- **Line 14** - We do the stripping of `self.args` once and for all here. We also store the stripped version back
|
2020-07-03 22:32:50 +02:00
|
|
|
into `self.args`, overwriting it. So there is no way to get back the non-stripped version from here on, which is fine
|
2021-08-06 17:16:44 +02:00
|
|
|
for this command.
|
2020-07-03 22:32:50 +02:00
|
|
|
- **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"]`
|
2021-08-06 17:16:44 +02:00
|
|
|
|
|
|
|
|
So we get a list of 1 or 2 elements. We assign it to two variables like this, `target, *weapon = `. That
|
2020-07-03 22:32:50 +02:00
|
|
|
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
|
2021-08-06 17:16:44 +02:00
|
|
|
under two conditions (from the example above):
|
2020-07-03 22:32:50 +02:00
|
|
|
1. `target` is simply `smaug`
|
|
|
|
|
2. `target` is `smaug sword`
|
2021-08-06 17:16:44 +02:00
|
|
|
|
|
|
|
|
To separate these cases we split `target` once again, this time by empty space `" "`. Again we store the
|
2020-07-03 22:32:50 +02:00
|
|
|
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
|
2021-08-06 17:16:44 +02:00
|
|
|
before running `strip()` on it. This is because we know that if it's falsy, it's an empty list `[]` and lists
|
2020-07-03 22:32:50 +02:00
|
|
|
don't have the `.strip()` method on them (so if we tried to use it, we'd get an error).
|
2021-08-06 17:16:44 +02:00
|
|
|
|
|
|
|
|
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
|
2020-07-03 22:32:50 +02:00
|
|
|
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!
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
> reload
|
|
|
|
|
> hit smaug with sword
|
2020-07-03 22:32:50 +02:00
|
|
|
Could not find 'sword'.
|
|
|
|
|
You hit smaug with bare fists!
|
2021-08-06 17:16:44 +02:00
|
|
|
|
2020-07-03 22:32:50 +02:00
|
|
|
Oops, our `self.caller.search(self.weapon)` is telling us that it found no sword. Since we are not `return`ing
|
2021-08-06 17:16:44 +02:00
|
|
|
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
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
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
|
2020-07-03 22:32:50 +02:00
|
|
|
change (no code changed, only stuff in the database).
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
> hit smaug with sword
|
|
|
|
|
You hit smaug with sword!
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
## Adding a Command to an object
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
The commands of a cmdset attached to an object with `obj.cmdset.add()` will by default be made available to that object
|
2022-08-30 23:03:39 +02:00
|
|
|
but _also to those in the same location as that object_. If you did the [Building introduction](./Beginner-Tutorial-Building-Quickstart.md)
|
|
|
|
|
you've seen an example of this with the "Red Button" object. The [Tutorial world](./Beginner-Tutorial-Tutorial-World.md)
|
2021-08-06 17:16:44 +02:00
|
|
|
also has many examples of objects with commands on them.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
To show how this could work, let's put our 'hit' Command on our simple `sword` object from the previous section.
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
> self.search("sword").cmdset.add("commands.mycommands.MyCmdSet", persistent=True)
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
We find the sword (it's still in our inventory so `self.search` should be able to find it), then
|
|
|
|
|
add `MyCmdSet` to it. This actually adds both `hit` and `echo` to the sword, which is fine.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
Let's try to swing it!
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
> hit
|
2020-07-03 22:32:50 +02:00
|
|
|
More than one match for 'hit' (please narrow target):
|
|
|
|
|
hit-1 (sword #11)
|
|
|
|
|
hit-2
|
|
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
```{sidebar} Multi-matches
|
2021-08-06 17:16:44 +02:00
|
|
|
|
2020-07-03 22:32:50 +02:00
|
|
|
Some game engines will just pick the first hit when finding more than one.
|
|
|
|
|
Evennia will always give you a choice. The reason for this is that Evennia
|
|
|
|
|
cannot know if `hit` and `hit` are different or the same - maybe it behaves
|
2021-08-06 17:16:44 +02:00
|
|
|
differently depending on the object it sits on? Besides, imagine if you had
|
2020-07-03 22:32:50 +02:00
|
|
|
a red and a blue button both with the command `push` on it. Now you just write
|
|
|
|
|
`push`. Wouldn't you prefer to be asked `which` button you really wanted to push?
|
|
|
|
|
```
|
2021-08-06 17:16:44 +02:00
|
|
|
Woah, that didn't go as planned. Evennia actually found _two_ `hit` commands to didn't know which one to use
|
|
|
|
|
(_we_ know they are the same, but Evennia can't be sure of that). As we can see, `hit-1` is the one found on
|
|
|
|
|
the sword. The other one is from adding `MyCmdSet` to ourself earlier. It's easy enough to tell Evennia which
|
|
|
|
|
one you meant:
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
> hit-1
|
2020-07-03 22:32:50 +02:00
|
|
|
Who do you want to hit?
|
|
|
|
|
> hit-2
|
2021-08-06 17:16:44 +02:00
|
|
|
Who do you want to hit?
|
|
|
|
|
|
|
|
|
|
In this case we don't need both command-sets, so let's just keep the one on the sword:
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
> self.cmdset.remove("commands.mycommands.MyCmdSet")
|
|
|
|
|
> hit
|
|
|
|
|
Who do you want to hit?
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
Now try this:
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
> tunnel n = kitchen
|
2021-08-06 17:16:44 +02:00
|
|
|
> n
|
|
|
|
|
> drop sword
|
2020-07-03 22:32:50 +02:00
|
|
|
> s
|
|
|
|
|
> hit
|
|
|
|
|
Command 'hit' is not available. Maybe you meant ...
|
|
|
|
|
> n
|
2021-08-06 17:16:44 +02:00
|
|
|
> hit
|
|
|
|
|
Who do you want to hit?
|
|
|
|
|
|
|
|
|
|
The `hit` command is now only available if you hold or are in the same room as the sword.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
### You need to hold the sword!
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
Let's get a little ahead of ourselves and make it so you have to _hold_ the sword for the `hit` command to
|
2020-07-03 22:32:50 +02:00
|
|
|
be available. This involves a _Lock_. We've cover locks in more detail later, just know that they are useful
|
|
|
|
|
for limiting the kind of things you can do with an object, including limiting just when you can call commands on
|
2021-08-06 17:16:44 +02:00
|
|
|
it.
|
2021-10-21 21:04:14 +02:00
|
|
|
```{sidebar} Locks
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
Evennia Locks are defined as a mini-language defined in `lockstrings`. The lockstring
|
|
|
|
|
is on a form `<situation>:<lockfuncs>`, where `situation` determines when this
|
|
|
|
|
lock applies and the `lockfuncs` (there can be more than one) are run to determine
|
|
|
|
|
if the lock-check passes or not depending on circumstance.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> py self.search("sword").locks.add("call:holds()")
|
2021-08-06 17:16:44 +02:00
|
|
|
|
|
|
|
|
We added a new lock to the sword. The _lockstring_ `"call:holds()"` means that you can only _call_ commands on
|
|
|
|
|
this object if you are _holding_ the object (that is, it's in your inventory).
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
For locks to work, you cannot be _superuser_, since the superuser passes all locks. You need to `quell` yourself
|
2021-08-06 17:16:44 +02:00
|
|
|
first:
|
2021-10-21 21:04:14 +02:00
|
|
|
```{sidebar} quell/unquell
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
Quelling allows you as a developer to take on the role of players with less
|
2021-08-06 17:16:44 +02:00
|
|
|
priveleges. This is useful for testing and debugging, in particular since a
|
2020-07-03 22:32:50 +02:00
|
|
|
superuser has a little `too` much power sometimes.
|
|
|
|
|
Use `unquell` to get back to your normal self.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> quell
|
2021-08-06 17:16:44 +02:00
|
|
|
|
2020-07-03 22:32:50 +02:00
|
|
|
If the sword lies on the ground, try
|
|
|
|
|
|
|
|
|
|
> hit
|
|
|
|
|
Command 'hit' is not available. ..
|
2021-08-06 17:16:44 +02:00
|
|
|
> get sword
|
|
|
|
|
> hit
|
2020-07-03 22:32:50 +02:00
|
|
|
> Who do you want to hit?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Finally, we get rid of ours sword so we have a clean slate with no more `hit` commands floating around.
|
|
|
|
|
We can do that in two ways:
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
delete sword
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
or
|
|
|
|
|
|
|
|
|
|
py self.search("sword").delete()
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
## Adding the Command to a default Cmdset
|
|
|
|
|
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
As we have seen we can use `obj.cmdset.add()` to add a new cmdset to objects, whether that object
|
|
|
|
|
is ourself (`self`) or other objects like the `sword`.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
This is how all commands in Evennia work, including default commands like `look`, `dig`, `inventory` and so on.
|
|
|
|
|
All these commands are in just loaded on the default objects that Evennia provides out of the box.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
- Characters (that is 'you' in the gameworld) has the `CharacterCmdSet`.
|
|
|
|
|
- Accounts (the thing that represents your out-of-character existence on the server) has the `AccountCmdSet`
|
|
|
|
|
- Sessions (representing one single client connection) has the `SessionCmdSet`
|
|
|
|
|
- Before you log in (at the connection screen) you'll have access to the `UnloggedinCmdSet`.
|
|
|
|
|
|
|
|
|
|
The thing must commonly modified is the `CharacterCmdSet`.
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
The default cmdset are defined in `mygame/commands/default_cmdsets.py`. Open that file now:
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
"""
|
|
|
|
|
(module docstring)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from evennia import default_cmds
|
|
|
|
|
|
|
|
|
|
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|
|
|
|
|
|
|
|
|
key = "DefaultCharacter"
|
|
|
|
|
|
|
|
|
|
def at_cmdset_creation(self):
|
|
|
|
|
|
|
|
|
|
super().at_cmdset_creation()
|
|
|
|
|
#
|
|
|
|
|
# any commands you add below will overload the default ones
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
class AccountCmdSet(default_cmds.AccountCmdSet):
|
|
|
|
|
|
|
|
|
|
key = "DefaultAccount"
|
|
|
|
|
|
|
|
|
|
def at_cmdset_creation(self):
|
|
|
|
|
|
|
|
|
|
super().at_cmdset_creation()
|
|
|
|
|
#
|
|
|
|
|
# any commands you add below will overload the default ones
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
|
|
|
|
|
|
|
|
|
|
key = "DefaultUnloggedin"
|
|
|
|
|
|
|
|
|
|
def at_cmdset_creation(self):
|
|
|
|
|
|
|
|
|
|
super().at_cmdset_creation()
|
|
|
|
|
#
|
|
|
|
|
# any commands you add below will overload the default ones
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
class SessionCmdSet(default_cmds.SessionCmdSet):
|
|
|
|
|
|
|
|
|
|
key = "DefaultSession"
|
|
|
|
|
|
|
|
|
|
def at_cmdset_creation(self):
|
|
|
|
|
|
|
|
|
|
super().at_cmdset_creation()
|
|
|
|
|
#
|
|
|
|
|
# any commands you add below will overload the default ones
|
|
|
|
|
#
|
|
|
|
|
```
|
|
|
|
|
|
2021-10-21 21:04:14 +02:00
|
|
|
```{sidebar} super()
|
2021-08-06 17:16:44 +02:00
|
|
|
|
2020-07-03 22:32:50 +02:00
|
|
|
The `super()` function refers to the parent of the current class and is commonly
|
2021-08-06 17:16:44 +02:00
|
|
|
used to call same-named methods on the parent.
|
2020-07-03 22:32:50 +02:00
|
|
|
```
|
2021-08-06 17:16:44 +02:00
|
|
|
`evennia.default_cmds` is a container that holds all of Evennia's default commands and cmdsets. In this module
|
2020-07-03 22:32:50 +02:00
|
|
|
we can see that this was imported and then a new child class was made for each cmdset. Each class looks familiar
|
|
|
|
|
(except the `key`, that's mainly used to easily identify the cmdset in listings). In each `at_cmdset_creation` all
|
|
|
|
|
we do is call `super().at_cmdset_creation` which means that we call `at_cmdset_creation() on the _parent_ CmdSet.
|
2021-08-06 17:16:44 +02:00
|
|
|
This is what adds all the default commands to each CmdSet.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
To add even more Commands to a default cmdset, we can just add them below the `super()` line. Usefully, if we were to
|
2021-08-06 17:16:44 +02:00
|
|
|
add a Command with the same `.key` as a default command, it would completely replace that original. So if you were
|
|
|
|
|
to add a command with a key `look`, the original `look` command would be replaced by your own version.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
For now, let's add our own `hit` and `echo` commands to the `CharacterCmdSet`:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# ...
|
|
|
|
|
|
|
|
|
|
from commands import mycommands
|
|
|
|
|
|
|
|
|
|
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|
|
|
|
|
|
|
|
|
key = "DefaultCharacter"
|
|
|
|
|
|
|
|
|
|
def at_cmdset_creation(self):
|
|
|
|
|
|
|
|
|
|
super().at_cmdset_creation()
|
|
|
|
|
#
|
|
|
|
|
# any commands you add below will overload the default ones
|
|
|
|
|
#
|
|
|
|
|
self.add(mycommands.CmdEcho)
|
|
|
|
|
self.add(mycommands.CmdHit)
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
> reload
|
2020-07-03 22:32:50 +02:00
|
|
|
> hit
|
2021-08-06 17:16:44 +02:00
|
|
|
Who do you want to hit?
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
Your new commands are now available for all player characters in the game. There is another way to add a bunch
|
|
|
|
|
of commands at once, and that is to add a _CmdSet_ to the other cmdset. All commands in that cmdset will then be added:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from commands import mycommands
|
|
|
|
|
|
|
|
|
|
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|
|
|
|
|
|
|
|
|
key = "DefaultCharacter"
|
|
|
|
|
|
|
|
|
|
def at_cmdset_creation(self):
|
|
|
|
|
|
|
|
|
|
super().at_cmdset_creation()
|
|
|
|
|
#
|
|
|
|
|
# any commands you add below will overload the default ones
|
|
|
|
|
#
|
|
|
|
|
self.add(mycommands.MyCmdSet)
|
|
|
|
|
```
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
Which way you use depends on how much control you want, but if you already have a CmdSet,
|
2020-07-03 22:32:50 +02:00
|
|
|
this is practical. A Command can be a part of any number of different CmdSets.
|
|
|
|
|
|
|
|
|
|
### Removing Commands
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
To remove your custom commands again, you of course just delete the change you did to
|
|
|
|
|
`mygame/commands/default_cmdsets.py`. But what if you want to remove a default command?
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
We already know that we use `cmdset.remove()` to remove a cmdset. It turns out you can
|
|
|
|
|
do the same in `at_cmdset_creation`. For example, let's remove the default `get` Command
|
2020-07-03 22:32:50 +02:00
|
|
|
from Evennia. We happen to know this can be found as `default_cmds.CmdGet`.
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
|
2020-07-03 22:32:50 +02:00
|
|
|
```python
|
|
|
|
|
# ...
|
|
|
|
|
from commands import mycommands
|
|
|
|
|
|
|
|
|
|
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|
|
|
|
|
|
|
|
|
key = "DefaultCharacter"
|
|
|
|
|
|
|
|
|
|
def at_cmdset_creation(self):
|
|
|
|
|
|
|
|
|
|
super().at_cmdset_creation()
|
|
|
|
|
#
|
|
|
|
|
# any commands you add below will overload the default ones
|
|
|
|
|
#
|
|
|
|
|
self.add(mycommands.MyCmdSet)
|
|
|
|
|
self.remove(default_cmds.CmdGet)
|
|
|
|
|
# ...
|
|
|
|
|
```
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
> reload
|
2020-07-03 22:32:50 +02:00
|
|
|
> get
|
|
|
|
|
Command 'get' is not available ...
|
|
|
|
|
|
|
|
|
|
## Replace a default command
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
At this point you already have all the pieces for how to do this! We just need to add a new
|
|
|
|
|
command with the same `key` in the `CharacterCmdSet` to replace the default one.
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
Let's combine this with what we know about classes and
|
2020-07-03 22:32:50 +02:00
|
|
|
how to _override_ a parent class. Open `mygame/commands/mycommands.py` and lets override
|
|
|
|
|
that `CmdGet` command.
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# up top, by the other imports
|
|
|
|
|
from evennia import default_cmds
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
# somewhere below
|
2020-07-03 22:32:50 +02:00
|
|
|
class MyCmdGet(default_cmds.CmdGet):
|
|
|
|
|
|
|
|
|
|
def func(self):
|
|
|
|
|
super().func()
|
|
|
|
|
self.caller.msg(str(self.caller.location.contents))
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- **Line2**: We import `default_cmds` so we can get the parent class.
|
2021-08-06 17:16:44 +02:00
|
|
|
We made a new class and we make it _inherit_ `default_cmds.CmdGet`. We don't
|
|
|
|
|
need to set `.key` or `.parse`, that's already handled by the parent.
|
|
|
|
|
In `func` we call `super().func()` to let the parent do its normal thing,
|
2020-07-03 22:32:50 +02:00
|
|
|
- **Line 7**: By adding our own `func` we replace the one in the parent.
|
2021-08-06 17:16:44 +02:00
|
|
|
- **Line 8**: For this simple change we still want the command to work the
|
2020-07-03 22:32:50 +02:00
|
|
|
same as before, so we use `super()` to call `func` on the parent.
|
2021-08-06 17:16:44 +02:00
|
|
|
- **Line 9**: `.location` is the place an object is at. `.contents` contains, well, the
|
|
|
|
|
contents of an object. If you tried `py self.contents` you'd get a list that equals
|
|
|
|
|
your inventory. For a room, the contents is everything in it.
|
2020-07-03 22:32:50 +02:00
|
|
|
So `self.caller.location.contents` gets the contents of our current location. This is
|
|
|
|
|
a _list_. In order send this to us with `.msg` we turn the list into a string. Python
|
|
|
|
|
has a special function `str()` to do this.
|
2021-08-06 17:16:44 +02:00
|
|
|
|
|
|
|
|
We now just have to add this so it replaces the default `get` command. Open
|
2020-07-03 22:32:50 +02:00
|
|
|
`mygame/commands/default_cmdsets.py` again:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# ...
|
|
|
|
|
from commands import mycommands
|
|
|
|
|
|
|
|
|
|
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|
|
|
|
|
|
|
|
|
key = "DefaultCharacter"
|
|
|
|
|
|
|
|
|
|
def at_cmdset_creation(self):
|
|
|
|
|
|
|
|
|
|
super().at_cmdset_creation()
|
|
|
|
|
#
|
|
|
|
|
# any commands you add below will overload the default ones
|
|
|
|
|
#
|
|
|
|
|
self.add(mycommands.MyCmdSet)
|
|
|
|
|
self.add(mycommands.MyCmdGet)
|
|
|
|
|
# ...
|
|
|
|
|
```
|
2021-10-21 21:04:14 +02:00
|
|
|
```{sidebar} Another way
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
Instead of adding `MyCmdGet` explicitly in default_cmdset.py,
|
|
|
|
|
you could also add it to `mycommands.MyCmdSet` and let it be
|
2020-07-03 22:32:50 +02:00
|
|
|
added automatically for you.
|
|
|
|
|
```
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
> reload
|
|
|
|
|
> get
|
2020-07-03 22:32:50 +02:00
|
|
|
Get What?
|
2021-08-06 17:16:44 +02:00
|
|
|
[smaug, fluffy, YourName, ...]
|
2020-07-03 22:32:50 +02:00
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
We just made a new `get`-command that tells us everything we could pick up (well, we can't pick up ourselves, so
|
|
|
|
|
there's some room for improvement there).
|
2020-07-03 22:32:50 +02:00
|
|
|
|
|
|
|
|
## Summary
|
|
|
|
|
|
2021-08-06 17:16:44 +02:00
|
|
|
In this lesson we got into some more advanced string formatting - many of those tricks will help you a lot in
|
|
|
|
|
the future! We also made a functional sword. Finally we got into how to add to, extend and replace a default
|
2020-07-03 22:32:50 +02:00
|
|
|
command on ourselves.
|
|
|
|
|
|