Complete permanent->persistent rename of cmdset kwarg for consistency

This commit is contained in:
Griatch 2021-08-06 17:16:44 +02:00
parent 6e38d0ae4c
commit a815db4ca9
20 changed files with 362 additions and 362 deletions

View file

@ -42,16 +42,16 @@ Sets#merge-rules) section).
from evennia import CmdSet
# this is a theoretical custom module with commands we
# this is a theoretical custom module with commands we
# created previously: mygame/commands/mycommands.py
from commands import mycommands
class MyCmdSet(CmdSet):
class MyCmdSet(CmdSet):
def at_cmdset_creation(self):
"""
The only thing this method should need
to do is to add commands to the set.
"""
"""
self.add(mycommands.MyCommand1())
self.add(mycommands.MyCommand2())
self.add(mycommands.MyCommand3())
@ -61,7 +61,7 @@ The CmdSet's `add()` method can also take another CmdSet as input. In this case
from that CmdSet will be appended to this one as if you added them line by line:
```python
def at_cmdset_creation():
def at_cmdset_creation():
...
self.add(AdditionalCmdSet) # adds all command from this set
...
@ -71,10 +71,10 @@ If you added your command to an existing cmdset (like to the default cmdset), th
loaded into memory. You need to make the server aware of the code changes:
```
@reload
@reload
```
You should now be able to use the command.
You should now be able to use the command.
If you created a new, fresh cmdset, this must be added to an object in order to make the commands
within available. A simple way to temporarily test a cmdset on yourself is use the `@py` command to
@ -93,15 +93,15 @@ This will stay with you until you `@reset` or `@shutdown` the server, or you run
In the example above, a specific Cmdset class is removed. Calling `delete` without arguments will
remove the latest added cmdset.
> Note: Command sets added using `cmdset.add` are, by default, *not* persistent in the database.
> Note: Command sets added using `cmdset.add` are, by default, *not* persistent in the database.
If you want the cmdset to survive a reload, you can do:
If you want the cmdset to survive a reload, you can do:
```
@py self.cmdset.add(commands.mycmdset.MyCmdSet, permanent=True)
@py self.cmdset.add(commands.mycmdset.MyCmdSet, persistent=True)
```
Or you could add the cmdset as the *default* cmdset:
Or you could add the cmdset as the *default* cmdset:
```
@py self.cmdset.add_default(commands.mycmdset.MyCmdSet)
@ -288,12 +288,12 @@ theory](https://en.wikipedia.org/wiki/Set_theory).
cmdset ends up in the merged cmdset. Same-key commands are merged by priority.
# Union
A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4
A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4
- **Intersect** - Only commands found in *both* cmdsets (i.e. which have the same keys) end up in
the merged cmdset, with the higher-priority cmdset replacing the lower one's commands.
# Intersect
# Intersect
A1,A3,A5 + B1,B2,B4,B5 = A1,A5
- **Replace** - The commands of the higher-prio cmdset completely replaces the lower-priority
@ -337,23 +337,23 @@ onto E and not B, our `key_mergetype` directive won't trigger. To make sure it w
sure we merge onto B. Setting E's priority to, say, -4 will make sure to merge it onto B and affect
it appropriately.
More advanced cmdset example:
More advanced cmdset example:
```python
from commands import mycommands
class MyCmdSet(CmdSet):
key = "MyCmdSet"
priority = 4
mergetype = "Replace"
key_mergetypes = {'MyOtherCmdSet':'Union'}
key_mergetypes = {'MyOtherCmdSet':'Union'}
def at_cmdset_creation(self):
"""
The only thing this method should need
to do is to add commands to the set.
"""
"""
self.add(mycommands.MyCommand1())
self.add(mycommands.MyCommand2())
self.add(mycommands.MyCommand3())
@ -373,4 +373,4 @@ exits are merged in), these two commands will be considered *identical* since th
means only one of them will remain after the merger. Each will also be compared with all other
commands having any combination of the keys and/or aliases "kick", "punch" or "fight".
... So avoid duplicate aliases, it will only cause confusion.
... So avoid duplicate aliases, it will only cause confusion.

View file

@ -190,7 +190,7 @@ class Mech(Object):
def at_object_creation(self):
"This is called only when object is first created"
self.cmdset.add_default(default_cmds.CharacterCmdSet)
self.cmdset.add(MechCmdSet, permanent=True)
self.cmdset.add(MechCmdSet, persistent=True)
self.locks.add("puppet:all();call:false()")
self.db.desc = "This is a huge mech. It has missiles and stuff."
```
@ -233,4 +233,4 @@ Character (since any Object can move inside another). In that case the “inside
could be the “cockpit”. The cockpit would have the `MechCommandSet` stored on itself and all the
shooting goodness would be made available to you only when you enter it.
And of course you could put more guns on it. And make it fly.
And of course you could put more guns on it. And make it fly.

View file

@ -126,7 +126,7 @@ class BlockingCmdSet(CmdSet):
class BlockingRoom(Room):
def at_object_creation(self):
self.cmdset.add(BlockingCmdSet, permanent=True)
self.cmdset.add(BlockingCmdSet, persistent=True)
# only share commands with players in the room that
# are NOT Builders or higher
self.locks.add("call:not perm(Builders)")
@ -375,4 +375,4 @@ for staff to browse the list and display how long ago the login occurred.
their len() suggests. There is little Evennia can (reliably) do about this. If you are using such
characters, you need to make sure to use a suitable mono-spaced font where are width are equal. You
can set this in your web client and need to recommend it for telnet-client users. See [this
discussion](https://github.com/evennia/evennia/issues/1522) where some suitable fonts are suggested.
discussion](https://github.com/evennia/evennia/issues/1522) where some suitable fonts are suggested.

View file

@ -232,7 +232,7 @@ from evennia.utils.create import create_object
# class for our front shop room
class NPCShop(DefaultRoom):
def at_object_creation(self):
# we could also use add(ShopCmdSet, permanent=True)
# we could also use add(ShopCmdSet, persistent=True)
self.cmdset.add_default(ShopCmdSet)
self.db.storeroom = None
@ -331,4 +331,4 @@ Fixing these issues are left as an exercise.
If you want to keep the shop fully NPC-run you could add a [Script](../Components/Scripts) to restock the shop's
store room regularly. This shop example could also easily be owned by a human Player (run for them
by a hired NPC) - the shop owner would get the key to the store room and be responsible for keeping
it well stocked.
it well stocked.

View file

@ -1,30 +1,30 @@
# Our own commands
In this lesson we'll learn how to create our own Evennia _Commands_. If you are new to Python you'll
also learn some more basics about how to manipulate strings and get information out of Evennia.
In this lesson we'll learn how to create our own Evennia _Commands_. If you are new to Python you'll
also learn some more basics about how to manipulate strings and get information out of Evennia.
A Command is something that handles the input from a user and causes a result to happen.
An example is `look`, which examines your current location and tells how it looks like and
what is in it.
what is in it.
```sidebar:: Commands are not typeclassed
If you just came from the previous lesson, you might want to know that Commands and
CommandSets are not `typeclassed`. That is, instances of them are not saved to the
If you just came from the previous lesson, you might want to know that Commands and
CommandSets are not `typeclassed`. That is, instances of them are not saved to the
database. They are "just" normal Python classes.
```
In Evennia, a Command is a Python _class_. If you are unsure about what a class is, review the
In Evennia, a Command is a Python _class_. If you are unsure about what a class is, review the
previous lessons! A Command inherits from `evennia.Command` or from one of the alternative command-
classes, such as `MuxCommand` which is what most default commands use.
classes, such as `MuxCommand` which is what most default commands use.
All Commands are in turn grouped in another class called a _Command Set_. Think of a Command Set
as a bag holding many different commands. One CmdSet could for example hold all commands for
combat, another for building etc. By default, Evennia groups all character-commands into one
big cmdset.
as a bag holding many different commands. One CmdSet could for example hold all commands for
combat, another for building etc. By default, Evennia groups all character-commands into one
big cmdset.
Command-Sets are then associated with objects, for example with your Character. Doing so makes the
commands in that cmdset available to the object. So, to summarize:
Command-Sets are then associated with objects, for example with your Character. Doing so makes the
commands in that cmdset available to the object. So, to summarize:
- Commands are classes
- A group of Commands is stored in a CmdSet
@ -45,25 +45,25 @@ from evennia import Command as BaseCommand
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.
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.
> 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:
`mygame/commands/mycommands.py` and add the following code:
```python
@ -74,8 +74,8 @@ class CmdEcho(Command):
```
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.
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:
@ -96,24 +96,24 @@ class MyCmdSet(CmdSet):
```
Our `EchoCmdSet` class must have an `at_cmdset_creation` method, named exactly
like this - this is what Evennia will be looking for when setting up the cmdset later, so
if you didn't set it up, it will use the parent's version, which is empty. Inside we add the
command class to the cmdset by `self.add()`. If you wanted to add more commands to this CmdSet you
could just add more lines of `self.add` after this.
Our `EchoCmdSet` class must have an `at_cmdset_creation` method, named exactly
like this - this is what Evennia will be looking for when setting up the cmdset later, so
if you didn't set it up, it will use the parent's version, which is empty. Inside we add the
command class to the cmdset by `self.add()`. If you wanted to add more commands to this CmdSet you
could just add more lines of `self.add` after this.
Finally, let's add this command to ourselves so we can try it out. In-game you can experiment with `py` again:
> py self.cmdset.add("commands.mycommands.MyCmdSet")
Now try
> echo
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
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:
@ -124,7 +124,7 @@ use your Command. Let's look at some of those listed:
cmdname (<class 'str'>): echo
raw_cmdname (<class 'str'>): echo
cmdstring (<class 'str'>): echo
args (<class 'str'>):
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,
@ -133,7 +133,7 @@ use your Command. Let's look at some of those listed:
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
raw_string (<class 'str'>): echo
--------------------------------------------------
echo - Command variables from evennia:
@ -147,16 +147,16 @@ use your Command. Let's look at some of those listed:
command string given (self.cmdstring): echo
current cmdset (self.cmdset): ChannelCmdSet
These are all properties you can access with `.` on the Command instance, such as `.key`, `.args` and so on.
Evennia makes these available to you and they will be different every time a command is run. The most
important ones we will make use of now are:
These are all properties you can access with `.` on the Command instance, such as `.key`, `.args` and so on.
Evennia makes these available to you and they will be different every time a command is run. The most
important ones we will make use of now are:
- `caller` - this is 'you', the person calling the command.
- `args` - this is all arguments to the command. Now it's empty, but if you tried `echo foo bar` you'd find
- `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
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
@ -165,47 +165,47 @@ looks for to figure out what a Command actually does. Modify your `CmdEcho` clas
class CmdEcho(Command):
"""
A simple echo command
Usage:
echo <something>
echo <something>
"""
key = "echo"
def func(self):
self.caller.msg(f"Echo: '{self.args}'")
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
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.
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
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
Echo: ''
Try to pass an argument:
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:
the command name, including spaces. Evennia will happily understand if you skip that space too:
> echoWoo Tang!
Echo: 'Woo Tang!'
There are ways to force Evennia to _require_ an initial space, but right now we want to just ignore it since
it looks a bit weird for our echo example. Tweak the code:
There are ways to force Evennia to _require_ an initial space, but right now we want to just ignore it since
it looks a bit weird for our echo example. Tweak the code:
```python
# ...
@ -213,15 +213,15 @@ it looks a bit weird for our echo example. Tweak the code:
class CmdEcho(Command):
"""
A simple echo command
Usage:
echo <something>
echo <something>
"""
key = "echo"
def func(self):
self.caller.msg(f"Echo: '{self.args.strip()}'")
self.caller.msg(f"Echo: '{self.args.strip()}'")
# ...
```
@ -230,25 +230,25 @@ The only difference is that we called `.strip()` on `self.args`. This is a helpe
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
> 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.
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:
enough to make `echo` a _persistent_ 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
> py self.cmdset.add("commands.mycommands.MyCmdSet", persistent=True)
Now you can `reload` as much as you want and your code changes will be available directly without
needing to re-add the MyCmdSet again. To remove the cmdset again, do
> py self.cmdset.remove("commands.mycommands.MyCmdSet")
@ -258,18 +258,18 @@ 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:
someone in the face! This is how we want it to work:
> hit <target>
You hit <target> with full force!
You hit <target> with full force!
Not only that, we want the <target> to see
You got hit by <hitter> with full force!
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.
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`.
Still in `mygame/commands/mycommands.py`, add a new class, between `CmdEcho` and `MyCmdSet`.
```python
# ...
@ -277,7 +277,7 @@ Still in `mygame/commands/mycommands.py`, add a new class, between `CmdEcho` and
class CmdHit(Command):
"""
Hit a target.
Usage:
hit <target>
@ -288,11 +288,11 @@ class CmdHit(Command):
args = self.args.strip()
if not args:
self.caller.msg("Who do you want to hit?")
return
return
target = self.caller.search(args)
if not target:
return
self.caller.msg(f"You hit {target.key} with full force!")
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!")
# ...
@ -302,46 +302,46 @@ 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
- **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
The full form of the if statement is
if condition:
...
elif othercondition:
...
else:
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.
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":
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`
- `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".
- 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:
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 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`.
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.
Finally we must also add this to a CmdSet. Let's add it to `MyCmdSet` which we made persistent earlier.
```python
# ...
@ -357,17 +357,17 @@ class MyCmdSet(CmdSet):
```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
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:
Next we reload to let Evennia know of these code changes and try it out:
> reload
> reload
hit
Who do you want to hit?
hit me
@ -377,7 +377,7 @@ Next we reload to let Evennia know of these code changes and try it out:
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
hit smaug
You hit Smaug with full force!
You won't see the second string. Only Smaug sees that (and is not amused).
@ -385,8 +385,8 @@ You won't see the second string. Only Smaug sees that (and is not amused).
## Summary
In this lesson we learned how to create our own Command, add it to a CmdSet and then to ourselves.
We also upset a dragon.
In this lesson we learned how to create our own Command, add it to a CmdSet and then to ourselves.
We also upset a dragon.
In the next lesson we'll learn how to hit Smaug with different weapons. We'll also
get into how we replace and extend Evennia's default Commands.
In the next lesson we'll learn how to hit Smaug with different weapons. We'll also
get into how we replace and extend Evennia's default Commands.

View file

@ -1,25 +1,25 @@
# More about Commands
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.
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.
## More advanced parsing
## More advanced parsing
In the last lesson we made a `hit` Command and hit a dragon with it. You should have the code
from that still around.
In the last lesson we made a `hit` Command and hit 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:
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
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
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`:
@ -29,19 +29,19 @@ a little, in a new method `parse`:
class CmdHit(Command):
"""
Hit a target.
Usage:
hit <target>
"""
key = "hit"
def parse(self):
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()
target, *weapon = target.split(" ", 1)
self.target = target.strip()
if weapon:
self.weapon = weapon.strip()
else:
@ -50,162 +50,162 @@ class CmdHit(Command):
def func(self):
if not self.args:
self.caller.msg("Who do you want to hit?")
return
return
# get the target for the hit
target = self.caller.search(self.target)
target = self.caller.search(self.target)
if not target:
return
# get and handle the weapon
return
# get and handle the weapon
weapon = None
if self.weapon:
weapon = self.caller.search(self.weapon)
if weapon:
if weapon:
weaponstr = f"{weapon.key}"
else:
weaponstr = "bare fists"
self.caller.msg(f"You hit {target.key} with {weaponstr}!")
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_
`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
```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.
- 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
- **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.
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
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):
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
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
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
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
> 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
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!
> hit smaug with sword
You hit smaug with sword!
## Adding a Command to an object
## 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](./Building-Quickstart)
you've seen an example of this with the "Red Button" object. The [Tutorial world](./Tutorial-World-Introduction)
also has many examples of objects with commands on them.
you've seen an example of this with the "Red Button" object. The [Tutorial world](./Tutorial-World-Introduction)
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.
> self.search("sword").cmdset.add("commands.mycommands.MyCmdSet", permanent=True)
> self.search("sword").cmdset.add("commands.mycommands.MyCmdSet", persistent=True)
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.
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.
Let's try to swing it!
> hit
> hit
More than one match for 'hit' (please narrow target):
hit-1 (sword #11)
hit-2
```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
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 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:
> hit-1
> hit-1
Who do you want to hit?
> hit-2
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:
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:
> self.cmdset.remove("commands.mycommands.MyCmdSet")
> hit
Who do you want to hit?
Now try this:
Now try this:
> tunnel n = kitchen
> n
> drop sword
> n
> drop sword
> s
> hit
Command 'hit' is not available. Maybe you meant ...
> n
> 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.
> 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.
### 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
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.
it.
```sidebar:: Locks
Evennia Locks are defined as a mini-language defined in `lockstrings`. The lockstring
@ -215,49 +215,49 @@ 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:
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
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
Command 'hit' is not available. ..
> get sword
> hit
> 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:
delete sword
or
delete sword
py self.search("sword").delete()
or
py self.search("sword").delete()
## 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`.
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.
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`
@ -266,7 +266,7 @@ All these commands are in just loaded on the default objects that Evennia provid
The thing must commonly modified is the `CharacterCmdSet`.
The default cmdset are defined in `mygame/commands/default_cmdsets.py`. Open that file now:
The default cmdset are defined in `mygame/commands/default_cmdsets.py`. Open that file now:
```python
"""
@ -321,19 +321,19 @@ class SessionCmdSet(default_cmds.SessionCmdSet):
```
```sidebar:: super()
The `super()` function refers to the parent of the current class and is commonly
used to call same-named methods on the parent.
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
`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.
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.
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.
For now, let's add our own `hit` and `echo` commands to the `CharacterCmdSet`:
@ -358,9 +358,9 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
```
> reload
> reload
> hit
Who do you want to 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:
@ -381,19 +381,19 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
self.add(mycommands.MyCmdSet)
```
Which way you use depends on how much control you want, but if you already have a CmdSet,
Which way you use depends on how much control you want, but if you already have a CmdSet,
this is practical. A Command can be a part of any number of different CmdSets.
### Removing Commands
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?
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?
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
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`.
```python
# ...
from commands import mycommands
@ -413,16 +413,16 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
# ...
```
> reload
> reload
> get
Command 'get' is not available ...
## Replace a default command
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.
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
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.
@ -430,7 +430,7 @@ that `CmdGet` command.
# up top, by the other imports
from evennia import default_cmds
# somewhere below
# somewhere below
class MyCmdGet(default_cmds.CmdGet):
def func(self):
@ -440,20 +440,20 @@ class MyCmdGet(default_cmds.CmdGet):
```
- **Line2**: 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,
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,
- **Line 7**: By adding our own `func` we replace the one in the parent.
- **Line 8**: For this simple change we still want the command to work the
- **Line 8**: For this simple change we still want the command to work the
same as before, so we use `super()` to call `func` on the parent.
- **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.
- **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.
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.
We now just have to add this so it replaces the default `get` command. Open
We now just have to add this so it replaces the default `get` command. Open
`mygame/commands/default_cmdsets.py` again:
```python
@ -476,22 +476,22 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
```
```sidebar:: Another way
Instead of adding `MyCmdGet` explicitly in default_cmdset.py,
you could also add it to `mycommands.MyCmdSet` and let it be
Instead of adding `MyCmdGet` explicitly in default_cmdset.py,
you could also add it to `mycommands.MyCmdSet` and let it be
added automatically for you.
```
> reload
> get
> reload
> get
Get What?
[smaug, fluffy, YourName, ...]
[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
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.

View file

@ -56,8 +56,8 @@ class Character(DefaultCharacter):
[...]
"""
def at_object_creation(self):
"This is called when object is first created, only."
self.db.power = 1
"This is called when object is first created, only."
self.db.power = 1
self.db.combat_score = 1
```
@ -94,10 +94,10 @@ check it. Using this method however will make it easy to add more functionality
What we need are the following:
- One character generation [Command](../../../Components/Commands) to set the "Power" on the `Character`.
- A chargen [CmdSet](../../../Components/Command-Sets) to hold this command. Lets call it `ChargenCmdset`.
- A custom `ChargenRoom` type that makes this set of commands available to players in such rooms.
- One such room to test things in.
- One character generation [Command](../../../Components/Commands) to set the "Power" on the `Character`.
- A chargen [CmdSet](../../../Components/Command-Sets) to hold this command. Lets call it `ChargenCmdset`.
- A custom `ChargenRoom` type that makes this set of commands available to players in such rooms.
- One such room to test things in.
### The +setpower command
@ -114,7 +114,7 @@ Open `command.py` file. It contains documented empty templates for the base comm
`MuxCommand` class offers some extra features like stripping whitespace that may be useful - if so,
just import from that instead.
Add the following to the end of the `command.py` file:
Add the following to the end of the `command.py` file:
```python
# end of command.py
@ -124,13 +124,13 @@ class CmdSetPower(Command):
"""
set the power of a character
Usage:
Usage:
+setpower <1-10>
This sets the power of the current character. This can only be
used during character generation.
This sets the power of the current character. This can only be
used during character generation.
"""
key = "+setpower"
help_category = "mush"
@ -138,10 +138,10 @@ class CmdSetPower(Command):
"This performs the actual command"
errmsg = "You must supply a number between 1 and 10."
if not self.args:
self.caller.msg(errmsg)
self.caller.msg(errmsg)
return
try:
power = int(self.args)
power = int(self.args)
except ValueError:
self.caller.msg(errmsg)
return
@ -180,7 +180,7 @@ class ChargenCmdset(CmdSet):
key = "Chargen"
def at_cmdset_creation(self):
"This is called at initialization"
self.add(command.CmdSetPower())
self.add(command.CmdSetPower())
```
In the future you can add any number of commands to this cmdset, to expand your character generation
@ -193,10 +193,10 @@ It's cleaner to put it on a room, so it's only available when players are in tha
We will create a simple Room typeclass to act as a template for all our Chargen areas. Edit
`mygame/typeclasses/rooms.py` next:
```python
```python
from commands.default_cmdsets import ChargenCmdset
# ...
# ...
# down at the end of rooms.py
class ChargenRoom(Room):
@ -206,10 +206,10 @@ class ChargenRoom(Room):
"""
def at_object_creation(self):
"this is called only at first creation"
self.cmdset.add(ChargenCmdset, permanent=True)
self.cmdset.add(ChargenCmdset, persistent=True)
```
Note how new rooms created with this typeclass will always start with `ChargenCmdset` on themselves.
Don't forget the `permanent=True` keyword or you will lose the cmdset after a server reload. For
Don't forget the `persistent=True` keyword or you will lose the cmdset after a server reload. For
more information about [Command Sets](../../../Components/Command-Sets) and [Commands](../../../Components/Commands), see the respective
links.
@ -235,7 +235,7 @@ as `chargen;character generation`.
So in summary, this will create a new room of type ChargenRoom and open an exit `chargen` to it and
an exit back here named `finish`. If you see errors at this stage, you must fix them in your code.
`@reload`
between fixes. Don't continue until the creation seems to have worked okay.
between fixes. Don't continue until the creation seems to have worked okay.
chargen
@ -263,19 +263,19 @@ set during Character generation:
> +attack
You +attack with a combat score of 12!
Go back to `mygame/commands/command.py` and add the command to the end like this:
Go back to `mygame/commands/command.py` and add the command to the end like this:
```python
```python
import random
# ...
# ...
class CmdAttack(Command):
"""
issues an attack
issues an attack
Usage:
+attack
Usage:
+attack
This will calculate a new combat score based on your Power.
Your combat score is visible to everyone in the same location.
@ -288,8 +288,8 @@ class CmdAttack(Command):
caller = self.caller
power = caller.db.power
if not power:
# this can happen if caller is not of
# our custom Character typeclass
# this can happen if caller is not of
# our custom Character typeclass
power = 1
combat_score = random.randint(1, 10 * power)
caller.db.combat_score = combat_score
@ -297,10 +297,10 @@ class CmdAttack(Command):
# announce
message = "%s +attack%s with a combat score of %s!"
caller.msg(message % ("You", "", combat_score))
caller.location.msg_contents(message %
caller.location.msg_contents(message %
(caller.key, "s", combat_score),
exclude=caller)
```
```
What we do here is simply to generate a "combat score" using Python's inbuilt `random.randint()`
function. We then store that and echo the result to everyone involved.
@ -349,8 +349,8 @@ class Character(DefaultCharacter):
[...]
"""
def at_object_creation(self):
"This is called when object is first created, only."
self.db.power = 1
"This is called when object is first created, only."
self.db.power = 1
self.db.combat_score = 1
def return_appearance(self, looker):
@ -395,16 +395,16 @@ instead put all relevant NPC commands in the default command set and limit event
### Creating an NPC with +createNPC
We need a command for creating the NPC, this is a very straightforward command:
We need a command for creating the NPC, this is a very straightforward command:
> +createnpc Anna
You created the NPC 'Anna'.
You created the NPC 'Anna'.
At the end of `command.py`, create our new command:
```python
from evennia import create_object
class CmdCreateNPC(Command):
"""
create a new npc
@ -413,12 +413,12 @@ class CmdCreateNPC(Command):
+createNPC <name>
Creates a new, named NPC. The NPC will start with a Power of 1.
"""
"""
key = "+createnpc"
aliases = ["+createNPC"]
locks = "call:not perm(nonpcs)"
help_category = "mush"
help_category = "mush"
def func(self):
"creates the object and names it"
caller = self.caller
@ -432,15 +432,15 @@ class CmdCreateNPC(Command):
# make name always start with capital letter
name = self.args.strip().capitalize()
# create npc in caller's location
npc = create_object("characters.Character",
key=name,
npc = create_object("characters.Character",
key=name,
location=caller.location,
locks="edit:id(%i) and perm(Builders);call:false()" % caller.id)
# announce
# announce
message = "%s created the NPC '%s'."
caller.msg(message % ("You", name))
caller.location.msg_contents(message % (caller.key, name),
exclude=caller)
caller.msg(message % ("You", name))
caller.location.msg_contents(message % (caller.key, name),
exclude=caller)
```
Here we define a `+createnpc` (`+createNPC` works too) that is callable by everyone *not* having the
`nonpcs` "[permission](../../../Components/Locks#Permissions)" (in Evennia, a "permission" can just as well be used to
@ -475,38 +475,38 @@ principle re-work our old `+setpower` command, but let's try something more usef
`+editNPC` command.
> +editNPC Anna/power = 10
Set Anna's property 'power' to 10.
Set Anna's property 'power' to 10.
This is a slightly more complex command. It goes at the end of your `command.py` file as before.
This is a slightly more complex command. It goes at the end of your `command.py` file as before.
```python
class CmdEditNPC(Command):
"""
edit an existing NPC
Usage:
Usage:
+editnpc <name>[/<attribute> [= value]]
Examples:
+editnpc mynpc/power = 5
+editnpc mynpc/power - displays power value
+editnpc mynpc - shows all editable
+editnpc mynpc - shows all editable
attributes and values
This command edits an existing NPC. You must have
This command edits an existing NPC. You must have
permission to edit the NPC to use this.
"""
key = "+editnpc"
aliases = ["+editNPC"]
locks = "cmd:not perm(nonpcs)"
help_category = "mush"
help_category = "mush"
def parse(self):
"We need to do some parsing here"
args = self.args
propname, propval = None, None
if "=" in args:
args, propval = [part.strip() for part in args.rsplit("=", 1)]
if "=" in args:
args, propval = [part.strip() for part in args.rsplit("=", 1)]
if "/" in args:
args, propname = [part.strip() for part in args.rsplit("/", 1)]
# store, so we can access it below in func()
@ -519,38 +519,38 @@ class CmdEditNPC(Command):
"do the editing"
allowed_propnames = ("power", "attribute1", "attribute2")
caller = self.caller
if not self.args or not self.name:
caller.msg("Usage: +editnpc name[/propname][=propval]")
caller.msg("Usage: +editnpc name[/propname][=propval]")
return
npc = caller.search(self.name)
if not npc:
return
if not npc.access(caller, "edit"):
caller.msg("You cannot change this NPC.")
return
return
if not self.propname:
# this means we just list the values
output = "Properties of %s:" % npc.key
for propname in allowed_propnames:
for propname in allowed_propnames:
propvalue = npc.attributes.get(propname, default="N/A")
output += "\n %s = %s" % (propname, propvalue)
caller.msg(output)
elif self.propname not in allowed_propnames:
caller.msg("You may only change %s." %
elif self.propname not in allowed_propnames:
caller.msg("You may only change %s." %
", ".join(allowed_propnames))
elif self.propval:
# assigning a new propvalue
# in this example, the properties are all integers...
intpropval = int(self.propval)
npc.attributes.add(self.propname, intpropval)
intpropval = int(self.propval)
npc.attributes.add(self.propname, intpropval)
caller.msg("Set %s's property '%s' to %s" %
(npc.key, self.propname, self.propval))
else:
# propname set, but not propval - show current value
caller.msg("%s has property %s = %s" %
(npc.key, self.propname,
caller.msg("%s has property %s = %s" %
(npc.key, self.propname,
npc.attributes.get(self.propname, default="N/A")))
```
@ -559,7 +559,7 @@ checking. It searches for the given npc in the same room, and checks so the call
permission to "edit" it before continuing. An account without the proper permission won't even be
able to view the properties on the given NPC. It's up to each game if this is the way it should be.
Add this to the default command set like before and you should be able to try it out.
Add this to the default command set like before and you should be able to try it out.
_Note: If you wanted a player to use this command to change an on-object property like the NPC's
name (the `key` property), you'd need to modify the command since "key" is not an Attribute (it is
@ -587,11 +587,11 @@ class CmdNPC(Command):
"""
controls an NPC
Usage:
Usage:
+npc <name> = <command>
This causes the npc to perform a command as itself. It will do so
with its own permissions and accesses.
with its own permissions and accesses.
"""
key = "+npc"
locks = "call:not perm(nonpcs)"
@ -601,7 +601,7 @@ class CmdNPC(Command):
"Simple split of the = sign"
name, cmdname = None, None
if "=" in self.args:
name, cmdname = [part.strip()
name, cmdname = [part.strip()
for part in self.args.rsplit("=", 1)]
self.name, self.cmdname = name, cmdname
@ -611,7 +611,7 @@ class CmdNPC(Command):
if not self.cmdname:
caller.msg("Usage: +npc <name> = <command>")
return
npc = caller.search(self.name)
npc = caller.search(self.name)
if not npc:
return
if not npc.access(caller, "edit"):
@ -651,4 +651,4 @@ specific player (or npc) and automatically compare their relevant attributes to
To continue from here, you can take a look at the [Tutorial World](../Part1/Tutorial-World-Introduction). For
more specific ideas, see the [other tutorials and hints](../../Howto-Overview) as well
as the [Evennia Component overview](../../../Components/Components-Overview).
as the [Evennia Component overview](../../../Components/Components-Overview).

View file

@ -847,7 +847,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
def delete(self, *args, **kwargs):
"""
Deletes the account permanently.
Deletes the account persistently.
Notes:
`*args` and `**kwargs` are passed on to the base delete
@ -1196,7 +1196,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
self.locks.add(lockstring)
# The ooc account cmdset
self.cmdset.add_default(_CMDSET_ACCOUNT, permanent=True)
self.cmdset.add_default(_CMDSET_ACCOUNT, persistent=True)
def at_account_creation(self):
"""

View file

@ -177,7 +177,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
# merge-stack, every cmdset in the stack must have `duplicates` set explicitly.
duplicates = None
permanent = False
persistent = False
key_mergetypes = {}
errmessage = ""
# pre-store properties to duplicate straight off
@ -187,7 +187,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
"no_exits",
"no_objs",
"no_channels",
"permanent",
"persistent",
"mergetype",
"priority",
"duplicates",
@ -357,7 +357,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
commands (str): Representation of commands in Cmdset.
"""
perm = "perm" if self.permanent else "non-perm"
perm = "perm" if self.persistent else "non-perm"
options = ", ".join([
"{}:{}".format(opt, "T" if getattr(self, opt) else "F")
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")

View file

@ -305,7 +305,7 @@ class CmdSetHandler(object):
self.mergetype_stack = ["Union"]
# the subset of the cmdset_paths that are to be stored in the database
self.permanent_paths = [""]
self.persistent_paths = [""]
if init_true:
self.update(init_mode=True) # is then called from the object __init__.
@ -363,7 +363,7 @@ class CmdSetHandler(object):
Args:
init_mode (bool, optional): Used automatically right after
this handler was created; it imports all permanent cmdsets
this handler was created; it imports all persistent cmdsets
from the database.
Notes:
@ -378,7 +378,7 @@ class CmdSetHandler(object):
"""
if init_mode:
# reimport all permanent cmdsets
# reimport all persistent cmdsets
storage = self.obj.cmdset_storage
if storage:
self.cmdset_stack = []
@ -408,7 +408,7 @@ class CmdSetHandler(object):
if _IN_GAME_ERRORS:
self.obj.msg(err)
continue
cmdset.permanent = cmdset.key != "_CMDSET_ERROR"
cmdset.persistent = cmdset.key != "_CMDSET_ERROR"
self.cmdset_stack.append(cmdset)
# merge the stack into a new merged cmdset
@ -423,7 +423,7 @@ class CmdSetHandler(object):
self.mergetype_stack.append(new_current.actual_mergetype)
self.current = new_current
def add(self, cmdset, emit_to_obj=None, persistent=True, default_cmdset=False,
def add(self, cmdset, emit_to_obj=None, persistent=False, default_cmdset=False,
**kwargs):
"""
Add a cmdset to the handler, on top of the old ones, unless it
@ -465,7 +465,7 @@ class CmdSetHandler(object):
# this is (maybe) a python path. Try to import from cache.
cmdset = self._import_cmdset(cmdset)
if cmdset and cmdset.key != "_CMDSET_ERROR":
cmdset.permanent = persistent # TODO change on cmdset too
cmdset.persistent = persistent
if persistent and cmdset.key != "_CMDSET_ERROR":
# store the path permanently
storage = self.obj.cmdset_storage or [""]
@ -514,7 +514,7 @@ class CmdSetHandler(object):
# remove the default cmdset only
if self.cmdset_stack:
cmdset = self.cmdset_stack[0]
if cmdset.permanent:
if cmdset.persistent:
storage = self.obj.cmdset_storage or [""]
storage[0] = ""
self.obj.cmdset_storage = storage
@ -531,7 +531,7 @@ class CmdSetHandler(object):
if not cmdset:
# remove the last one in the stack
cmdset = self.cmdset_stack.pop()
if cmdset.permanent:
if cmdset.persistent:
storage = self.obj.cmdset_storage
storage.pop()
self.obj.cmdset_storage = storage
@ -548,12 +548,12 @@ class CmdSetHandler(object):
]
storage = []
if any(cset.permanent for cset in delcmdsets):
if any(cset.persistent for cset in delcmdsets):
# only hit database if there's need to
storage = self.obj.cmdset_storage
updated = False
for cset in delcmdsets:
if cset.permanent:
if cset.persistent:
try:
storage.remove(cset.path)
updated = True

View file

@ -162,7 +162,7 @@ def _menu_savefunc(caller, buf):
def _menu_quitfunc(caller):
caller.cmdset.add(
BuildingMenuCmdSet,
permanent=caller.ndb._building_menu and caller.ndb._building_menu.persistent or False,
persistent=caller.ndb._building_menu and caller.ndb._building_menu.persistent or False,
)
if caller.ndb._building_menu:
caller.ndb._building_menu.move(back=True)
@ -951,7 +951,7 @@ class BuildingMenu(object):
if caller.cmdset.has(BuildingMenuCmdSet):
caller.cmdset.remove(BuildingMenuCmdSet)
self.caller.cmdset.add(BuildingMenuCmdSet, permanent=self.persistent)
self.caller.cmdset.add(BuildingMenuCmdSet, persistent=self.persistent)
self.display()
def open_parent_menu(self):

View file

@ -50,7 +50,7 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
"total_achievements": 14,
}
self.cmdset.add(CmdSetEvScapeRoom, permanent=True)
self.cmdset.add(CmdSetEvScapeRoom, persistent=True)
self.log("Room created and log started.")

View file

@ -1550,7 +1550,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
self.db._recog_obj2regex = {}
self.db._recog_obj2recog = {}
self.cmdset.add(RPSystemCmdSet, permanent=True)
self.cmdset.add(RPSystemCmdSet, persistent=True)
# initializing sdesc
self.sdesc.add("A normal person")

View file

@ -130,4 +130,4 @@ class TalkingNPC(DefaultObject):
"This is called when object is first created."
self.db.desc = "This is a talkative NPC."
# assign the talk command to npc
self.cmdset.add_default(TalkingCmdSet, permanent=True)
self.cmdset.add_default(TalkingCmdSet, persistent=True)

View file

@ -112,7 +112,7 @@ class Mob(tut_objects.TutorialObject):
Called the first time the object is created.
We set up the base properties and flags here.
"""
self.cmdset.add(MobCmdSet, permanent=True)
self.cmdset.add(MobCmdSet, persistent=True)
# Main AI flags. We start in dead mode so we don't have to
# chase the mob around when building.
self.db.patrolling = True

View file

@ -129,7 +129,7 @@ class TutorialReadable(TutorialObject):
)
self.db.readable_text = "There is no text written on %s." % self.key
# define a command on the object.
self.cmdset.add_default(CmdSetReadable, permanent=True)
self.cmdset.add_default(CmdSetReadable, persistent=True)
# -------------------------------------------------------------
@ -195,7 +195,7 @@ class TutorialClimbable(TutorialObject):
def at_object_creation(self):
"""Called at initial creation only"""
self.cmdset.add_default(CmdSetClimbable, permanent=True)
self.cmdset.add_default(CmdSetClimbable, persistent=True)
# -------------------------------------------------------------
@ -343,7 +343,7 @@ class LightSource(TutorialObject):
# when created.
self.db.desc = "A splinter of wood with remnants of resin on it, enough for burning."
# add the Light command
self.cmdset.add_default(CmdSetLight, permanent=True)
self.cmdset.add_default(CmdSetLight, persistent=True)
def _burnout(self):
"""
@ -670,7 +670,7 @@ class CrumblingWall(TutorialObject, DefaultExit):
# exit_open is set to True.
self.locks.add("cmd:locattr(is_lit);traverse:objattr(exit_open)")
# set cmdset
self.cmdset.add(CmdSetCrumblingWall, permanent=True)
self.cmdset.add(CmdSetCrumblingWall, persistent=True)
def open_wall(self):
"""
@ -950,7 +950,7 @@ class TutorialWeapon(TutorialObject):
self.db.parry = 0.8 # parry chance
self.db.damage = 1.0
self.db.magic = False
self.cmdset.add_default(CmdSetWeapon, permanent=True)
self.cmdset.add_default(CmdSetWeapon, persistent=True)
def reset(self):
"""
@ -1148,7 +1148,7 @@ class TutorialWeaponRack(TutorialObject):
"""
called at creation
"""
self.cmdset.add_default(CmdSetWeaponRack, permanent=True)
self.cmdset.add_default(CmdSetWeaponRack, persistent=True)
self.db.rack_id = "weaponrack_1"
# these are prototype names from the prototype
# dictionary above.

View file

@ -429,7 +429,7 @@ class IntroRoom(TutorialRoom):
"This assigns the health Attribute to "
"the account."
)
self.cmdset.add(CmdSetEvenniaIntro, permanent=True)
self.cmdset.add(CmdSetEvenniaIntro, persistent=True)
def at_object_receive(self, character, source_location):
"""
@ -732,7 +732,7 @@ class BridgeRoom(WeatherRoom):
self.db.east_exit = "gate"
self.db.fall_exit = "cliffledge"
# add the cmdset on the room.
self.cmdset.add(BridgeCmdSet, permanent=True)
self.cmdset.add(BridgeCmdSet, persistent=True)
# since the default Character's at_look() will access the room's
# return_description (this skips the cmdset) when
# first entering it, we need to explicitly turn off the room
@ -957,7 +957,7 @@ class DarkRoom(TutorialRoom):
self.db.tutorial_info = "This is a room with custom command sets on itself."
# the room starts dark.
self.db.is_lit = False
self.cmdset.add(DarkCmdSet, permanent=True)
self.cmdset.add(DarkCmdSet, persistent=True)
def at_init(self):
"""
@ -1009,7 +1009,7 @@ class DarkRoom(TutorialRoom):
# noone is carrying light - darken the room
self.db.is_lit = False
self.locks.add("view:false()")
self.cmdset.add(DarkCmdSet, permanent=True)
self.cmdset.add(DarkCmdSet, persistent=True)
for char in (obj for obj in self.contents if obj.has_account):
if char.is_superuser:
char.msg("You are Superuser, so you are not affected by the dark state.")

View file

@ -1310,7 +1310,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
def at_object_delete(self):
"""
Called just before the database object is permanently
Called just before the database object is persistently
delete()d from the database. If this method returns False,
deletion is aborted.
@ -2261,7 +2261,7 @@ class DefaultCharacter(DefaultObject):
";".join(["get:false()", "call:false()"]) # noone can pick up the character
) # no commands can be called on character from outside
# add the default cmdset
self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
self.cmdset.add_default(settings.CMDSET_CHARACTER, persistent=True)
def at_after_move(self, source_location, **kwargs):
"""
@ -2711,7 +2711,7 @@ class DefaultExit(DefaultObject):
if "force_init" in kwargs or not self.cmdset.has_cmdset("ExitCmdSet", must_be_default=True):
# we are resetting, or no exit-cmdset was set. Create one dynamically.
self.cmdset.add_default(self.create_exit_cmdset(self), permanent=False)
self.cmdset.add_default(self.create_exit_cmdset(self), persistent=False)
def at_init(self):
"""

View file

@ -893,7 +893,7 @@ class EvEditor:
persistent = False
# Create the commands we need
caller.cmdset.add(EvEditorCmdSet, permanent=persistent)
caller.cmdset.add(EvEditorCmdSet, persistent=persistent)
# echo inserted text back to caller
self._echo_mode = True

View file

@ -665,7 +665,7 @@ class EvMenu:
menu_cmdset = EvMenuCmdSet()
menu_cmdset.mergetype = str(cmdset_mergetype).lower().capitalize() or "Replace"
menu_cmdset.priority = int(cmdset_priority)
self.caller.cmdset.add(menu_cmdset, permanent=persistent)
self.caller.cmdset.add(menu_cmdset, persistent=persistent)
reserved_startnode_kwargs = set(("nodename", "raw_string"))
startnode_kwargs = {}