mirror of
https://github.com/evennia/evennia.git
synced 2026-03-28 02:36:32 +01:00
Cleanup tutorial docs
This commit is contained in:
parent
d1ddb80b22
commit
95d063e4e0
9 changed files with 429 additions and 276 deletions
|
|
@ -5,7 +5,7 @@ also learn how to add, modify and extend Evennia's default commands.
|
|||
|
||||
## More advanced parsing
|
||||
|
||||
In the last lesson we made a `hit` Command and hit a dragon with it. You should have the code
|
||||
In the [last lesson](./Beginner-Tutorial-Adding-Commands.md) we made a `hit` Command and struck a dragon with it. You should have the code
|
||||
from that still around.
|
||||
|
||||
Let's expand our simple `hit` command to accept a little more complex input:
|
||||
|
|
@ -19,11 +19,12 @@ That is, we want to support all of these forms
|
|||
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`:
|
||||
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
|
||||
```{code-block} python
|
||||
:linenos:
|
||||
:emphasize-lines: 14,15,16,18,29,35,41
|
||||
#...
|
||||
|
||||
class CmdHit(Command):
|
||||
|
|
@ -67,13 +68,9 @@ class CmdHit(Command):
|
|||
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.
|
||||
The `parse` method is a special one Evennia knows to call _before_ `func`. At this time it has access to all the same on-command variables as `func` does. 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
|
||||
|
||||
|
|
@ -111,14 +108,16 @@ from this class and just implement the `func` needed for that command without im
|
|||
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.
|
||||
Now onto the `func` method. The main difference is we now have `self.target` and `self.weapon` available for convenient use.
|
||||
```{sidebar}
|
||||
Here we create the messages to send to each side of the fight explicitly. Later we'll find out how to use Evennia's [inline functions](../../../Components/FuncParser.md) to send a single string that looks different depending on who sees it.
|
||||
```
|
||||
|
||||
- **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.
|
||||
|
||||
- **Lines 41-42** - We merge the `weaponstr` with our attack texts and send it to attacker and target respectively.
|
||||
Let's try it out!
|
||||
|
||||
> reload
|
||||
|
|
@ -126,9 +125,9 @@ Let's try it out!
|
|||
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.
|
||||
Oops, our `self.caller.search(self.weapon)` is telling us that it found no sword. This is reasonable (we don't have a sword). Since we are not `return`ing when failing to find a weapon in the way we do if we find no `target`, we still continue fighting with our bare hands.
|
||||
|
||||
This won't do. Let's make ourselves a sword:
|
||||
|
||||
> create sword
|
||||
|
||||
|
|
@ -139,13 +138,16 @@ change (no code changed, only stuff in the database).
|
|||
> hit smaug with sword
|
||||
You hit smaug with sword!
|
||||
|
||||
Poor Smaug.
|
||||
|
||||
## Adding a Command to an object
|
||||
|
||||
The commands of a cmdset attached to an object with `obj.cmdset.add()` will by default be made available to that object
|
||||
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)
|
||||
also has many examples of objects with commands on them.
|
||||
```{sidebar} Command Sets on Characters
|
||||
In case you wonder, the 'Character CmdSet' on `Characters` is configured to be available to _only_ that Character. If not, you'd get command multi-matches for things like `look` whenever you were in the same room with another character using the same command set. See [Command Sets](../../../Components/Command-Sets.md) docs for more info.
|
||||
```
|
||||
As we learned in the lesson about [Adding commands](./Beginner-Tutorial-Adding-Commands.md), Commands are are grouped in Command Sets. Such Command Sets are attached to an object with `obj.cmdset.add()` and will then be available for that object to use.
|
||||
|
||||
What we didn't mention before is that by default those commands are _also available to those in the same location as that object_. If you did the [Building quickstart lesson](./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) also has many examples of objects with commands on them.
|
||||
|
||||
To show how this could work, let's put our 'hit' Command on our simple `sword` object from the previous section.
|
||||
|
||||
|
|
@ -163,12 +165,10 @@ Let's try to swing it!
|
|||
|
||||
```{sidebar} Multi-matches
|
||||
|
||||
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 differently depending on the object it sits on? Besides, imagine if you had 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?
|
||||
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 differently depending on the object it sits on? Besides, imagine if you had 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?
|
||||
```
|
||||
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:
|
||||
|
||||
Woah, that didn't go as planned. Evennia actually found _two_ `hit` commands and 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:
|
||||
|
||||
> hit-1
|
||||
Who do you want to hit?
|
||||
|
|
@ -181,7 +181,7 @@ In this case we don't need both command-sets, so let's just keep the one on the
|
|||
> hit
|
||||
Who do you want to hit?
|
||||
|
||||
Now try this:
|
||||
Now try making a new location and then drop the sword in it.
|
||||
|
||||
> tunnel n = kitchen
|
||||
> n
|
||||
|
|
@ -193,33 +193,30 @@ Now try this:
|
|||
> 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.
|
||||
The `hit` command is only available if you hold _or_ are in the same room as the sword.
|
||||
|
||||
### You need to hold the sword!
|
||||
|
||||
Let's get a little ahead of ourselves and make it so you have to _hold_ the sword for the `hit` command to
|
||||
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
|
||||
it.
|
||||
```{sidebar} Locks
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
Let's get a little ahead of ourselves and make it so you have to _hold_ the sword for the `hit` command to be available. This involves a [Lock](../../../Components/Locks.md). 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 it.
|
||||
|
||||
> py self.search("sword").locks.add("call:holds()")
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
For locks to work, you cannot be _superuser_, since the superuser passes all locks. You need to `quell` yourself first:
|
||||
|
||||
For locks to work, you cannot be _superuser_, since the superuser passes all locks. You need to `quell` yourself
|
||||
first:
|
||||
```{sidebar} quell/unquell
|
||||
|
||||
Quelling allows you as a developer to take on the role of players with less priveleges. This is useful for testing and debugging, in particular since a superuser has a little `too` much power sometimes. Use `unquell` to get back to your normal self.
|
||||
```
|
||||
|
||||
> quell
|
||||
|
||||
|
||||
If the sword lies on the ground, try
|
||||
|
||||
> hit
|
||||
|
|
@ -227,10 +224,8 @@ If the sword lies on the ground, try
|
|||
> get sword
|
||||
> hit
|
||||
> 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:
|
||||
|
||||
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:
|
||||
|
||||
delete sword
|
||||
|
||||
|
|
@ -242,18 +237,7 @@ or
|
|||
## Adding the Command to a default Cmdset
|
||||
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
- 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`.
|
||||
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`. Doing this this way is a little cumbersome though. It would be better to add this to all characters.
|
||||
|
||||
The default cmdset are defined in `mygame/commands/default_cmdsets.py`. Open that file now:
|
||||
|
||||
|
|
@ -313,15 +297,15 @@ class SessionCmdSet(default_cmds.SessionCmdSet):
|
|||
|
||||
The `super()` function refers to the parent of the current class and is commonly used to call same-named methods on the parent.
|
||||
```
|
||||
`evennia.default_cmds` is a container that holds all of Evennia's default commands and cmdsets. In this module
|
||||
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.
|
||||
`evennia.default_cmds` is a container that holds all of Evennia's default commands and cmdsets. In this module 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.
|
||||
This is what adds all the default commands to each CmdSet.
|
||||
|
||||
To add even more Commands to a default cmdset, we can just add them below the `super()` line. Usefully, if we were to
|
||||
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.
|
||||
When the `DefaultCharacter` (or a child of it) is created, you'll find that the equivalence of `self.cmdset.add("default_cmdsets.CharacterCmdSet, persistent=True")` gets called. This means that all new Characters get this cmdset. After adding more commands to it, you just need to reload to have all characters see it.
|
||||
|
||||
- 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) your Session have access to the `UnloggedinCmdSet`.
|
||||
|
||||
For now, let's add our own `hit` and `echo` commands to the `CharacterCmdSet`:
|
||||
|
||||
|
|
@ -350,8 +334,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
> hit
|
||||
Who do you want to hit?
|
||||
|
||||
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:
|
||||
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 your own _CmdSet_ to the other cmdset.
|
||||
|
||||
```python
|
||||
from commands import mycommands
|
||||
|
|
@ -379,7 +362,7 @@ To remove your custom commands again, you of course just delete the change you d
|
|||
|
||||
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
|
||||
from Evennia. We happen to know this can be found as `default_cmds.CmdGet`.
|
||||
from Evennia. If you investigate the `default_cmds.CharacterCmdSet` parent, you'll find that its class is `default_cmds.CmdGet` (the 'real' location is `evennia.commands.default.general.CmdGet`).
|
||||
|
||||
|
||||
```python
|
||||
|
|
@ -410,11 +393,12 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
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.
|
||||
|
||||
Let's combine this with what we know about classes and
|
||||
how to _override_ a parent class. Open `mygame/commands/mycommands.py` and lets override
|
||||
that `CmdGet` command.
|
||||
Let's combine this with what we know about classes and how to _override_ a parent class. Open `mygame/commands/mycommands.py` and make a new `get` command:
|
||||
|
||||
```{code-block} python
|
||||
:linenos:
|
||||
:emphasize-lines: 2,7,8,9
|
||||
|
||||
```python
|
||||
# up top, by the other imports
|
||||
from evennia import default_cmds
|
||||
|
||||
|
|
@ -424,10 +408,9 @@ 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.
|
||||
- **Line 2**: We import `default_cmds` so we can get the parent class.
|
||||
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,
|
||||
|
|
@ -462,6 +445,9 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
self.add(mycommands.MyCmdGet)
|
||||
# ...
|
||||
```
|
||||
|
||||
We don't need to use `self.remove()` first; just adding a command with the same `key` (`get`) will replace the default `get` we had from before.
|
||||
|
||||
```{sidebar} Another way
|
||||
|
||||
Instead of adding `MyCmdGet` explicitly in default_cmdset.py, you could also add it to `mycommands.MyCmdSet` and let it be added automatically here for you.
|
||||
|
|
@ -472,12 +458,10 @@ Instead of adding `MyCmdGet` explicitly in default_cmdset.py, you could also add
|
|||
Get What?
|
||||
[smaug, fluffy, YourName, ...]
|
||||
|
||||
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).
|
||||
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 ...).
|
||||
|
||||
## Summary
|
||||
|
||||
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
|
||||
command on ourselves.
|
||||
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 command on ourselves. Knowing to add commands is a big part of making a game!
|
||||
|
||||
We have been beating on poor Smaug for too long. Next we'll create more things to play around with.
|
||||
Loading…
Add table
Add a link
Reference in a new issue