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 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 # created previously: mygame/commands/mycommands.py
from commands import mycommands from commands import mycommands
class MyCmdSet(CmdSet): class MyCmdSet(CmdSet):
def at_cmdset_creation(self): def at_cmdset_creation(self):
""" """
The only thing this method should need The only thing this method should need
to do is to add commands to the set. to do is to add commands to the set.
""" """
self.add(mycommands.MyCommand1()) self.add(mycommands.MyCommand1())
self.add(mycommands.MyCommand2()) self.add(mycommands.MyCommand2())
self.add(mycommands.MyCommand3()) 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: from that CmdSet will be appended to this one as if you added them line by line:
```python ```python
def at_cmdset_creation(): def at_cmdset_creation():
... ...
self.add(AdditionalCmdSet) # adds all command from this set 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: 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 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 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 In the example above, a specific Cmdset class is removed. Calling `delete` without arguments will
remove the latest added cmdset. 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) @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. cmdset ends up in the merged cmdset. Same-key commands are merged by priority.
# Union # 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 - **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. 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 A1,A3,A5 + B1,B2,B4,B5 = A1,A5
- **Replace** - The commands of the higher-prio cmdset completely replaces the lower-priority - **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 sure we merge onto B. Setting E's priority to, say, -4 will make sure to merge it onto B and affect
it appropriately. it appropriately.
More advanced cmdset example: More advanced cmdset example:
```python ```python
from commands import mycommands from commands import mycommands
class MyCmdSet(CmdSet): class MyCmdSet(CmdSet):
key = "MyCmdSet" key = "MyCmdSet"
priority = 4 priority = 4
mergetype = "Replace" mergetype = "Replace"
key_mergetypes = {'MyOtherCmdSet':'Union'} key_mergetypes = {'MyOtherCmdSet':'Union'}
def at_cmdset_creation(self): def at_cmdset_creation(self):
""" """
The only thing this method should need The only thing this method should need
to do is to add commands to the set. to do is to add commands to the set.
""" """
self.add(mycommands.MyCommand1()) self.add(mycommands.MyCommand1())
self.add(mycommands.MyCommand2()) self.add(mycommands.MyCommand2())
self.add(mycommands.MyCommand3()) 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 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". 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): def at_object_creation(self):
"This is called only when object is first created" "This is called only when object is first created"
self.cmdset.add_default(default_cmds.CharacterCmdSet) 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.locks.add("puppet:all();call:false()")
self.db.desc = "This is a huge mech. It has missiles and stuff." 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 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. 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): class BlockingRoom(Room):
def at_object_creation(self): 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 # only share commands with players in the room that
# are NOT Builders or higher # are NOT Builders or higher
self.locks.add("call:not perm(Builders)") 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 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 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 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 for our front shop room
class NPCShop(DefaultRoom): class NPCShop(DefaultRoom):
def at_object_creation(self): 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.cmdset.add_default(ShopCmdSet)
self.db.storeroom = None 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 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 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 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 # Our own commands
In this lesson we'll learn how to create our own Evennia _Commands_. If you are new to Python you'll 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. 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. A Command is something that handles the input from a user and causes a result to happen.
An example is `look`, which examines your current location and tells how it looks like and An example is `look`, which examines your current location and tells how it looks like and
what is in it. what is in it.
```sidebar:: Commands are not typeclassed ```sidebar:: Commands are not typeclassed
If you just came from the previous lesson, you might want to know that Commands and 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 CommandSets are not `typeclassed`. That is, instances of them are not saved to the
database. They are "just" normal Python classes. 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- 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 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 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 combat, another for building etc. By default, Evennia groups all character-commands into one
big cmdset. big cmdset.
Command-Sets are then associated with objects, for example with your Character. Doing so makes the 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 in that cmdset available to the object. So, to summarize:
- Commands are classes - Commands are classes
- A group of Commands is stored in a CmdSet - A group of Commands is stored in a CmdSet
@ -45,25 +45,25 @@ from evennia import Command as BaseCommand
class Command(BaseCommand): class Command(BaseCommand):
""" """
(class docstring) (class docstring)
""" """
pass pass
# (lots of commented-out stuff) # (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. 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 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 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 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. 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 > 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 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 ```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 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. 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: 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 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 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 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 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. 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: Finally, let's add this command to ourselves so we can try it out. In-game you can experiment with `py` again:
> py self.cmdset.add("commands.mycommands.MyCmdSet") > py self.cmdset.add("commands.mycommands.MyCmdSet")
Now try Now try
> echo > echo
Command echo has no defined `func()` - showing on-command variables: 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 "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: 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 cmdname (<class 'str'>): echo
raw_cmdname (<class 'str'>): echo raw_cmdname (<class 'str'>): echo
cmdstring (<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, 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, 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, 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 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 session (<class 'evennia.server.serversession.ServerSession'>): Griatch(#1)@1:2:7:.:0:.:0:.:1
account (<class 'typeclasses.accounts.Account'>): Griatch(account 1) account (<class 'typeclasses.accounts.Account'>): Griatch(account 1)
raw_string (<class 'str'>): echo raw_string (<class 'str'>): echo
-------------------------------------------------- --------------------------------------------------
echo - Command variables from evennia: 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 command string given (self.cmdstring): echo
current cmdset (self.cmdset): ChannelCmdSet current cmdset (self.cmdset): ChannelCmdSet
These are all properties you can access with `.` on the Command instance, such as `.key`, `.args` and so on. 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 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: important ones we will make use of now are:
- `caller` - this is 'you', the person calling the command. - `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"`. that this would be `" foo bar"`.
- `obj` - this is object on which this Command (and CmdSet) "sits". So you, in this case. - `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: looks for to figure out what a Command actually does. Modify your `CmdEcho` class:
```python ```python
@ -165,47 +165,47 @@ looks for to figure out what a Command actually does. Modify your `CmdEcho` clas
class CmdEcho(Command): class CmdEcho(Command):
""" """
A simple echo command A simple echo command
Usage: Usage:
echo <something> echo <something>
""" """
key = "echo" key = "echo"
def func(self): 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 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 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 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 [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 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 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: try out the new version:
> reload > reload
> py self.cmdset.add("commands.mycommands.MyCmdSet") > py self.cmdset.add("commands.mycommands.MyCmdSet")
> echo > echo
Echo: '' Echo: ''
Try to pass an argument: Try to pass an argument:
> echo Woo Tang! > echo Woo Tang!
Echo: ' Woo Tang!' Echo: ' Woo Tang!'
Note that there is an extra space before `Woo!`. That is because self.args contains the _everything_ after 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! > echoWoo Tang!
Echo: 'Woo 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 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: it looks a bit weird for our echo example. Tweak the code:
```python ```python
# ... # ...
@ -213,15 +213,15 @@ it looks a bit weird for our echo example. Tweak the code:
class CmdEcho(Command): class CmdEcho(Command):
""" """
A simple echo command A simple echo command
Usage: Usage:
echo <something> echo <something>
""" """
key = "echo" key = "echo"
def func(self): 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 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. have any space in front of it.
> reload > reload
> py self.cmdset.add("commands.mycommands.MyCmdSet") > py self.cmdset.add("commands.mycommands.MyCmdSet")
> echo Woo Tang! > echo Woo Tang!
Echo: 'Woo Tang!' Echo: 'Woo Tang!'
Don't forget to look at the help for the echo command:
> help echo Don't forget to look at the help for the echo command:
You will get the docstring you put in your Command-class. > help echo
You will get the docstring you put in your Command-class.
### Making our cmdset persistent ### 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 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) > 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 Now you can `reload` as much as you want and your code changes will be available directly without
needing to re-add the MyCmdSet again. To remove the cmdset again, do needing to re-add the MyCmdSet again. To remove the cmdset again, do
> py self.cmdset.remove("commands.mycommands.MyCmdSet") > 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 ### Figuring out who to hit
Let's try something a little more exciting than just echo. Let's make a `hit` command, for punching 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> > hit <target>
You hit <target> with full force! You hit <target> with full force!
Not only that, we want the <target> to see Not only that, we want the <target> to see
You got hit by <hitter> with full force! 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 ```python
# ... # ...
@ -277,7 +277,7 @@ Still in `mygame/commands/mycommands.py`, add a new class, between `CmdEcho` and
class CmdHit(Command): class CmdHit(Command):
""" """
Hit a target. Hit a target.
Usage: Usage:
hit <target> hit <target>
@ -288,11 +288,11 @@ class CmdHit(Command):
args = self.args.strip() args = self.args.strip()
if not args: if not args:
self.caller.msg("Who do you want to hit?") self.caller.msg("Who do you want to hit?")
return return
target = self.caller.search(args) target = self.caller.search(args)
if not target: if not target:
return return
self.caller.msg(f"You hit {target.key} with full force!") self.caller.msg(f"You hit {target.key} with full force!")
target.msg(f"You got hit by {self.caller.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. - **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. - **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 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 `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 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. have the whitespace and is not the same as `args` in this example.
```sidebar:: if-statements ```sidebar:: if-statements
The full form of the if statement is The full form of the if statement is
if condition: if condition:
... ...
elif othercondition: elif othercondition:
... ...
else: else:
... ...
There can be any number of `elifs` to mark when different branches of the code should run. If 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 `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. 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 - **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 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`. - `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. - `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 `''''''` - The empty string `""` or `''` or `""""""` or `''''''`
- Empty _iterables_ we haven't seen yet, like empty lists `[]`, empty tuples `()` and empty dicts `{}`. - 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 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. - **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. - **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. - **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. - **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 ```python
# ... # ...
@ -357,17 +357,17 @@ class MyCmdSet(CmdSet):
```sidebar:: Errors in your code ```sidebar:: Errors in your code
With longer code snippets to try, it gets more and more likely you'll 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 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). 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 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 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. 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 hit
Who do you want to hit? Who do you want to hit?
hit me 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 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): you could try to hit it (if you dare):
hit smaug hit smaug
You hit Smaug with full force! You hit Smaug with full force!
You won't see the second string. Only Smaug sees that (and is not amused). 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 ## Summary
In this lesson we learned how to create our own Command, add it to a CmdSet and then to ourselves. 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. We also upset a dragon.
In the next lesson we'll learn how to hit Smaug with different weapons. We'll also 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. get into how we replace and extend Evennia's default Commands.

View file

@ -1,25 +1,25 @@
# More about Commands # More about Commands
In this lesson we learn some basics about parsing the input of Commands. We will 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. 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 In the last lesson we made a `hit` Command and hit a dragon with it. You should have the code
from that still around. 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>] hit <target> [[with] <weapon>]
That is, we want to support all of these forms That is, we want to support all of these forms
hit target hit target
hit target weapon hit target weapon
hit target with 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 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 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`: a little, in a new method `parse`:
@ -29,19 +29,19 @@ a little, in a new method `parse`:
class CmdHit(Command): class CmdHit(Command):
""" """
Hit a target. Hit a target.
Usage: Usage:
hit <target> hit <target>
""" """
key = "hit" key = "hit"
def parse(self): def parse(self):
self.args = self.args.strip() self.args = self.args.strip()
target, *weapon = self.args.split(" with ", 1) target, *weapon = self.args.split(" with ", 1)
if not weapon: if not weapon:
target, *weapon = target.split(" ", 1) target, *weapon = target.split(" ", 1)
self.target = target.strip() self.target = target.strip()
if weapon: if weapon:
self.weapon = weapon.strip() self.weapon = weapon.strip()
else: else:
@ -50,162 +50,162 @@ class CmdHit(Command):
def func(self): def func(self):
if not self.args: if not self.args:
self.caller.msg("Who do you want to hit?") self.caller.msg("Who do you want to hit?")
return return
# get the target for the hit # get the target for the hit
target = self.caller.search(self.target) target = self.caller.search(self.target)
if not target: if not target:
return return
# get and handle the weapon # get and handle the weapon
weapon = None weapon = None
if self.weapon: if self.weapon:
weapon = self.caller.search(self.weapon) weapon = self.caller.search(self.weapon)
if weapon: if weapon:
weaponstr = f"{weapon.key}" weaponstr = f"{weapon.key}"
else: else:
weaponstr = "bare fists" 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}!") 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 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 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. 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 `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 `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 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. - **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 `.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: 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"]`. 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"]` 2. `hit smaug sword` gives `["smaug sword"]`
3. `hit smaug with 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 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. "soaks" up everything left over.
1. `target` becomes `"smaug"` and `weapon` becomes `[]` 1. `target` becomes `"smaug"` and `weapon` becomes `[]`
2. `target` becomes `"smaug sword"` and `weapon` becomes `[]` 2. `target` becomes `"smaug sword"` and `weapon` becomes `[]`
3. `target` becomes `"smaug"` and `weapon` becomes `sword` 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 - **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` 1. `target` is simply `smaug`
2. `target` is `smaug sword` 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: result back with `target, *weapon =`. The result will be one of the following:
1. `target` remains `smaug` and `weapon` remains `[]` 1. `target` remains `smaug` and `weapon` remains `[]`
2. `target` becomes `smaug` and `weapon` becomes `sword` 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 - **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 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). 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 Now onto the `func` method. The main difference is we now have `self.target` and `self.weapon` available for
convenient use. convenient use.
- **Lines 29 and 35** - We make use of the previously parsed search terms for the target and weapon to find the - **Lines 29 and 35** - We make use of the previously parsed search terms for the target and weapon to find the
respective resource. 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 - **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. 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 text.
Let's try it out! Let's try it out!
> reload > reload
> hit smaug with sword > hit smaug with sword
Could not find 'sword'. Could not find 'sword'.
You hit smaug with bare fists! 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 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.
Since we didn't specify `/drop`, the sword will end up in our inventory and can seen with the `i` or This won't do. Let's make ourselves a sword.
`inventory` command. The `.search` helper will still find it there. There is no need to reload to see this
> 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). change (no code changed, only stuff in the database).
> hit smaug with sword > hit smaug with sword
You 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 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) 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) 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. 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. 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 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. add `MyCmdSet` to it. This actually adds both `hit` and `echo` to the sword, which is fine.
Let's try to swing it! Let's try to swing it!
> hit > hit
More than one match for 'hit' (please narrow target): More than one match for 'hit' (please narrow target):
hit-1 (sword #11) hit-1 (sword #11)
hit-2 hit-2
```sidebar:: Multi-matches ```sidebar:: Multi-matches
Some game engines will just pick the first hit when finding more than one. 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 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 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 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? `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 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 (_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 the sword. The other one is from adding `MyCmdSet` to ourself earlier. It's easy enough to tell Evennia which
one you meant: one you meant:
> hit-1 > hit-1
Who do you want to hit? Who do you want to hit?
> hit-2 > hit-2
Who do you want to hit? 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: 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") > self.cmdset.remove("commands.mycommands.MyCmdSet")
> hit > hit
Who do you want to hit? Who do you want to hit?
Now try this: Now try this:
> tunnel n = kitchen > tunnel n = kitchen
> n > n
> drop sword > drop sword
> s > s
> hit > hit
Command 'hit' is not available. Maybe you meant ... Command 'hit' is not available. Maybe you meant ...
> n > n
> hit > hit
Who do you want to 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 now only available if you hold or are in the same room as the sword.
### You need to hold 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 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 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 ```sidebar:: Locks
Evennia Locks are defined as a mini-language defined in `lockstrings`. The lockstring 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()") > 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 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). 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 For locks to work, you cannot be _superuser_, since the superuser passes all locks. You need to `quell` yourself
first: first:
```sidebar:: quell/unquell ```sidebar:: quell/unquell
Quelling allows you as a developer to take on the role of players with less 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. superuser has a little `too` much power sometimes.
Use `unquell` to get back to your normal self. Use `unquell` to get back to your normal self.
``` ```
> quell > quell
If the sword lies on the ground, try If the sword lies on the ground, try
> hit > hit
Command 'hit' is not available. .. Command 'hit' is not available. ..
> get sword > get sword
> hit > hit
> Who do you want to 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. 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: We can do that in two ways:
delete sword delete sword
or
py self.search("sword").delete() or
py self.search("sword").delete()
## Adding the Command to a default Cmdset ## 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 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`. 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. 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. 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`. - 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` - 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 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 ```python
""" """
@ -321,19 +321,19 @@ class SessionCmdSet(default_cmds.SessionCmdSet):
``` ```
```sidebar:: super() ```sidebar:: super()
The `super()` function refers to the parent of the current class and is commonly 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 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 (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. 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 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 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. 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`: 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 > 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 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: 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) 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. this is practical. A Command can be a part of any number of different CmdSets.
### Removing Commands ### Removing Commands
To remove your custom commands again, you of course just delete the change you did to 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? `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 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 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. We happen to know this can be found as `default_cmds.CmdGet`.
```python ```python
# ... # ...
from commands import mycommands from commands import mycommands
@ -413,16 +413,16 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
# ... # ...
``` ```
> reload > reload
> get > get
Command 'get' is not available ... Command 'get' is not available ...
## Replace a default command ## 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 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. 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 how to _override_ a parent class. Open `mygame/commands/mycommands.py` and lets override
that `CmdGet` command. that `CmdGet` command.
@ -430,7 +430,7 @@ that `CmdGet` command.
# up top, by the other imports # up top, by the other imports
from evennia import default_cmds from evennia import default_cmds
# somewhere below # somewhere below
class MyCmdGet(default_cmds.CmdGet): class MyCmdGet(default_cmds.CmdGet):
def func(self): def func(self):
@ -440,20 +440,20 @@ class MyCmdGet(default_cmds.CmdGet):
``` ```
- **Line2**: We import `default_cmds` so we can get the parent class. - **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 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. 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, 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 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. 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 - **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 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. 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 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 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. 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: `mygame/commands/default_cmdsets.py` again:
```python ```python
@ -476,22 +476,22 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
``` ```
```sidebar:: Another way ```sidebar:: Another way
Instead of adding `MyCmdGet` explicitly in default_cmdset.py, Instead of adding `MyCmdGet` explicitly in default_cmdset.py,
you could also add it to `mycommands.MyCmdSet` and let it be you could also add it to `mycommands.MyCmdSet` and let it be
added automatically for you. added automatically for you.
``` ```
> reload > reload
> get > get
Get What? 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 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). there's some room for improvement there).
## Summary ## Summary
In this lesson we got into some more advanced string formatting - many of those tricks will help you a lot in 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 the future! We also made a functional sword. Finally we got into how to add to, extend and replace a default
command on ourselves. command on ourselves.

View file

@ -56,8 +56,8 @@ class Character(DefaultCharacter):
[...] [...]
""" """
def at_object_creation(self): def at_object_creation(self):
"This is called when object is first created, only." "This is called when object is first created, only."
self.db.power = 1 self.db.power = 1
self.db.combat_score = 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: What we need are the following:
- One character generation [Command](../../../Components/Commands) to set the "Power" on the `Character`. - 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 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. - A custom `ChargenRoom` type that makes this set of commands available to players in such rooms.
- One such room to test things in. - One such room to test things in.
### The +setpower command ### 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, `MuxCommand` class offers some extra features like stripping whitespace that may be useful - if so,
just import from that instead. 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 ```python
# end of command.py # end of command.py
@ -124,13 +124,13 @@ class CmdSetPower(Command):
""" """
set the power of a character set the power of a character
Usage: Usage:
+setpower <1-10> +setpower <1-10>
This sets the power of the current character. This can only be This sets the power of the current character. This can only be
used during character generation. used during character generation.
""" """
key = "+setpower" key = "+setpower"
help_category = "mush" help_category = "mush"
@ -138,10 +138,10 @@ class CmdSetPower(Command):
"This performs the actual command" "This performs the actual command"
errmsg = "You must supply a number between 1 and 10." errmsg = "You must supply a number between 1 and 10."
if not self.args: if not self.args:
self.caller.msg(errmsg) self.caller.msg(errmsg)
return return
try: try:
power = int(self.args) power = int(self.args)
except ValueError: except ValueError:
self.caller.msg(errmsg) self.caller.msg(errmsg)
return return
@ -180,7 +180,7 @@ class ChargenCmdset(CmdSet):
key = "Chargen" key = "Chargen"
def at_cmdset_creation(self): def at_cmdset_creation(self):
"This is called at initialization" "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 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 We will create a simple Room typeclass to act as a template for all our Chargen areas. Edit
`mygame/typeclasses/rooms.py` next: `mygame/typeclasses/rooms.py` next:
```python ```python
from commands.default_cmdsets import ChargenCmdset from commands.default_cmdsets import ChargenCmdset
# ... # ...
# down at the end of rooms.py # down at the end of rooms.py
class ChargenRoom(Room): class ChargenRoom(Room):
@ -206,10 +206,10 @@ class ChargenRoom(Room):
""" """
def at_object_creation(self): def at_object_creation(self):
"this is called only at first creation" "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. 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 more information about [Command Sets](../../../Components/Command-Sets) and [Commands](../../../Components/Commands), see the respective
links. 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 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. an exit back here named `finish`. If you see errors at this stage, you must fix them in your code.
`@reload` `@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 chargen
@ -263,19 +263,19 @@ set during Character generation:
> +attack > +attack
You +attack with a combat score of 12! 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 import random
# ... # ...
class CmdAttack(Command): class CmdAttack(Command):
""" """
issues an attack issues an attack
Usage: Usage:
+attack +attack
This will calculate a new combat score based on your Power. This will calculate a new combat score based on your Power.
Your combat score is visible to everyone in the same location. Your combat score is visible to everyone in the same location.
@ -288,8 +288,8 @@ class CmdAttack(Command):
caller = self.caller caller = self.caller
power = caller.db.power power = caller.db.power
if not power: if not power:
# this can happen if caller is not of # this can happen if caller is not of
# our custom Character typeclass # our custom Character typeclass
power = 1 power = 1
combat_score = random.randint(1, 10 * power) combat_score = random.randint(1, 10 * power)
caller.db.combat_score = combat_score caller.db.combat_score = combat_score
@ -297,10 +297,10 @@ class CmdAttack(Command):
# announce # announce
message = "%s +attack%s with a combat score of %s!" message = "%s +attack%s with a combat score of %s!"
caller.msg(message % ("You", "", combat_score)) caller.msg(message % ("You", "", combat_score))
caller.location.msg_contents(message % caller.location.msg_contents(message %
(caller.key, "s", combat_score), (caller.key, "s", combat_score),
exclude=caller) exclude=caller)
``` ```
What we do here is simply to generate a "combat score" using Python's inbuilt `random.randint()` 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. function. We then store that and echo the result to everyone involved.
@ -349,8 +349,8 @@ class Character(DefaultCharacter):
[...] [...]
""" """
def at_object_creation(self): def at_object_creation(self):
"This is called when object is first created, only." "This is called when object is first created, only."
self.db.power = 1 self.db.power = 1
self.db.combat_score = 1 self.db.combat_score = 1
def return_appearance(self, looker): 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 ### 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 > +createnpc Anna
You created the NPC 'Anna'. You created the NPC 'Anna'.
At the end of `command.py`, create our new command: At the end of `command.py`, create our new command:
```python ```python
from evennia import create_object from evennia import create_object
class CmdCreateNPC(Command): class CmdCreateNPC(Command):
""" """
create a new npc create a new npc
@ -413,12 +413,12 @@ class CmdCreateNPC(Command):
+createNPC <name> +createNPC <name>
Creates a new, named NPC. The NPC will start with a Power of 1. Creates a new, named NPC. The NPC will start with a Power of 1.
""" """
key = "+createnpc" key = "+createnpc"
aliases = ["+createNPC"] aliases = ["+createNPC"]
locks = "call:not perm(nonpcs)" locks = "call:not perm(nonpcs)"
help_category = "mush" help_category = "mush"
def func(self): def func(self):
"creates the object and names it" "creates the object and names it"
caller = self.caller caller = self.caller
@ -432,15 +432,15 @@ class CmdCreateNPC(Command):
# make name always start with capital letter # make name always start with capital letter
name = self.args.strip().capitalize() name = self.args.strip().capitalize()
# create npc in caller's location # create npc in caller's location
npc = create_object("characters.Character", npc = create_object("characters.Character",
key=name, key=name,
location=caller.location, location=caller.location,
locks="edit:id(%i) and perm(Builders);call:false()" % caller.id) locks="edit:id(%i) and perm(Builders);call:false()" % caller.id)
# announce # announce
message = "%s created the NPC '%s'." message = "%s created the NPC '%s'."
caller.msg(message % ("You", name)) caller.msg(message % ("You", name))
caller.location.msg_contents(message % (caller.key, name), caller.location.msg_contents(message % (caller.key, name),
exclude=caller) exclude=caller)
``` ```
Here we define a `+createnpc` (`+createNPC` works too) that is callable by everyone *not* having the 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 `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` command.
> +editNPC Anna/power = 10 > +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 ```python
class CmdEditNPC(Command): class CmdEditNPC(Command):
""" """
edit an existing NPC edit an existing NPC
Usage: Usage:
+editnpc <name>[/<attribute> [= value]] +editnpc <name>[/<attribute> [= value]]
Examples: Examples:
+editnpc mynpc/power = 5 +editnpc mynpc/power = 5
+editnpc mynpc/power - displays power value +editnpc mynpc/power - displays power value
+editnpc mynpc - shows all editable +editnpc mynpc - shows all editable
attributes and values 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. permission to edit the NPC to use this.
""" """
key = "+editnpc" key = "+editnpc"
aliases = ["+editNPC"] aliases = ["+editNPC"]
locks = "cmd:not perm(nonpcs)" locks = "cmd:not perm(nonpcs)"
help_category = "mush" help_category = "mush"
def parse(self): def parse(self):
"We need to do some parsing here" "We need to do some parsing here"
args = self.args args = self.args
propname, propval = None, None propname, propval = None, None
if "=" in args: if "=" in args:
args, propval = [part.strip() for part in args.rsplit("=", 1)] args, propval = [part.strip() for part in args.rsplit("=", 1)]
if "/" in args: if "/" in args:
args, propname = [part.strip() for part in args.rsplit("/", 1)] args, propname = [part.strip() for part in args.rsplit("/", 1)]
# store, so we can access it below in func() # store, so we can access it below in func()
@ -519,38 +519,38 @@ class CmdEditNPC(Command):
"do the editing" "do the editing"
allowed_propnames = ("power", "attribute1", "attribute2") allowed_propnames = ("power", "attribute1", "attribute2")
caller = self.caller caller = self.caller
if not self.args or not self.name: if not self.args or not self.name:
caller.msg("Usage: +editnpc name[/propname][=propval]") caller.msg("Usage: +editnpc name[/propname][=propval]")
return return
npc = caller.search(self.name) npc = caller.search(self.name)
if not npc: if not npc:
return return
if not npc.access(caller, "edit"): if not npc.access(caller, "edit"):
caller.msg("You cannot change this NPC.") caller.msg("You cannot change this NPC.")
return return
if not self.propname: if not self.propname:
# this means we just list the values # this means we just list the values
output = "Properties of %s:" % npc.key output = "Properties of %s:" % npc.key
for propname in allowed_propnames: for propname in allowed_propnames:
propvalue = npc.attributes.get(propname, default="N/A") propvalue = npc.attributes.get(propname, default="N/A")
output += "\n %s = %s" % (propname, propvalue) output += "\n %s = %s" % (propname, propvalue)
caller.msg(output) caller.msg(output)
elif self.propname not in allowed_propnames: elif self.propname not in allowed_propnames:
caller.msg("You may only change %s." % caller.msg("You may only change %s." %
", ".join(allowed_propnames)) ", ".join(allowed_propnames))
elif self.propval: elif self.propval:
# assigning a new propvalue # assigning a new propvalue
# in this example, the properties are all integers... # in this example, the properties are all integers...
intpropval = int(self.propval) intpropval = int(self.propval)
npc.attributes.add(self.propname, intpropval) npc.attributes.add(self.propname, intpropval)
caller.msg("Set %s's property '%s' to %s" % caller.msg("Set %s's property '%s' to %s" %
(npc.key, self.propname, self.propval)) (npc.key, self.propname, self.propval))
else: else:
# propname set, but not propval - show current value # propname set, but not propval - show current value
caller.msg("%s has property %s = %s" % caller.msg("%s has property %s = %s" %
(npc.key, self.propname, (npc.key, self.propname,
npc.attributes.get(self.propname, default="N/A"))) 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 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. 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 _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 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 controls an NPC
Usage: Usage:
+npc <name> = <command> +npc <name> = <command>
This causes the npc to perform a command as itself. It will do so 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" key = "+npc"
locks = "call:not perm(nonpcs)" locks = "call:not perm(nonpcs)"
@ -601,7 +601,7 @@ class CmdNPC(Command):
"Simple split of the = sign" "Simple split of the = sign"
name, cmdname = None, None name, cmdname = None, None
if "=" in self.args: if "=" in self.args:
name, cmdname = [part.strip() name, cmdname = [part.strip()
for part in self.args.rsplit("=", 1)] for part in self.args.rsplit("=", 1)]
self.name, self.cmdname = name, cmdname self.name, self.cmdname = name, cmdname
@ -611,7 +611,7 @@ class CmdNPC(Command):
if not self.cmdname: if not self.cmdname:
caller.msg("Usage: +npc <name> = <command>") caller.msg("Usage: +npc <name> = <command>")
return return
npc = caller.search(self.name) npc = caller.search(self.name)
if not npc: if not npc:
return return
if not npc.access(caller, "edit"): 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 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 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): def delete(self, *args, **kwargs):
""" """
Deletes the account permanently. Deletes the account persistently.
Notes: Notes:
`*args` and `**kwargs` are passed on to the base delete `*args` and `**kwargs` are passed on to the base delete
@ -1196,7 +1196,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
self.locks.add(lockstring) self.locks.add(lockstring)
# The ooc account cmdset # 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): 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. # merge-stack, every cmdset in the stack must have `duplicates` set explicitly.
duplicates = None duplicates = None
permanent = False persistent = False
key_mergetypes = {} key_mergetypes = {}
errmessage = "" errmessage = ""
# pre-store properties to duplicate straight off # pre-store properties to duplicate straight off
@ -187,7 +187,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
"no_exits", "no_exits",
"no_objs", "no_objs",
"no_channels", "no_channels",
"permanent", "persistent",
"mergetype", "mergetype",
"priority", "priority",
"duplicates", "duplicates",
@ -357,7 +357,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
commands (str): Representation of commands in Cmdset. 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([ options = ", ".join([
"{}:{}".format(opt, "T" if getattr(self, opt) else "F") "{}:{}".format(opt, "T" if getattr(self, opt) else "F")
for opt in ("no_exits", "no_objs", "no_channels", "duplicates") for opt in ("no_exits", "no_objs", "no_channels", "duplicates")

View file

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

View file

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

View file

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

View file

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

View file

@ -130,4 +130,4 @@ class TalkingNPC(DefaultObject):
"This is called when object is first created." "This is called when object is first created."
self.db.desc = "This is a talkative NPC." self.db.desc = "This is a talkative NPC."
# assign the talk command to 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. Called the first time the object is created.
We set up the base properties and flags here. 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 # Main AI flags. We start in dead mode so we don't have to
# chase the mob around when building. # chase the mob around when building.
self.db.patrolling = True 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 self.db.readable_text = "There is no text written on %s." % self.key
# define a command on the object. # 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): def at_object_creation(self):
"""Called at initial creation only""" """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. # when created.
self.db.desc = "A splinter of wood with remnants of resin on it, enough for burning." self.db.desc = "A splinter of wood with remnants of resin on it, enough for burning."
# add the Light command # add the Light command
self.cmdset.add_default(CmdSetLight, permanent=True) self.cmdset.add_default(CmdSetLight, persistent=True)
def _burnout(self): def _burnout(self):
""" """
@ -670,7 +670,7 @@ class CrumblingWall(TutorialObject, DefaultExit):
# exit_open is set to True. # exit_open is set to True.
self.locks.add("cmd:locattr(is_lit);traverse:objattr(exit_open)") self.locks.add("cmd:locattr(is_lit);traverse:objattr(exit_open)")
# set cmdset # set cmdset
self.cmdset.add(CmdSetCrumblingWall, permanent=True) self.cmdset.add(CmdSetCrumblingWall, persistent=True)
def open_wall(self): def open_wall(self):
""" """
@ -950,7 +950,7 @@ class TutorialWeapon(TutorialObject):
self.db.parry = 0.8 # parry chance self.db.parry = 0.8 # parry chance
self.db.damage = 1.0 self.db.damage = 1.0
self.db.magic = False self.db.magic = False
self.cmdset.add_default(CmdSetWeapon, permanent=True) self.cmdset.add_default(CmdSetWeapon, persistent=True)
def reset(self): def reset(self):
""" """
@ -1148,7 +1148,7 @@ class TutorialWeaponRack(TutorialObject):
""" """
called at creation called at creation
""" """
self.cmdset.add_default(CmdSetWeaponRack, permanent=True) self.cmdset.add_default(CmdSetWeaponRack, persistent=True)
self.db.rack_id = "weaponrack_1" self.db.rack_id = "weaponrack_1"
# these are prototype names from the prototype # these are prototype names from the prototype
# dictionary above. # dictionary above.

View file

@ -429,7 +429,7 @@ class IntroRoom(TutorialRoom):
"This assigns the health Attribute to " "This assigns the health Attribute to "
"the account." "the account."
) )
self.cmdset.add(CmdSetEvenniaIntro, permanent=True) self.cmdset.add(CmdSetEvenniaIntro, persistent=True)
def at_object_receive(self, character, source_location): def at_object_receive(self, character, source_location):
""" """
@ -732,7 +732,7 @@ class BridgeRoom(WeatherRoom):
self.db.east_exit = "gate" self.db.east_exit = "gate"
self.db.fall_exit = "cliffledge" self.db.fall_exit = "cliffledge"
# add the cmdset on the room. # 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 # since the default Character's at_look() will access the room's
# return_description (this skips the cmdset) when # return_description (this skips the cmdset) when
# first entering it, we need to explicitly turn off the room # 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." self.db.tutorial_info = "This is a room with custom command sets on itself."
# the room starts dark. # the room starts dark.
self.db.is_lit = False self.db.is_lit = False
self.cmdset.add(DarkCmdSet, permanent=True) self.cmdset.add(DarkCmdSet, persistent=True)
def at_init(self): def at_init(self):
""" """
@ -1009,7 +1009,7 @@ class DarkRoom(TutorialRoom):
# noone is carrying light - darken the room # noone is carrying light - darken the room
self.db.is_lit = False self.db.is_lit = False
self.locks.add("view: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): for char in (obj for obj in self.contents if obj.has_account):
if char.is_superuser: if char.is_superuser:
char.msg("You are Superuser, so you are not affected by the dark state.") 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): 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, delete()d from the database. If this method returns False,
deletion is aborted. deletion is aborted.
@ -2261,7 +2261,7 @@ class DefaultCharacter(DefaultObject):
";".join(["get:false()", "call:false()"]) # noone can pick up the character ";".join(["get:false()", "call:false()"]) # noone can pick up the character
) # no commands can be called on character from outside ) # no commands can be called on character from outside
# add the default cmdset # 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): 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): 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. # 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): def at_init(self):
""" """

View file

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

View file

@ -665,7 +665,7 @@ class EvMenu:
menu_cmdset = EvMenuCmdSet() menu_cmdset = EvMenuCmdSet()
menu_cmdset.mergetype = str(cmdset_mergetype).lower().capitalize() or "Replace" menu_cmdset.mergetype = str(cmdset_mergetype).lower().capitalize() or "Replace"
menu_cmdset.priority = int(cmdset_priority) 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")) reserved_startnode_kwargs = set(("nodename", "raw_string"))
startnode_kwargs = {} startnode_kwargs = {}