Unless you are dealing with a relatively simple dynamic menu, defining menus with lambda’s is
probably more work than it’s worth: You can create dynamic menus by instead making each node
-function more clever. See the NPC shop tutorial for an example of this.
+function more clever. See the NPC shop tutorial for an example of this.
diff --git a/docs/1.0-dev/Contribs/Contrib-Cooldowns.html b/docs/1.0-dev/Contribs/Contrib-Cooldowns.html
index 80f6373a7d..e7add3bfac 100644
--- a/docs/1.0-dev/Contribs/Contrib-Cooldowns.html
+++ b/docs/1.0-dev/Contribs/Contrib-Cooldowns.html
@@ -117,8 +117,7 @@ asynchronous timer that you can query to see if a certain time has yet passed.
state. They do not fire callbacks, so are not a good fit for use cases
where something needs to happen on a specific schedule (use delay or
a TickerHandler for that instead).
-
Arx - After the Reckoning is a big and very popular
-Evennia-based game. Arx is heavily roleplaying-centric, relying on game
-masters to drive the story. Technically it’s maybe best described as “a MUSH, but with more coded
-systems”. In August of 2018, the game’s developer, Tehom, generously released the source code of
-Arx on github. This is a treasure-trove for developers wanting
-to pick ideas or even get a starting game to build on.
-
-
These instructions are based on the Arx-code released as of Aug 12, 2018. They will probably
-not work 100% out of the box anymore. Report any differences and changes needed.
-
+
+
Warning
+
Arxcode is separately maintained.
+
While Arxcode uses Evennia, it is not part of Evennia itself; we include this documentation only as a service to users. Also, while Arxcode is still actively maintained (2022), these instructions are based on the Arx-code released as of Aug 12, 2018. They will probably not work 100% out of the box anymore.
Arx - After the Reckoning is a big and very popular Evennia-based game. Arx is heavily roleplaying-centric, relying on game masters to drive the story. Technically it’s maybe best described as “a MUSH, but with more coded systems”. In August of 2018, the game’s developer, Tehom, generously released the source code of Arx on github. This is a treasure-trove for developers wanting to pick ideas or even get a starting game to build on.
It’s not too hard to run Arx from the sources (of course you’ll start with an empty database) but
since part of Arx has grown organically, it doesn’t follow standard Evennia paradigms everywhere.
-This page covers one take on installing and setting things up while making your new Arx-based game
-better match with the vanilla Evennia install.
+This page covers one take on installing and setting things up while making your new Arx-based game better match with the vanilla Evennia install.
Firstly, set aside a folder/directory on your drive for everything to follow.
-
You need to start by installing Evennia by following most of the
-Git-installation instructions for your OS. The difference is that you
-need to gitclonehttps://github.com/TehomCD/evennia.git instead of Evennia’s repo because Arx
-uses TehomCD’s older Evennia 0.8 fork, notably still using
-Python2. This detail is important if referring to newer Evennia documentation.
-
If you are new to Evennia it’s highly recommended that you run through the normal install
-instructions in full - including initializing and starting a new empty game and connecting to it.
+
You need to start by installing Evennia by following most of the Git-installation instructions for your OS. The difference is that instead of cloning from upstream Evennia, you should do
This is because Arx uses TehomCD’s older Evennia 0.8 fork, notably still using Python2. This detail is important if referring to newer Evennia documentation.
+
If you are new to Evennia it’s highly recommended that you run through the normal install instructions in full - including initializing and starting a new empty game and connecting to it.
That way you can be sure Evennia works correctly as a baseline.
-
After installing you should have a virtualenv running and you should have the following file
-structure in your set-aside folder:
+
After installing you should have a virtualenv running and you should have the following file structure in your set-aside folder:
muddev/vienv/evennia/
@@ -161,8 +155,7 @@ to compare to.
A new folder myarx should appear next to the ones you already had. You could rename this to
something else if you want.
-
cd into myarx. If you wonder about the structure of the game dir, you can
-read more about it here.
+
cd into myarx. If you wonder about the structure of the game dir, you can read more about it here.
Arx has split evennia’s normal settings into base_settings.py and production_settings.py. It
@@ -184,27 +177,18 @@ way but we’ll remove the secret-handling and replace it with the normal Evenni
-
Note: Indents and capitalization matter in Python. Make indents 4 spaces (not tabs) for your own
-sanity. If you want a starter on Python in Evennia, [you can look here](Python-basic-
-introduction).
+
Note: Indents and capitalization matter in Python. Make indents 4 spaces (not tabs) for your own sanity. If you want a starter on Python in Evennia, [you can look here](Beginner-Tutorial-Python-basic- introduction).
-
This will import Arx’ base settings and override them with the Evennia-default telnet port and give
-the game a name. The slogan changes the sub-text shown under the name of your game in the website
-header. You can tweak these to your own liking later.
+
This will import Arx’ base settings and override them with the Evennia-default telnet port and give the game a name. The slogan changes the sub-text shown under the name of your game in the website header. You can tweak these to your own liking later.
Next, create a new, empty file secret_settings.py in the same location as the settings.py file.
This can just contain the following:
Replace the long random string with random ASCII characters of your own. The secret key should not
-be shared.
-
Next, open myarx/server/conf/base_settings.py in your text editor. We want to remove/comment out
-all mentions of the decouple package, which Evennia doesn’t use (we use private_settings.py to
-hide away settings that should not be shared).
-
Comment out fromdecoupleimportconfig by adding a # to the start of the line: #fromdecoupleimportconfig. Then search for config( in the file and comment out all lines where this is used.
-Many of these are specific to the server environment where the original Arx runs, so is not that
-relevant to us.
+
Replace the long random string with random ASCII characters of your own. The secret key should not be shared.
+
Next, open myarx/server/conf/base_settings.py in your text editor. We want to remove/comment out all mentions of the decouple package, which Evennia doesn’t use (we use private_settings.py to hide away settings that should not be shared).
+
Comment out fromdecoupleimportconfig by adding a # to the start of the line: #fromdecoupleimportconfig. Then search for config( in the file and comment out all lines where this is used. Many of these are specific to the server environment where the original Arx runs, so is not that relevant to us.
If all goes well Evennia will now start up, running Arx! You can connect to it on localhost (or
-127.0.0.1 if your platform doesn’t alias localhost), port 4000 using a Telnet client.
-Alternatively, you can use your web browser to browse to http://localhost:4001 to see the game’s
-website and get to the web client.
+
If all goes well Evennia will now start up, running Arx! You can connect to it on localhost (or 127.0.0.1 if your platform doesn’t alias localhost), port 4000 using a Telnet client. Alternatively, you can use your web browser to browse to http://localhost:4001 to see the game’s website and get to the web client.
When you log in you’ll get the standard Evennia greeting (since the database is empty), but you can
try help to see that it’s indeed Arx that is running.
@@ -261,86 +242,89 @@ run steps 7-8 and 10 to create and connect to your in-came Character.
Navigate to the Accounts section.
Add a new Account named for the new staffer. Use a place holder password and dummy e-mail
address.
-
Flag account as Staff and apply the Admin permission group (This assumes you have already set
-up an Admin Group in Django).
+
Flag account as Staff and apply the Admin permission group (This assumes you have already set up an Admin Group in Django).
Add Tags named player and developer.
-
Log into the game using the web client (or a third-party telnet client) using your superuser
-account. Move to where you want the new staffer character to appear.
-
In the game client, run @create/drop<staffername>:typeclasses.characters.Character, where
-<staffername> is usually the same name you used for the Staffer account you created in the
-Admin earlier (if you are creating a Character for your superuser, use your superuser account
-name).
-This creates a new in-game Character and places it in your current location.
+
Log into the game using the web client (or a third-party telnet client) using your superuser account. Move to where you want the new staffer character to appear.
+
In the game client, run @create/drop<staffername>:typeclasses.characters.Character, where <staffername> is usually the same name you used for the Staffer account you created in the Admin earlier (if you are creating a Character for your superuser, use your superuser account name). This creates a new in-game Character and places it in your current location.
Have the new Admin player log into the game.
Have the new Admin puppet the character with @icStafferName.
Have the new Admin change their password - @password<oldpassword>=<newpassword>.
-
Now that you have a Character and an Account object, there’s a few additional things you may need to
-do in order for some commands to function properly. You can either execute these as in-game commands
-while ic (controlling your character object).
Those steps will give you a ‘RosterEntry’, ‘PlayerOrNpc’, and ‘AssetOwner’ objects. RosterEntry
+
Now that you have a Character and an Account object, there’s a few additional things you may need to do in order for some commands to function properly. You can either execute these as in-game commands while ic (controlling your character object).
+
py from web.character.models import RosterEntry;RosterEntry.objects.create(player=self.player, character=self)
+
+py from world.dominion.models import PlayerOrNpc, AssetOwner;dompc = PlayerOrNpc.objects.create(player=self.player);AssetOwner.objects.create(player=dompc)
+
+
+
Those steps will give you ‘RosterEntry’, ‘PlayerOrNpc’, and ‘AssetOwner’ objects. RosterEntry
explicitly connects a character and account object together, even while offline, and contains
additional information about a character’s current presence in game (such as which ‘roster’ they’re
-in, if you choose to use an active roster of characters). PlayerOrNpc are more character extensions,
-as well as support for npcs with no in-game presence and just represented by a name which can be
-offscreen members of a character’s family. It also allows for membership in Organizations.
-AssetOwner holds information about a character or organization’s money and resources.
+in, if you choose to use an active roster of characters). PlayerOrNpc are more character extensions, as well as support for npcs with no in-game presence and just represented by a name which can be offscreen members of a character’s family. It also allows for membership in Organizations. AssetOwner holds information about a character or organization’s money and resources.
If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions
-identical to the ones above), it’s possible to get Evennia/Arx running under Anaconda for Windows. The
-process is a little bit trickier.
+
If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions identical to the ones above), it’s possible to get Evennia/Arx running under Anaconda for Windows. The process is a little bit trickier.
cd ~
mkdir Source
cd Source
mkdir Arx
-cd Arx
+cd Arx
+
+
Replace the SSH git clone links below with your own github forks.
If you don’t plan to change Evennia at all, you can use the
evennia/evennia.git repo instead of a forked one.
…and do the first run. You need winpty because Windows does not have a TTY/PTY
by default, and so the Python console input commands (used for prompts on first
run) will fail and you will end up in an unhappy place. Future runs, you should
not need winpty.
-
winpty …/evennia/bin/windows/evennia.bat start
+
winpty ../evennia/bin/windows/evennia.bat start
+
+
Once this is done, you should have your Evennia server running Arxcode up
-on localhost at port 4000, and the webserver at http://localhost:4001/
+on localhost at port 4000, and the webserver at http://localhost:4001/.
@@ -360,10 +344,10 @@ on localhost at port 4000, and the webserver at modules |
All in-game objets you can touch usually has some weight. What weight does varies from game to game. Commonly it limits how much you can carry. A heavy stone may also hurt you more than a ballon, if it falls on you. If you want to get fancy, a pressure plate may only trigger if the one stepping on it is heavy enough.
Line 6: We use the ObjectParent mixin. Since this mixin is used for Characters, Exits and Rooms as well as for Object, it means all of those will automatically also have weight!
+
Line 8: We use an AttributeProperty to set up the ‘default’ weight of 1 (whatever that is). Setting autocreate=False means no actual Attribute will be created until the weight is actually changed from the default of 1. See the AttributeProperty documentation for caveats with this.
+
Line 10 and 11: Using the @property decorator on total_weight means that we will be able to call obj.total_weight instead of obj.total_weight() later.
+
Line 12: We sum up all weights from everything “in” this object, by looping over self.contents. Since all objects will have weight now, this should always work!
Here we make sure to add another AttributeProperty telling us how much to carry. In a real game, this may be based on how strong the Character is. When we consider how much weight we already carry, we should not include our own weight, so we subtract that.
+
To honor this limit, we’ll need to override the default get command.
> hit goblin with sword
+You strike goblin with the sword. It dodges!
+> hit goblin with sword
+You are off-balance and can't attack again yet.
+
+
Some types of games want to limit how often a command can be run. If a
character casts the spell Firestorm, you might not want them to spam that
-command over and over. Or in an advanced combat system, a massive swing may
+command over and over. In an advanced combat system, a massive swing may
offer a chance of lots of damage at the cost of not being able to re-do it for
-a while. Such effects are called cooldowns.
-
This page exemplifies a very resource-efficient way to do cooldowns. A more
-‘active’ way is to use asynchronous delays as in the Blocking commands, the two might be useful to
-combine if you want to echo some message to the user after the cooldown ends.
The Cooldown contrib is a ready-made solution for
-command cooldowns you can use. It implements a handler on the object to
-conveniently manage and store the cooldowns in a similar manner exemplified in
-this tutorial.
This little recipe will limit how often a particular command can be run. Since
-Commands are class instances, and those are cached in memory, a command
-instance will remember things you store on it. So just store the current time
-of execution! Next time the command is run, it just needs to check if it has
-that time stored, and compare it with the current time to see if a desired
-delay has passed.
+a while.
+
Such effects are called command cooldowns.
+
+
This howto exemplifies a very resource-efficient way to do cooldowns. A more
+‘active’ way is to use asynchronous delays as in the Command-Duration howto suggests. The two howto’s might be useful to combine if you want to echo some message to the user after the cooldown ends.
The idea is that when a Command runs, we store the time it runs. When it next runs, we check again the current time. The command is only allowed to run if enough time passed since now and the previous run. This is a very efficient implementation that only checks on-demand.
# in, say, mygame/commands/spells.pyimporttime
@@ -157,7 +155,7 @@ delay has passed.
"Implement the spell"now=time.time()
- last_cast=caller.ndb.firestorm_last_cast# could be None
+ last_cast=caller.db.firestorm_last_cast# could be Noneiflast_castand(now-last_cast<self.rate_of_fire):message="You cannot cast this spell again yet."self.caller.msg(message)
@@ -166,21 +164,20 @@ delay has passed.
# [the spell effect is implemented]# if the spell was successfully cast, store the casting time
- self.caller.ndb.firestorm_last_cast=now
+ self.caller.db.firestorm_last_cast=now
-
We specify rate_of_fire and then just check for a NAtrribute
-firestorm_last_cast and update it if everything works out.
-
Simple and very effective since everything is just stored in memory. The
-drawback of this simple scheme is that it’s non-persistent. If you do
-reload, the cache is cleaned and all such ongoing cooldowns will be
-forgotten.
+
We specify rate_of_fire and then just check for an Attributefirestorm_last_cast on the caller. It is either None (because the spell was never cast before) or an timestamp representing the last time the spell was cast.
The above implementation will survive a reload. If you don’t want that, you can just switch to let firestorm_last_cast be a NAtrribute instead. For example:
That is, use .ndb instead of .db. Since a NAttributes are purely in-memory, they can be faster to read and write to than an Attribute. So this can be more optimal if your intervals are short and need to change often. The drawback is that they’ll reset if the server reloads.
To make a cooldown persistent (so it survives a server reload), just
-use the same technique, but use Attributes (that is, .db instead
-of .ndb storage to save the last-cast time.
@@ -253,7 +250,7 @@ reuse this mixin for all your cooldowns.
you can have all fire-related spells store the cooldown with the same
cooldown_storage_key (like fire_spell_last_used). That would mean casting
of Firestorm would block all other fire-related spells for a while.
-
Similarly, when you take that that big sword swing, other types of attacks could
+
Similarly, when you take that big sword swing, other types of attacks could
be blocked before you can recover your balance.
@@ -275,14 +272,14 @@ be blocked before you can recover your balance.
modules |
Before reading this tutorial, if you haven’t done so already, you might want to
-read the documentation on commands to get a basic understanding of
-how commands work in Evennia.
-
In some types of games a command should not start and finish immediately.
-Loading a crossbow might take a bit of time to do - time you don’t have when
+
+
> craft fine sword
+You start crafting a fine sword.
+> north
+You are too focused on your crafting, and can't move!
+You create the blade of the sword.
+You create the pommel of the sword.
+You finish crafting a Fine Sword.
+
+
+
In some types of games a command should not start and finish immediately.
+
Loading a crossbow might take a bit of time to do - time you don’t have when
the enemy comes rushing at you. Crafting that armour will not be immediate
either. For some types of games the very act of moving or changing pose all
comes with a certain time associated with it.
Evennia allows a shortcut in syntax to create simple pauses in commands. This
-syntax uses the yield keyword. The yield keyword is used in Python to
-create generators, although you don’t need to know what generators are to use
-this syntax. A short example will probably make it clear:
-
classCmdTest(Command):
+
There are two main suitable ways to introduce a ‘delay’ in a Command’s execution:
The yield keyword is a reserved word in Python. It’s used to create generators, which are interesting in their own right. For the purpose of this howto though, we just need to know that Evennia will use it to ‘pause’ the execution of the command for a certain time.
classCmdTest(Command):""" A test command just to test waiting.
@@ -138,51 +163,81 @@ this syntax. A short example will probably make it clear:
"""key="test"
- locks="cmd:all()"deffunc(self):self.msg("Before ten seconds...")
- yield10
- self.msg("Afterwards.")
-
+yield10
+self.msg("Afterwards.")
+
-
-
Important: The yield functionality will only work in the func method of
-Commands. It only works because Evennia has especially
-catered for it in Commands. If you want the same functionality elsewhere you
-must use the interactive decorator.
-
-
The important line is the yield10. It tells Evennia to “pause” the command
+
+
Line 15 : This is the important line. The yield10 tells Evennia to “pause” the command
and to wait for 10 seconds to execute the rest. If you add this command and
run it, you’ll see the first message, then, after a pause of ten seconds, the
-next message. You can use yield several times in your command.
-
This syntax will not “freeze” all commands. While the command is “pausing”,
-you can execute other commands (or even call the same command again). And
-other players aren’t frozen either.
+next message. You can use yield several times in your command.
+
+
This syntax will not “freeze” all commands. While the command is “pausing”, you can execute other commands (or even call the same command again). And other players aren’t frozen either.
-
Note: this will not save anything in the database. If you reload the game
-while a command is “paused”, it will not resume after the server has
-reloaded.
+
Using yield is non-persistent. If you reload the game while a command is “paused”, that pause state is lost and it will not resume after the server has reloaded.
The yield syntax is easy to read, easy to understand, easy to use. But it’s not that flexible if
-you want more advanced options. Learning to use alternatives might be much worth it in the end.
-
Below is a simple command example for adding a duration for a command to finish.
The yield syntax is easy to read, easy to understand, easy to use. But it’s non-persistent and not that flexible if you want more advanced options.
+
The evennia.utils.delay represents is a more powerful way to introduce delays. Unlike yield, it
+can be made persistent and also works outside of Command.func. It’s however a little more cumbersome to write since unlike yield it will not actually stop at the line it’s called.
fromevenniaimportdefault_cmds,utilsclassCmdEcho(default_cmds.MuxCommand):"""
- wait for an echo
+ Wait for an echo Usage: echo <string>
- Calls and waits for an echo
+ Calls and waits for an echo. """key="echo"
- locks="cmd:all()"
+
+defecho(self):
+"Called after 10 seconds."
+ shout=self.args
+ self.caller.msg(
+ "You hear an echo: "
+ f"{shout.upper()} ... "
+ f"{shout.capitalize()} ... "
+ f"{shout.lower()}"
+ )deffunc(self):"""
@@ -190,50 +245,67 @@ you want more advanced options. Learning to use alternatives might be much wort
"""self.caller.msg(f"You shout '{self.args}' and wait for an echo ...")# this waits non-blocking for 10 seconds, then calls self.echo
- utils.delay(10,self.echo)# call echo after 10 seconds
-
- defecho(self):
- "Called after 10 seconds."
- shout=self.args
- self.caller.msg(
- f"You hear an echo: {shout.upper()} ... {shout.capitalize()} ... {shout.lower()}"
- )
+utils.delay(10,self.echo)# call echo after 10 seconds
+
+
+
+
Import this new echo command into the default command set and reload the server. You will find that it will take 10 seconds before you see your shout coming back.
+
+
Line 14: We add a new method echo. This is a callback - a method/function we will call after a certain time.
+
Line 30: Here we use utils.delay to tell Evennia “Please wait for 10 seconds, then call “self.echo”. Note how we pass self.echo and notself.echo()! If we did the latter, echo would fire immediately. Instead we let Evennia do this call for us ten seconds later.
+
+
You will also find that this is a non-blocking effect; you can issue other commands in the interim and the game will go on as usual. The echo will come back to you in its own time.
Import this new echo command into the default command set and reload the server. You will find that
-it will take 10 seconds before you see your shout coming back. You will also find that this is a
-non-blocking effect; you can issue other commands in the interim and the game will go on as usual.
-The echo will come back to you in its own time.
utils.delay(timedelay,callback,persistent=False,*args,**kwargs) is a useful function. It will
-wait timedelay seconds, then call the callback function, optionally passing to it the arguments
-provided to utils.delay by way of *args and/or **kwargs`.
-
-
Note: The callback argument should be provided with a python path to the desired function, for
-instance my_object.my_function instead of my_object.my_function(). Otherwise my_function would
-get called and run immediately upon attempting to pass it to the delay function.
-If you want to provide arguments for utils.delay to use, when calling your callback function, you
-have to do it separatly, for instance using the utils.delay *args and/or **kwargs, as mentioned
-above.
Looking at it you might think that utils.delay(10,callback) in the code above is just an
-alternative to some more familiar thing like time.sleep(10). This is not the case. If you do
-time.sleep(10) you will in fact freeze the entire server for ten seconds! The utils.delay()is
-a thin wrapper around a Twisted
-Deferred that will delay
-execution until 10 seconds have passed, but will do so asynchronously, without bothering anyone else
-(not even you - you can continue to do stuff normally while it waits to continue).
-
The point to remember here is that the delay() call will not “pause” at that point when it is
+
+
If you set persistent=True, this delay will survive a reload. If you pass *args and/or **kwargs, they will be passed on into the callback. So this way you can pass more complex arguments to the delayed function.
+
It’s important to remember that the delay() call will not “pause” at that point when it is
called (the way yield does in the previous section). The lines after the delay() call will
actually execute right away. What you must do is to tell it which function to call after the time
has passed (its “callback”). This may sound strange at first, but it is normal practice in
-asynchronous systems. You can also link such calls together as seen below:
-
fromevenniaimportdefault_cmds,utils
+asynchronous systems. You can also link such calls together:
+
fromevenniaimportdefault_cmds,utilsclassCmdEcho(default_cmds.MuxCommand):"""
@@ -245,56 +317,61 @@ asynchronous systems. You can also link such calls together as seen below:
Calls and waits for an echo """key="echo"
- locks="cmd:all()"deffunc(self):"This sets off a chain of delayed calls"self.caller.msg(f"You shout '{self.args}', waiting for an echo ...")# wait 2 seconds before calling self.echo1
- utils.delay(2,self.echo1)
-
+utils.delay(2,self.echo1)
+# callback chain, started above
- defecho1(self):
- "First echo"
+defecho1(self):
+"First echo"self.caller.msg(f"... {self.args.upper()}")# wait 2 seconds for the next oneutils.delay(2,self.echo2)
- defecho2(self):
- "Second echo"
+defecho2(self):
+"Second echo"self.caller.msg(f"... {self.args.capitalize()}")# wait another 2 secondsutils.delay(2,callback=self.echo3)
- defecho3(self):
- "Last echo"
+defecho3(self):
+"Last echo"self.caller.msg(f"... {self.args.lower()} ...")
-
+
The above version will have the echoes arrive one after another, each separated by a two second
delay.
-
> echo Hello!
+
+
Line 19: This sets off the chain, telling Evennia to wait 2 seconds before calling self.echo1.
+
Line 22: This is called after 2 seconds. It tells Evennia to wait another 2 seconds before calling self.echo2.
+
Line 28: This is called after yet another 2 seonds (4s total). It tells Evennia to wait another 2 seconds before calling, self.echo3.
+
Line34 Called after another 2 seconds (6s total). This ends the delay-chain.
You may be aware of the time.sleep function coming with Python. Doing `time.sleep(10) pauses Python for 10 seconds. Do not use this, it will not work with Evennia. If you use it, you will block the entire server (everyone!) for ten seconds!
As mentioned, a great thing about the delay introduced by yield or utils.delay() is that it does
-not block. It just goes on in the background and you are free to play normally in the interim. In
-some cases this is not what you want however. Some commands should simply “block” other commands
-while they are running. If you are in the process of crafting a helmet you shouldn’t be able to also
-start crafting a shield at the same time, or if you just did a huge power-swing with your weapon you
-should not be able to do it again immediately.
-
The simplest way of implementing blocking is to use the technique covered in the Command Cooldown tutorial. In that tutorial we implemented cooldowns by having the
-Command store the current time. Next time the Command was called, we compared the current time to
-the stored time to determine if enough time had passed for a renewed use. This is a very
-efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player
-when enough time has passed unless they keep trying.
Both yield or utils.delay() pauses the command but allows the user to use other commands while the first one waits to finish.
+
In some cases you want to instead have that command ‘block’ other commands from running. An example is crafting a helmet: most likely you should not be able to start crafting a shield at the same time. Or even walk out of the smithy.
+
The simplest way of implementing blocking is to use the technique covered in the How to implement a Command Cooldown tutorial. In that tutorial we cooldowns are implemented by comparing the current time with the last time the command was used. This is the best approach if you can get away with it. It could work well for our crafting example … if you don’t want to automatically update the player on their progress.
+
In short:
+- If you are fine with the player making an active input to check their status, compare timestamps as done in the Command-cooldown tutorial. On-demand is by far the most efficent.
+- If you want Evennia to tell the user their status without them taking a further action, you need to use yield , delay (or some other active time-keeping method).
Here is an example where we will use utils.delay to tell the player when the cooldown has passed:
fromevenniaimportutils,default_cmds
@@ -338,12 +415,10 @@ when enough time has passed unless they keep trying.
Note how, after the cooldown, the user will get a message telling them they are now ready for
another swing.
By storing the off_balance flag on the character (rather than on, say, the Command instance
-itself) it can be accessed by other Commands too. Other attacks may also not work when you are off
-balance. You could also have an enemy Command check your off_balance status to gain bonuses, to
-take another example.
+itself) it can be accessed by other Commands too. Other attacks may also not work when you are off balance. You could also have an enemy Command check your off_balance status to gain bonuses, to take another example.
-
-
One can imagine that you will want to abort a long-running command before it has a time to finish.
If you are in the middle of crafting your armor you will probably want to stop doing that when a
monster enters your smithy.
@@ -443,60 +518,6 @@ Below is an example of a crafting command that can be aborted by starting a figh
attack command is issued during this process it will set a flag that causes the crafting to be
quietly canceled next time it tries to update.
-
-
In the latter examples above we used .ndb storage. This is fast and easy but it will reset all
-cooldowns/blocks/crafting etc if you reload the server. If you don’t want that you can replace
-.ndb with .db. But even this won’t help because the yield keyword is not persisent and nor is
-the use of delay shown above. To resolve this you can use delay with the persistent=True
-keyword. But wait! Making something persistent will add some extra complications, because now you
-must make sure Evennia can properly store things to the database.
-
Here is the original echo-command reworked to function with persistence:
-
fromevenniaimportdefault_cmds,utils
-
-# this is now in the outermost scope and takes two args!
-defecho(caller,args):
- "Called after 10 seconds."
- shout=args
- caller.msg(
- f"You hear an echo: {shout.upper()} ... {shout.capitalize()} ... {shout.lower()}"
- )
-
-classCmdEcho(default_cmds.MuxCommand):
- """
- wait for an echo
-
- Usage:
- echo <string>
-
- Calls and waits for an echo
- """
- key="echo"
- locks="cmd:all()"
-
- deffunc(self):
- """
- This is called at the initial shout.
- """
- self.caller.msg(f"You shout '{self.args}' and wait for an echo ...")
- # this waits non-blocking for 10 seconds, then calls echo(self.caller, self.args)
- utils.delay(10,echo,self.caller,self.args,persistent=True)# changes!
-
-
-
-
Above you notice two changes:
-
-
The callback (echo) was moved out of the class and became its own stand-alone function in the
-outermost scope of the module. It also now takes caller and args as arguments (it doesn’t have
-access to them directly since this is now a stand-alone function).
-
utils.delay specifies the echo function (not self.echo - it’s no longer a method!) and sends
-self.caller and self.args as arguments for it to use. We also set persistent=True.
-
-
The reason for this change is because Evennia needs to pickle the callback into storage and it
-cannot do this correctly when the method sits on the command class. Now this behave the same as the
-first version except if you reload (or even shut down) the server mid-delay it will still fire the
-callback when the server comes back up (it will resume the countdown and ignore the downtime).
-
@@ -515,14 +536,14 @@ callback when the server comes back up (it will resume the countdown and ignore
modules |
A prompt is quite common in MUDs. The prompt display useful details about your character that you
-are likely to want to keep tabs on at all times, such as health, magical power etc. It might also
-show things like in-game time, weather and so on. Many modern MUD clients (including Evennia’s own
-webclient) allows for identifying the prompt and have it appear in a correct location (usually just
-above the input line). Usually it will remain like that until it is explicitly updated.
The prompt display useful details about your character that you are likely to want to keep tabs on at all times. It could be health, magical power, gold and current location. It might also show things like in-game time, weather and so on.
+
Traditionally, the prompt (changed or not) was returned with every reply from the server and just displayed on its own line. Many modern MUD clients (including Evennia’s own webclient) allows for identifying the prompt and have it appear in a fixed location that gets updated in-place (usually just above the input line).
A prompt is sent using the prompt keyword to the msg() method on objects. The prompt will be
sent without any line breaks.
-
self.msg(prompt="HP: 5, MP: 2, SP: 8")
+
self.msg(prompt="HP: 5, MP: 2, SP: 8")
You can combine the sending of normal text with the sending (updating of the prompt):
-
self.msg("This is a text",prompt="This is a prompt")
+
self.msg("This is a text",prompt="This is a prompt")
You can update the prompt on demand, this is normally done using OOB-tracking of the relevant
@@ -167,17 +169,10 @@ the prompt when they cause a change in health, for example.
The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a
-special flag). Most MUD telnet clients will understand and allow users to catch this and keep the
-prompt in place until it updates. So in principle you’d not need to update the prompt every
-command.
-
However, with a varying user base it can be unclear which clients are used and which skill level the
-users have. So sending a prompt with every command is a safe catch-all. You don’t need to manually
-go in and edit every command you have though. Instead you edit the base command class for your
-custom commands (like MuxCommand in your mygame/commands/command.py folder) and overload the
-at_post_cmd() hook. This hook is always called after the main func() method of the Command.
The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a special flag). Most MUD telnet clients will understand and allow users to catch this and keep the prompt in place until it updates. So in principle you’d not need to update the prompt every command.
+
However, with a varying user base it can be unclear which clients are used and which skill level the users have. So sending a prompt with every command is a safe catch-all. You don’t need to manually go in and edit every command you have though. Instead you edit the base command class for your custom commands (like MuxCommand in your mygame/commands/command.py folder) and overload the at_post_cmd() hook. This hook is always called after the main func() method of the Command.
If you want to add something small like this to Evennia’s default commands without modifying them
-directly the easiest way is to just wrap those with a multiple inheritance to your own base class:
+
If you want to add something small like this to Evennia’s default commands without modifying them directly the easiest way is to just wrap those with a multiple inheritance to your own base class:
# in (for example) mygame/commands/mycommands.pyfromevenniaimportdefault_cmds
@@ -240,14 +234,14 @@ directly the easiest way is to just wrap those with a multiple inheritance to yo
modules |
Evennia allows for exits to have any name. The command “kitchen” is a valid exit name as well as “jump out the window”
-or “north”. An exit actually consists of two parts: an Exit Object and
+
+
> north
+Ouch! You bump into a wall!
+> out
+ But you are already outside ...?
+
+
+
Evennia allows for exits to have any name. The command “kitchen” is a valid exit name as well as “jump out the window” or “north”. An exit actually consists of two parts: an Exit Object and
an Exit Command stored on said exit object. The command has the same key and aliases as the
exit-object, which is why you can see the exit in the room and just write its name to traverse it.
-
So if you try to enter the name of a non-existing exit, Evennia treats is the same way as if you were trying to
-use a non-existing command:
+
So if you try to enter the name of a non-existing exit, Evennia treats is the same way as if you were trying to use a non-existing command:
> jump out the window
Command 'jump out the window' is not available. Type "help" for help.
-
Many games don’t need this type of freedom however. They define only the cardinal directions as valid exit names (
-Evennia’s tunnel command also offers this functionality). In this case, the error starts to look less logical:
+
Many games don’t need this type of freedom. They define only the cardinal directions as valid exit names ( Evennia’s tunnel command also offers this functionality). In this case, the error starts to look less logical:
> west
Command 'west' is not available. Maybe you meant "set" or "reset"?
-
Since we for our particular game know that west is an exit direction, it would be better if the error message just
-told us that we couldn’t go there.
+
Since we for our particular game know that west is an exit direction, it would be better if the error message just told us that we couldn’t go there.
The way to do this is to give Evennia an alternative Command to use when no Exit-Command is found
-in the room. See Adding Commands for more info about the
-process of adding new Commands to Evennia.
-
In this example all we’ll do is echo an error message.
+
The way to do this is to give Evennia an alternative Command to use when no Exit-Command is found in the room. See Adding Commands for more info about the process of adding new Commands to Evennia.
+
In this example we will just echo an error message, but you could do everything (maybe you lose health if you bump into a wall?)
# for example in a file mygame/commands/movecommands.pyfromevenniaimportdefault_cmds,CmdSet
@@ -177,9 +174,7 @@ process of adding new Commands to Evennia.
self.add(CmdExitErrorSouth())
-
We pack our commands in a new little cmdset; if we add this to our
-CharacterCmdSet, we can just add more errors to MovementFailCmdSet
-later without having to change code in two places.
+
We pack our commands in a new little cmdset; if we add this to our CharacterCmdSet, we can just add more errors to MovementFailCmdSet later without having to change code in two places.
# in mygame/commands/default_cmdsets.pyfromcommandsimportmovecommands
@@ -193,16 +188,12 @@ later without having to change code in two places.
self.add(movecommands.MovementFailCmdSet)
-
reload the server. What happens henceforth is that if you are in a room with an Exitobject (let’s say it’s “north”),
-the proper Exit-command will overload your error command (also named “north”). But if you enter a direction without
-having a matching exit for it, you will fall back to your default error commands:
+
reload the server. What happens henceforth is that if you are in a room with an Exitobject (let’s say it’s “north”), the proper Exit-command will overload your error command (also named “north”). But if you enter a direction without having a matching exit for it, you will fall back to your default error commands:
> east
You cannot move east.
-
Further expansions by the exit system (including manipulating the way the Exit command itself is created) can be done by
-modifying the Exit typeclass directly.
-
+
Further expansions by the exit system (including manipulating the way the Exit command itself is created) can be done by modifying the Exit typeclass directly.
The reason is that this would not work. Understanding why is important.
-
Evennia’s command system compares commands by key and/or aliases. If any key or alias
-match, the two commands are considered identical. When the cmdsets merge, priority will then decide which of these
-‘identical’ commandss replace which.
-
So the above example would work fine as long as there were no Exits at all in the room. But when we enter
-a room with an exit “north”, its Exit-command (which has a higher priority) will override the single CmdExitError
-with its alias ‘north’. So the CmdExitError will be gone and while “north” will work, we’ll again get the normal
-“Command not recognized” error for the other directions.
+
This would not work the way we want. Understanding why is important.
+
Evennia’s command system compares commands by key and/or aliases. If any key or alias match, the two commands are considered identical. When the cmdsets merge, priority will then decide which of these ‘identical’ commandss replace which.
+
So the above example would work fine as long as there were no Exits at all in the room. But when we enter a room with an exit “north”, its Exit-command (which has a higher priority) will override the single CmdExitError with its alias ‘north’. So the CmdExitError will be gone and while “north” will work, we’ll again get the normal “Command not recognized” error for the other directions.
@@ -243,14 +229,14 @@ with its alias ‘north’. So the modules |
Recommended starting point! This will take you from absolute beginner to making
-a small, but full, game with Evennia. Even if you have a very different game style
-in mind for your own game, this will give you a good start.
+a small but full game with Evennia. Other tutorials and howto’s tend to assume you are already familiar with the concepts explained in the Beginning tutorial.
The latter parts of the beginner tutorial are still being worked on.
@@ -201,10 +200,31 @@ in mind for your own game, this will give you a good start.
An easy addition to add dynamic variety to your world objects is to give them some mass. Why mass
-and not weight? Weight varies in setting; for example things on the Moon weigh 1/6 as much. On
-Earth’s surface and in most environments, no relative weight factor is needed.
-
In most settings, mass can be used as weight to spring a pressure plate trap or a floor giving way,
-determine a character’s burden weight for travel speed… The total mass of an object can
-contribute to the force of a weapon swing, or a speeding meteor to give it a potential striking
-force.
Now that we have reasons for keeping track of object mass, let’s look at the default object class
-inside your mygame/typeclasses/objects.py and see how easy it is to total up mass from an object and
-its contents.
-
# inside your mygame/typeclasses/objects.py
-
-classObject(DefaultObject):
-# [...]
- defget_mass(self):
- mass=self.attributes.get('mass',1)# Default objects have 1 unit mass.
- returnmass+sum(obj.get_mass()forobjinself.contents)
-
-
-
Adding the get_mass definition to the objects you want to sum up the masses for is done with
-Python’s “sum” function which operates on all the contents, in this case by summing them to
-return a total mass value.
-
If you only wanted specific object types to have mass or have the new object type in a different
-module, see [[Adding-Object-Typeclass-Tutorial]] with its Heavy class object. You could set the
-default for Heavy types to something much larger than 1 gram or whatever unit you want to use. Any
-non-default mass would be stored on the mass [[Attributes]] of the objects.
You can add a get_mass definition to characters and rooms, also.
-
If you were in a one metric-ton elevator with four other friends also wearing armor and carrying
-gold bricks, you might wonder if this elevator’s going to move, and how fast.
-
Assuming the unit is grams and the elevator itself weights 1,000 kilograms, it would already be
-@setelevator/mass=1000000, we’re @setme/mass=85000 and our armor is @setarmor/mass=50000.
-We’re each carrying 20 gold bars each @setgoldbar/mass=12400 then step into the elevator and see
-the following message in the elevator’s appearance: Elevatorweightandcontentsshouldnotexceed3metrictons. Are we safe? Maybe not if you consider dynamic loading. But at rest:
-
# Elevator object knows when it checks itself:
-ifself.get_mass()<3000000:
- pass# Elevator functions as normal.
-else:
- pass# Danger! Alarm sounds, cable snaps, elevator stops...
-
This tutorial will describe how to make an NPC-run shop. We will make use of the EvMenu
-system to present shoppers with a menu where they can buy things from the store’s stock.
-
Our shop extends over two rooms - a “front” room open to the shop’s customers and a locked “store
-room” holding the wares the shop should be able to sell. We aim for the following features:
-
-
The front room should have an Attribute storeroom that points to the store room.
-
Inside the front room, the customer should have a command buy or browse. This will open a
-menu listing all items available to buy from the store room.
-
A customer should be able to look at individual items before buying.
-
We use “gold” as an example currency. To determine cost, the system will look for an Attribute
-gold_value on the items in the store room. If not found, a fixed base value of 1 will be assumed.
-The wealth of the customer should be set as an Attribute gold on the Character. If not set, they
-have no gold and can’t buy anything.
-
When the customer makes a purchase, the system will check the gold_value of the goods and
-compare it to the gold Attribute of the customer. If enough gold is available, this will be
-deducted and the goods transferred from the store room to the inventory of the customer.
-
We will lock the store room so that only people with the right key can get in there.
We want to show a menu to the customer where they can list, examine and buy items in the store. This
-menu should change depending on what is currently for sale. Evennia’s EvMenu utility will manage
-the menu for us. It’s a good idea to read up on EvMenu if you are not familiar with it.
The shopping menu’s design is straightforward. First we want the main screen. You get this when you
-enter a shop and use the browse or buy command:
-
*** Welcome to ye Old Sword shop! ***
- Things for sale (choose 1-3 to inspect, quit to exit):
-_________________________________________________________
-1. A rusty sword (5 gold)
-2. A sword with a leather handle (10 gold)
-3. Excalibur (100 gold)
-
-
-
There are only three items to buy in this example but the menu should expand to however many items
-are needed. When you make a selection you will get a new screen showing the options for that
-particular item:
EvMenu defines the nodes (each menu screen with options) as normal Python functions. Each node
-must be able to change on the fly depending on what items are currently for sale. EvMenu will
-automatically make the quit command available to us so we won’t add that manually. For compactness
-we will put everything needed for our shop in one module, mygame/typeclasses/npcshop.py.
-
# mygame/typeclasses/npcshop.py
-
-fromevennia.utilsimportevmenu
-
-defmenunode_shopfront(caller):
- "This is the top-menu screen."
-
- shopname=caller.location.key
- wares=caller.location.db.storeroom.contents
-
- # Wares includes all items inside the storeroom, including the
- # door! Let's remove that from our for sale list.
- wares=[wareforwareinwaresifware.key.lower()!="door"]
-
- text=f"*** Welcome to {shopname}! ***\n"
- ifwares:
- text+=f" Things for sale (choose 1-{len(wares)} to inspect); quit to exit:"
- else:
- text+=" There is nothing for sale; quit to exit."
-
- options=[]
- forwareinwares:
- # add an option for every ware in store
- gold_val=ware.db.gold_valueor1
- options.append({"desc":f"{ware.key} ({gold_val} gold)",
- "goto":"menunode_inspect_and_buy"})
- returntext,options
-
-
-
In this code we assume the caller to be inside the shop when accessing the menu. This means we can
-access the shop room via caller.location and get its key to display as the shop’s name. We also
-assume the shop has an Attribute storeroom we can use to get to our stock. We loop over our goods
-to build up the menu’s options.
-
Note that all options point to the same menu node called menunode_inspect_and_buy! We can’t know
-which goods will be available to sale so we rely on this node to modify itself depending on the
-circumstances. Let’s create it now.
-
# further down in mygame/typeclasses/npcshop.py
-
-defmenunode_inspect_and_buy(caller,raw_string):
- "Sets up the buy menu screen."
-
- wares=caller.location.db.storeroom.contents
- # Don't forget, we will need to remove that pesky door again!
- wares=[wareforwareinwaresifware.key.lower()!="door"]
- iware=int(raw_string)-1
- ware=wares[iware]
- value=ware.db.gold_valueor1
- wealth=caller.db.goldor0
- text=f"You inspect {ware.key}:\n\n{ware.db.desc}"
-
- defbuy_ware_result(caller):
- "This will be executed first when choosing to buy."
- ifwealth>=value:
- rtext=f"You pay {value} gold and purchase {ware.key}!"
- caller.db.gold-=value
- ware.move_to(caller,quiet=True,move_type="buy")
- else:
- rtext=f"You cannot afford {value} gold for {ware.key}!"
- caller.msg(rtext)
-
- gold_val=ware.db.gold_valueor1
- options=({
- "desc":f"Buy {ware.key} for {gold_val} gold",
- "goto":"menunode_shopfront",
- "exec":buy_ware_result,
- },{
- "desc":"Look for something else",
- "goto":"menunode_shopfront",
- })
-
- returntext,options
-
-
-
In this menu node we make use of the raw_string argument to the node. This is the text the menu
-user entered on the previous node to get here. Since we only allow numbered options in our menu,
-raw_input must be an number for the player to get to this point. So we convert it to an integer
-index (menu lists start from 1, whereas Python indices always starts at 0, so we need to subtract
-1). We then use the index to get the corresponding item from storage.
-
We just show the customer the desc of the item. In a more elaborate setup you might want to show
-things like weapon damage and special stats here as well.
-
When the user choose the “buy” option, EvMenu will execute the exec instruction before we go
-back to the top node (the goto instruction). For this we make a little inline function
-buy_ware_result. EvMenu will call the function given to exec like any menu node but it does not
-need to return anything. In buy_ware_result we determine if the customer can afford the cost and
-give proper return messages. This is also where we actually move the bought item into the inventory
-of the customer.
We could in principle launch the shopping menu the moment a customer steps into our shop room, but
-this would probably be considered pretty annoying. It’s better to create a Command for
-customers to explicitly wanting to shop around.
-
# mygame/typeclasses/npcshop.py
-
-fromevenniaimportCommand
-
-classCmdBuy(Command):
- """
- Start to do some shopping
-
- Usage:
- buy
- shop
- browse
-
- This will allow you to browse the wares of the
- current shop and buy items you want.
- """
- key="buy"
- aliases=("shop","browse")
-
- deffunc(self):
- "Starts the shop EvMenu instance"
- evmenu.EvMenu(self.caller,
- "typeclasses.npcshop",
- startnode="menunode_shopfront")
-
-
-
This will launch the menu. The EvMenu instance is initialized with the path to this very module -
-since the only global functions available in this module are our menu nodes, this will work fine
-(you could also have put those in a separate module). We now just need to put this command in a
-CmdSet so we can add it correctly to the game:
There are really only two things that separate our shop from any other Room:
-
-
The shop has the storeroom Attribute set on it, pointing to a second (completely normal) room.
-
It has the ShopCmdSet stored on itself. This makes the buy command available to users entering
-the shop.
-
-
For testing we could easily add these features manually to a room using @py or other admin
-commands. Just to show how it can be done we’ll instead make a custom Typeclass for
-the shop room and make a small command that builders can use to build both the shop and the
-storeroom at once.
-
# bottom of mygame/typeclasses/npcshop.py
-
-fromevenniaimportDefaultRoom,DefaultExit,DefaultObject
-fromevennia.utils.createimportcreate_object
-
-# class for our front shop room
-classNPCShop(DefaultRoom):
- defat_object_creation(self):
- # we could also use add(ShopCmdSet, persistent=True)
- self.cmdset.add_default(ShopCmdSet)
- self.db.storeroom=None
-
-# command to build a complete shop (the Command base class
-# should already have been imported earlier in this file)
-classCmdBuildShop(Command):
- """
- Build a new shop
-
- Usage:
- @buildshop shopname
-
- This will create a new NPCshop room
- as well as a linked store room (named
- simply <storename>-storage) for the
- wares on sale. The store room will be
- accessed through a locked door in
- the shop.
- """
- key="@buildshop"
- locks="cmd:perm(Builders)"
- help_category="Builders"
-
- deffunc(self):
- "Create the shop rooms"
- ifnotself.args:
- self.msg("Usage: @buildshop <storename>")
- return
- # create the shop and storeroom
- shopname=self.args.strip()
- shop=create_object(NPCShop,
- key=shopname,
- location=None)
- storeroom=create_object(DefaultRoom,
- key=f"{shopname}-storage",
- location=None)
- shop.db.storeroom=storeroom
- # create a door between the two
- shop_exit=create_object(DefaultExit,
- key="back door",
- aliases=["storage","store room"],
- location=shop,
- destination=storeroom)
- storeroom_exit=create_object(DefaultExit,
- key="door",
- location=storeroom,
- destination=shop)
- # make a key for accessing the store room
- storeroom_key_name=f"{shopname}-storekey"
- storeroom_key=create_object(DefaultObject,
- key=storeroom_key_name,
- location=shop)
- # only allow chars with this key to enter the store room
- shop_exit.locks.add(f"traverse:holds({storeroom_key_name})")
-
- # inform the builder about progress
- self.caller.msg(f"The shop {shop} was created!")
-
-
-
Our typeclass is simple and so is our buildshop command. The command (which is for Builders only)
-just takes the name of the shop and builds the front room and a store room to go with it (always
-named "<shopname>-storage". It connects the rooms with a two-way exit. You need to add
-CmdBuildShop [to the default cmdset](Starting/Adding-Command-Tutorial#step-2-adding-the-command-to-a-
-default-cmdset) before you can use it. Once having created the shop you can now @teleport to it or
-@open a new exit to it. You could also easily expand the above command to automatically create
-exits to and from the new shop from your current location.
-
To avoid customers walking in and stealing everything, we create a Lock on the storage
-door. It’s a simple lock that requires the one entering to carry an object named
-<shopname>-storekey. We even create such a key object and drop it in the shop for the new shop
-keeper to pick up.
-
-
If players are given the right to name their own objects, this simple lock is not very secure and
-you need to come up with a more robust lock-key solution.
-
-
-
We don’t add any descriptions to all these objects so looking “at” them will not be too thrilling.
-You could add better default descriptions as part of the @buildshop command or leave descriptions
-this up to the Builder.
We now have a functioning shop and an easy way for Builders to create it. All you need now is to
-@open a new exit from the rest of the game into the shop and put some sell-able items in the store
-room. Our shop does have some shortcomings:
-
-
For Characters to be able to buy stuff they need to also have the gold Attribute set on
-themselves.
-
We manually remove the “door” exit from our items for sale. But what if there are other unsellable
-items in the store room? What if the shop owner walks in there for example - anyone in the store
-could then buy them for 1 gold.
-
What if someone else were to buy the item we’re looking at just before we decide to buy it? It
-would then be gone and the counter be wrong - the shop would pass us the next item in the list.
-
-
Fixing these issues are left as an exercise.
-
If you want to keep the shop fully NPC-run you could add a Script to restock the shop’s
-store room regularly. This shop example could also easily be owned by a human Player (run for them
-by a hired NPC) - the shop owner would get the key to the store room and be responsible for keeping
-it well stocked.
@@ -494,7 +494,7 @@ Prompt use evennia<
easily new game defining features can be added to Evennia.
You can easily build from this tutorial by expanding the map and creating more rooms to explore. Why
not add more features to your game by trying other tutorials: [Add weather to your world](Weather-
-Tutorial), fill your world with NPC’s or
+Tutorial), fill your world with NPC’s or
implement a combat system.
This tutorial shows the implementation of an NPC object that responds to characters entering their
-location. In this example the NPC has the option to respond aggressively or not, but any actions
-could be triggered this way.
-
One could imagine using a Script that is constantly checking for newcomers. This would be
-highly inefficient (most of the time its check would fail). Instead we handle this on-demand by
-using a couple of existing object hooks to inform the NPC that a Character has entered.
-
It is assumed that you already know how to create custom room and character typeclasses, please see
-the Basic Game tutorial if you haven’t already done this.
-
What we will need is the following:
-
-
An NPC typeclass that can react when someone enters.
-
A custom Room typeclass that can tell the NPC that someone entered.
-
We will also tweak our default Character typeclass a little.
-
-
To begin with, we need to create an NPC typeclass. Create a new file inside of your typeclasses
-folder and name it npcs.py and then add the following code:
-
fromtypeclasses.charactersimportCharacter
-
-classNPC(Character):
- """
- A NPC typeclass which extends the character class.
- """
- defat_char_entered(self,character):
- """
- A simple is_aggressive check.
- Can be expanded upon later.
- """
- ifself.db.is_aggressive:
- self.execute_cmd(f"say Graaah, die {character}!")
- else:
- self.execute_cmd(f"say Greetings, {character}!")
-
-
-
We will define our custom Character typeclass below. As for the new at_char_entered method we’ve
-just defined, we’ll ensure that it will be called by the room where the NPC is located, when a
-player enters that room. You’ll notice that right now, the NPC merely speaks. You can expand this
-part as you like and trigger all sorts of effects here (like combat code, fleeing, bartering or
-quest-giving) as your game design dictates.
-
Now your typeclasses.rooms module needs to have the following added:
-
# Add this import to the top of your file.
-fromevenniaimportutils
-
- # Add this hook in any empty area within your Room class.
- defat_object_receive(self,obj,source_location):
- ifutils.inherits_from(obj,'typeclasses.npcs.NPC'):# An NPC has entered
- return
- elifutils.inherits_from(obj,'typeclasses.characters.Character'):
- # A PC has entered.
- # Cause the player's character to look around.
- obj.execute_cmd('look')
- foriteminself.contents:
- ifutils.inherits_from(item,'typeclasses.npcs.NPC'):
- # An NPC is in the room
- item.at_char_entered(obj)
-
-
-
inherits_from must be given the full path of the class. If the object inherited a class from your
-world.races module, then you would check inheritance with world.races.Human, for example. There
-is no need to import these prior, as we are passing in the full path. As a matter of a fact,
-inherits_from does not properly work if you import the class and only pass in the name of the
-class.
-
-
Note:
-at_object_receive
-is a default hook of the DefaultObject typeclass (and its children). Here we are overriding this
-hook in our customized room typeclass to suit our needs.
-
-
This room checks the typeclass of objects entering it (using utils.inherits_from and responds to
-Characters, ignoring other NPCs or objects. When triggered the room will look through its
-contents and inform any NPCsinsidebycallingtheirat_char_entered` method.
-
You’ll also see that we have added a ‘look’ into this code. This is because, by default, the
-at_object_receive is carried out before the character’s at_post_move which, we will now
-overload. This means that a character entering would see the NPC perform its actions before the
-‘look’ command. Deactivate the look command in the default Character class within the
-typeclasses.characters module:
-
# Add this hook in any blank area within your Character class.
- defat_post_move(self,source_location):
- """
- Default is to look around after a move
- Note: This has been moved to Room.at_object_receive
- """
- # self.execute_cmd('look')
-
-
-
Now let’s create an NPC and make it aggressive. Type the following commands into your MUD client:
-
reload
-create/dropOrc:npcs.NPC
-
-
-
-
Note: You could also give the path as typeclasses.npcs.NPC, but Evennia will look into the
-typeclasses folder automatically, so this is a little shorter.
-
-
When you enter the aggressive NPC’s location, it will default to using its peaceful action (say your
-name is Anna):
-
Orcsays,"Greetings, Anna!"
-
-
-
Now we turn on the aggressive mode (we do it manually but it could also be triggered by some sort of
-AI code).
-
setorc/is_aggressive=True
-
-
-
Now it will perform its aggressive action whenever a character enters.
-
-
-
\ No newline at end of file
diff --git a/docs/1.0-dev/Howtos/Tutorial-NPCs-listening.html b/docs/1.0-dev/Howtos/Tutorial-NPC-Listening.html
similarity index 65%
rename from docs/1.0-dev/Howtos/Tutorial-NPCs-listening.html
rename to docs/1.0-dev/Howtos/Tutorial-NPC-Listening.html
index 48b4b34ac0..30a2769a11 100644
--- a/docs/1.0-dev/Howtos/Tutorial-NPCs-listening.html
+++ b/docs/1.0-dev/Howtos/Tutorial-NPC-Listening.html
@@ -6,7 +6,7 @@
- Tutorial NPCs listening — Evennia 1.0-dev documentation
+ NPCs that listen to what is said — Evennia 1.0-dev documentation
@@ -17,8 +17,8 @@
-
-
+
+
This tutorial shows the implementation of an NPC object that responds to characters speaking in
-their location. In this example the NPC parrots what is said, but any actions could be triggered
-this way.
-
It is assumed that you already know how to create custom room and character typeclasses, please see
-the Basic Game tutorial if you haven’t already done this.
-
What we will need is simply a new NPC typeclass that can react when someone speaks.
> say hi
+You say, "hi"
+The troll under the bridge answers, "well, well. Hello."
+
+
+
This howto explains how to make an NPC that reacts to characters speaking in their current location. The principle applies to other situations, such as enemies joining a fight or reacting to a character drawing a weapon.
# mygame/typeclasses/npc.pyfromcharactersimportCharacter
+
classNpc(Character):""" A NPC typeclass which extends the character class.
@@ -133,9 +134,38 @@ the returnf"{from_obj} said: '{message}'"
+
We add a simple method at_heard_say that formats what it hears. We assume that the message that enters it is on the form Someonesays,"Hello", and we make sure to only get Hello in that example.
+
We are not actually calling at_heard_say yet. We’ll handle that next.
When someone in the room speaks to this NPC, its msg method will be called. We will modify the
NPCs .msg method to catch says so the NPC can respond.
# mygame/typeclasses/npc.pyfromcharactersimportCharacterclassNpc(Character):
@@ -163,29 +193,18 @@ NPCs .msg# this is needed if anyone ever puppets this NPC - without it you would never
# get any feedback from the server (not even the results of look)super().msg(text=text,from_obj=from_obj,**kwargs)
-
+
So if the NPC gets a say and that say is not coming from the NPC itself, it will echo it using the
at_heard_say hook. Some things of note in the above example:
-
The text input can be on many different forms depending on where this msg is called from.
-Instead of trying to analyze text in detail with a range of if statements we just assume the
-form we want and catch the error if it does not match. This simplifies the code considerably. It’s
-called ‘leap before you look’ and is a Python paradigm that may feel unfamiliar if you are used to
-other languages. Here we ‘swallow’ the error silently, which is fine when the code checked is
-simple. If not we may want to import evennia.logger.log_trace and add log_trace() in the
-except clause.
-
We use execute_cmd to fire the say command back. We could also have called
-self.location.msg_contents directly but using the Command makes sure all hooks are called (so
-those seeing the NPC’s say can in turn react if they want).
-
Note the comments about super at the end. This will trigger the ‘default’ msg (in the parent
-class) as well. It’s not really necessary as long as no one puppets the NPC (by @ic<npcname>) but
-it’s wise to keep in there since the puppeting player will be totally blind if msg() is never
-returning anything to them!
+
Line 15 The text input can be on many different forms depending on where this msg is called from. If you look at the code of the ‘say’ command you’d find that it will call .msg with ("Hello",{"type":"say"}). We use this knowledge to figure out if this comes from a say or not.
+
Line 24: We use execute_cmd to fire the NPCs own say command back. This works because the NPC is actually a child of DefaultCharacter - so it has the CharacterCmdSet on it! Normally you should use execute_cmd only sparingly; it’s usually more efficient to call the actual code used by the Command directly. For this tutorial, invoking the command is shorter to write while making sure all hooks are called
+
Line26: Note the comments about super at the end. This will trigger the ‘default’ msg (in the parent class) as well. It’s not really necessary as long as no one puppets the NPC (by @ic<npcname>) but it’s wise to keep in there since the puppeting player will be totally blind if msg() is never returning anything to them!
Now that’s done, let’s create an NPC and see what it has to say for itself.
-
@reload
-@create/dropGuildMaster:npc.Npc
+
reload
+create/dropGuildMaster:npc.Npc
(you could also give the path as typeclasses.npc.Npc, but Evennia will look into the typeclasses
@@ -201,8 +220,7 @@ Guild Master says, "Anna said: 'hi'"
msg would be to modify the at_say hook on the Character instead. It could detect that it’s
sending to an NPC and call the at_heard_say hook directly.
While the tutorial solution has the advantage of being contained only within the NPC class,
-combining this with using the Character class gives more direct control over how the NPC will react.
-Which way to go depends on the design requirements of your particular game.
+combining this with using the Character class gives more direct control over how the NPC will react. Which way to go depends on the design requirements of your particular game.
@@ -222,14 +240,14 @@ Which way to go depends on the design requirements of your particular game.
modules |
*** Welcome to ye Old Sword shop! ***
+ Things for sale (choose 1-3 to inspect, quit to exit):
+_________________________________________________________
+1. A rusty sword (5 gold)
+2. A sword with a leather handle (10 gold)
+3. Excalibur (100 gold)
+
+
+
This will introduce an NPC able to sell things. In practice this means that when you interact with them you’ll get shown a menu of choices. Evennia provides the EvMenu utility to easily create in-game menus.
+
We will store all the merchant’s wares in their inventory. This means that they may stand in an actual shop room, at a market or wander the road. We will also use ‘gold’ as an example currency.
+To enter the shop, you’ll just need to stand in the same room and use the buy/shop command.
The merchant will respond to you giving the shop or buy command in their presence.
+
# in for example mygame/typeclasses/merchants.py
+
+fromtypeclasses.objectsimportObject
+fromevenniaimportCommand,CmdSet,EvMenu
+
+classCmdOpenShop(Command):
+ """
+ Open the shop!
+
+ Usage:
+ shop/buy
+
+ """
+ key="shop"
+ aliases=["buy"]
+
+ deffunc(self):
+ # this will sit on the Merchant, which is self.obj.
+ # the self.caller is the player wanting to buy stuff.
+ self.obj.open_shop(self.caller)
+
+
+classMerchantCmdSet(CmdSet):
+ defat_cmdset_creation(self):
+ self.add(CmdOpenShop())
+
+
+classNPCMerchant(Object):
+
+ defat_object_creation(self):
+ self.cmdset.add_default(MerchantCmdSet)
+
+ defopen_shop(self,shopper):
+ menunodes={}# TODO!
+ shopname=self.db.shopnameor"The shop"
+ EvMenu(shopper,menunodes,startnode="shop_start",
+ shopname=shopname,shopkeeper=self,wares=self.contents)
+
+
+
+
We could also have put the commands in a separate module, but for compactness, we put it all with the merchant typeclass.
+
Note that we make the merchant an Object! Since we don’t give them any other commands, it makes little sense to let them be a Character.
+
We make a very simple shop/buy Command and make sure to add it on the merchant in its own cmdset.
+
We initialize EvMenu on the shopper but we haven’t created any menunodes yet, so this will not actually do much at this point. It’s important that we we pass shopname, shopkeeper and wares into the menu, it means they will be made available as properties on the EvMenu instance - we will be able to access them from inside the menu.
EvMenu splits the menu into nodes represented by Python functions. Each node represents a stop in the menu where the user has to make a choice.
+
For simplicity, we’ll code the shop interface above the NPCMerchant class in the same module.
+
The start node of the shop named “ye Old Sword shop!” will look like this if there are only 3 wares to sell:
+
*** Welcome to ye Old Sword shop! ***
+ Things for sale (choose 1-3 to inspect, quit to exit):
+_________________________________________________________
+1. A rusty sword (5 gold)
+2. A sword with a leather handle (10 gold)
+3. Excalibur (100 gold)
+
+
+
# in mygame/typeclasses/merchants.py
+
+# top of module, above NPCMerchant class.
+
+defnode_shopfront(caller,raw_string,**kwargs):
+ "This is the top-menu screen."
+
+ # made available since we passed them to EvMenu on start
+ menu=caller.ndb._evmenu
+ shopname=menu.shopname
+ shopkeeper=menu.shopkeeper
+ wares=menu.wares
+
+ text=f"*** Welcome to {shopname}! ***\n"
+ ifwares:
+ text+=f" Things for sale (choose 1-{len(wares)} to inspect); quit to exit:"
+ else:
+ text+=" There is nothing for sale; quit to exit."
+
+ options=[]
+ forwareinwares:
+ # add an option for every ware in store
+ gold_val=ware.db.gold_valueor1
+ options.append({"desc":f"{ware.key} ({gold_val} gold)",
+ "goto":("inspect_and_buy",
+ {"selected_ware":ware})
+ })
+
+ returntext,options
+
+
+
Inside the node we can access the menu on the caller as caller.ndb._evmenu. The extra keywords we passed into EvMenu are available on this menu instance. Armed with this we can easily present a shop interface. Each option will become a numbered choice on this screen.
+
Note how we pass the ware with each option and label it selected_ware. This will be accessible in the next node’s **kwargs argument
+
If a player choose one of the wares, they should be able to inspect it. Here’s how it should look if they selected 1 in ye Old Sword shop:
Either way you should end up back at the top level of the shopping menu again and can continue browsing or quit the menu with quit.
+
Here’s how it looks in code:
+
# in mygame/typeclasses/merchants.py
+
+# right after the other node
+
+def_buy_item(caller,raw_string,**kwargs):
+ "Called if buyer chooses to buy"
+ selected_ware=kwargs["selected_ware"]
+ value=selected_ware.db.gold_valueor1
+ wealth=caller.db.goldor0
+
+ ifwealth>=value:
+ rtext=f"You pay {value} gold and purchase {ware.key}!"
+ caller.db.gold-=value
+ move_to(caller,quiet=True,move_type="buy")
+ else:
+ rtext=f"You cannot afford {value} gold for {ware.key}!"
+ caller.msg(rtext)
+ # no matter what, we return to the top level of the shop
+ return"shopfront"
+
+defnode_inspect_and_buy(caller,raw_string,**kwargs):
+ "Sets up the buy menu screen."
+
+ # passed from the option we chose
+ selected_ware=kwargs["selected_ware"]
+
+ value=selected_ware.db.gold_valueor1
+ text=f"You inspect {ware.key}:\n\n{ware.db.desc}"
+ gold_val=ware.db.gold_valueor1
+
+ options=({
+ "desc":f"Buy {ware.key} for {gold_val} gold",
+ "goto":(_buy_item,kwargs)
+ },{
+ "desc":"Look for something else",
+ "goto":"shopfront",
+ })
+ returntext,options
+
+
+
In this node we grab the selected_ware from kwargs - this we pased along from the option on the previous node. We display its description and value. If the user buys, we reroute through the _buy_item helper function (this is not a node, it’s just a callable that must return the name of the next node to go to.). In _buy_item we check if the buyer can affort the ware, and if it can we move it to their inventory. Either way, this method returns shop_front as the next node.
+
We have been referring to two nodes here: "shopfront" and "inspect_and_buy" , we should map them to the code in the menu. Scroll down to the NPCMerchant class in the same module and find that unfinished open_shop method again:
Note that a builder without any access to Python code can now set up a personalized merchant with just in-game commands. With the shop all set up, we just need to be in the same room to start consuming!
+
> buy
+*** Welcome to Stan's previously owned vessels! ***
+ Things for sale (choose 1-3 to inspect, quit to exit):
+_________________________________________________________
+1. A proud vessel (5 gold)
+2. A classic speedster (2 gold)
+
+> 1
+
+You inspect A proud vessel:
+
+The thing has holes in it.
+__________________________________________________________
+1. Buy A proud vessel (5 gold)
+2. Look for something else.
+
+> 1
+You pay 5 gold and purchase A proud vessel!
+
+*** Welcome to Stan's previously owned vessels! ***
+ Things for sale (choose 1-3 to inspect, quit to exit):
+_________________________________________________________
+1. A classic speedster (2 gold)
+
+
> north
+------------------------------------
+Meadow
+You are standing in a green meadow.
+A bandit is here.
+------------------------------------
+Bandit gives you a menacing look!
+
+
+
This tutorial shows the implementation of an NPC object that responds to characters entering their
+location.
+
What we will need is the following:
+
+
An NPC typeclass that can react when someone enters.
+
A custom Room typeclass that can tell the NPC that someone entered.
+
We will also tweak our default Character typeclass a little.
+
+
# in mygame/typeclasses/npcs.py (for example)
+
+fromtypeclasses.charactersimportCharacter
+
+classNPC(Character):
+ """
+ A NPC typeclass which extends the character class.
+ """
+ defat_char_entered(self,character):
+ """
+ A simple is_aggressive check.
+ Can be expanded upon later.
+ """
+ ifself.db.is_aggressive:
+ self.execute_cmd(f"say Graaah! Die, {character}!")
+ else:
+ self.execute_cmd(f"say Greetings, {character}!")
+
+
+
Here we make a simple method on the NPC˙. We expect it to be called when a (player-)character enters the room. We don’t actually set the is_aggressiveAttribute beforehand; if it’s not set, the NPC is simply non-hostile.
+
Whenever something enters the Room, its at_object_receive hook will be called. So we should override it.
+
# in mygame/typeclasses/rooms.py
+
+fromevenniaimportutils
+
+# ...
+
+classRoom(ObjectParent,DefaultRoom):
+
+ # ...
+
+ defat_object_receive(self,arriving_obj,source_location):
+ ifarriving_obj.account:
+ # this has an active acccount - a player character
+ foriteminself.contents:
+ # get all npcs in the room and inform them
+ ifutils.inherits_from(item,"typeclasses.npcs.NPC"):
+ self.at_char_entered(arriving_obj)
+
+
+
+
+
A currently puppeted Character will have an .account attached to it. We use that to know that the thing arriving is a Character. We then use Evennia’s utils.inherits_from helper utility to get every NPC in the room can each of their newly created at_char_entered method.
+
Make sure to reload.
+
Let’s create an NPC and make it aggressive. For the sake of this example, let’s assume your name is “Anna” and that there is a room to the north of your current location.
+
> create/drop Orc:typeclasses.npcs.NPC
+> north
+> south
+Orc says, Greetings, Anna!
+
+
+
Now let’s turn the orc aggressive.
+
> set orc/is_aggressive = True
+> north
+> south
+Orc says, Graah! Die, Anna!
+
diff --git a/docs/1.0-dev/_modules/evennia/commands/default/general.html b/docs/1.0-dev/_modules/evennia/commands/default/general.html
index 7fac7d1400..a38468b5d0 100644
--- a/docs/1.0-dev/_modules/evennia/commands/default/general.html
+++ b/docs/1.0-dev/_modules/evennia/commands/default/general.html
@@ -82,7 +82,6 @@
importrefromdjango.confimportsettings
-
fromevennia.typeclasses.attributesimportNickTemplateInvalidfromevennia.utilsimportutils
@@ -402,7 +401,10 @@
ifreplstring==old_replstring:string+=f"\nIdentical {nicktypestr.lower()} already set."else:
- string+=f"\n{nicktypestr} '|w{old_nickstring}|n' updated to map to '|w{replstring}|n'."
+ string+=(
+ f"\n{nicktypestr} '|w{old_nickstring}|n' updated to map to"
+ f" '|w{replstring}|n'."
+ )else:string+=f"\n{nicktypestr} '|w{nickstring}|n' mapped to '|w{replstring}|n'."try:
diff --git a/docs/1.0-dev/_sources/Components/EvMenu.md.txt b/docs/1.0-dev/_sources/Components/EvMenu.md.txt
index c11761cf18..b79b133e88 100644
--- a/docs/1.0-dev/_sources/Components/EvMenu.md.txt
+++ b/docs/1.0-dev/_sources/Components/EvMenu.md.txt
@@ -1115,7 +1115,7 @@ function - for example you can't use other Python keywords like `if` inside the
Unless you are dealing with a relatively simple dynamic menu, defining menus with lambda's is
probably more work than it's worth: You can create dynamic menus by instead making each node
-function more clever. See the [NPC shop tutorial](../Howtos/NPC-shop-Tutorial.md) for an example of this.
+function more clever. See the [NPC shop tutorial](../Howtos/Tutorial-NPC-Merchants.md) for an example of this.
## Ask for simple input
diff --git a/docs/1.0-dev/_sources/Contribs/Contrib-Cooldowns.md.txt b/docs/1.0-dev/_sources/Contribs/Contrib-Cooldowns.md.txt
index 7c00b51d64..5b8b696253 100644
--- a/docs/1.0-dev/_sources/Contribs/Contrib-Cooldowns.md.txt
+++ b/docs/1.0-dev/_sources/Contribs/Contrib-Cooldowns.md.txt
@@ -13,8 +13,7 @@ state. They do not fire callbacks, so are not a good fit for use cases
where something needs to happen on a specific schedule (use delay or
a TickerHandler for that instead).
-See also the evennia documentation for command cooldowns
-(https://github.com/evennia/evennia/wiki/Command-Cooldown) for more information
+See also the evennia [howto](../Howtos/Howto-Command-Cooldown.md) for more information
about the concept.
## Installation
diff --git a/docs/1.0-dev/_sources/Howtos/Arxcode-Installation.md.txt b/docs/1.0-dev/_sources/Howtos/Arxcode-Installation.md.txt
index 0d923827d5..5720b6e295 100644
--- a/docs/1.0-dev/_sources/Howtos/Arxcode-Installation.md.txt
+++ b/docs/1.0-dev/_sources/Howtos/Arxcode-Installation.md.txt
@@ -1,36 +1,32 @@
# Arxcode installing help
-[Arx - After the Reckoning](https://play.arxmush.org/) is a big and very popular
-[Evennia](https://www.evennia.com)-based game. Arx is heavily roleplaying-centric, relying on game
-masters to drive the story. Technically it's maybe best described as "a MUSH, but with more coded
-systems". In August of 2018, the game's developer, Tehom, generously released the [source code of
-Arx on github](https://github.com/Arx-Game/arxcode). This is a treasure-trove for developers wanting
-to pick ideas or even get a starting game to build on.
+```{warning} Arxcode is separately maintained.
-> These instructions are based on the Arx-code released as of *Aug 12, 2018*. They will probably
-> not work 100% out of the box anymore. Report any differences and changes needed.
+While Arxcode uses Evennia, it is _not_ part of Evennia itself; we include this documentation only as a service to users. Also, while Arxcode is still actively maintained (2022), these instructions are based on the Arx-code released as of *Aug 12, 2018*. They will probably not work 100% out of the box anymore.
+
+Arxcode bugs should be directed to [the Arxcode github issue tracker](https://github.com/Arx-Game/arxcode/issues).
+```
+
+[Arx - After the Reckoning](https://play.arxmush.org/) is a big and very popular [Evennia](https://www.evennia.com)-based game. Arx is heavily roleplaying-centric, relying on game masters to drive the story. Technically it's maybe best described as "a MUSH, but with more coded systems". In August of 2018, the game's developer, Tehom, generously released the [source code of Arx on github](https://github.com/Arx-Game/arxcode). This is a treasure-trove for developers wanting to pick ideas or even get a starting game to build on.
It's not too hard to run Arx from the sources (of course you'll start with an empty database) but
since part of Arx has grown organically, it doesn't follow standard Evennia paradigms everywhere.
-This page covers one take on installing and setting things up while making your new Arx-based game
-better match with the vanilla Evennia install.
+This page covers one take on installing and setting things up while making your new Arx-based game better match with the vanilla Evennia install.
## Installing Evennia
Firstly, set aside a folder/directory on your drive for everything to follow.
-You need to start by installing [Evennia](https://www.evennia.com) by following most of the
-[Git-installation instructions](../Setup/Installation-Git.md) for your OS. The difference is that you
-need to `git clone https://github.com/TehomCD/evennia.git` instead of Evennia's repo because Arx
-uses TehomCD's older Evennia 0.8 [fork](https://github.com/TehomCD/evennia), notably still using
-Python2. This detail is important if referring to newer Evennia documentation.
+You need to start by installing [Evennia](https://www.evennia.com) by following most of the [Git-installation instructions](../Setup/Installation-Git.md) for your OS. The difference is that instead of cloning from upstream Evennia, you should do
-If you are new to Evennia it's *highly* recommended that you run through the normal install
-instructions in full - including initializing and starting a new empty game and connecting to it.
+ git clone https://github.com/TehomCD/evennia.git
+
+This is because Arx uses TehomCD's older Evennia 0.8 [fork](https://github.com/TehomCD/evennia), notably still using Python2. This detail is important if referring to newer Evennia documentation.
+
+If you are new to Evennia it's *highly* recommended that you run through the normal install instructions in full - including initializing and starting a new empty game and connecting to it.
That way you can be sure Evennia works correctly as a baseline.
-After installing you should have a `virtualenv` running and you should have the following file
-structure in your set-aside folder:
+After installing you should have a `virtualenv` running and you should have the following file structure in your set-aside folder:
```
muddev/
@@ -53,8 +49,7 @@ to compare to.
A new folder `myarx` should appear next to the ones you already had. You could rename this to
something else if you want.
-`cd` into `myarx`. If you wonder about the structure of the game dir, you can
-[read more about it here](Beginner-Tutorial/Part1/Beginner-Tutorial-Gamedir-Overview.md).
+`cd` into `myarx`. If you wonder about the structure of the game dir, you can [read more about it here](Beginner-Tutorial/Part1/Beginner-Tutorial-Gamedir-Overview.md).
### Clean up settings
@@ -79,13 +74,9 @@ except ImportError:
print("secret_settings.py file not found or failed to import.")
```
-> Note: Indents and capitalization matter in Python. Make indents 4 spaces (not tabs) for your own
-> sanity. If you want a starter on Python in Evennia, [you can look here](Python-basic-
-introduction).
+> Note: Indents and capitalization matter in Python. Make indents 4 spaces (not tabs) for your own sanity. If you want a starter on Python in Evennia, [you can look here](Beginner-Tutorial-Python-basic- introduction).
-This will import Arx' base settings and override them with the Evennia-default telnet port and give
-the game a name. The slogan changes the sub-text shown under the name of your game in the website
-header. You can tweak these to your own liking later.
+This will import Arx' base settings and override them with the Evennia-default telnet port and give the game a name. The slogan changes the sub-text shown under the name of your game in the website header. You can tweak these to your own liking later.
Next, create a new, empty file `secret_settings.py` in the same location as the `settings.py` file.
This can just contain the following:
@@ -95,17 +86,11 @@ SECRET_KEY = "sefsefiwwj3 jnwidufhjw4545_oifej whewiu hwejfpoiwjrpw09&4er43233fw
```
-Replace the long random string with random ASCII characters of your own. The secret key should not
-be shared.
+Replace the long random string with random ASCII characters of your own. The secret key should not be shared.
-Next, open `myarx/server/conf/base_settings.py` in your text editor. We want to remove/comment out
-all mentions of the `decouple` package, which Evennia doesn't use (we use `private_settings.py` to
-hide away settings that should not be shared).
+Next, open `myarx/server/conf/base_settings.py` in your text editor. We want to remove/comment out all mentions of the `decouple` package, which Evennia doesn't use (we use `private_settings.py` to hide away settings that should not be shared).
-Comment out `from decouple import config` by adding a `#` to the start of the line: `# from decouple
-import config`. Then search for `config(` in the file and comment out all lines where this is used.
-Many of these are specific to the server environment where the original Arx runs, so is not that
-relevant to us.
+Comment out `from decouple import config` by adding a `#` to the start of the line: `# from decouple import config`. Then search for `config(` in the file and comment out all lines where this is used. Many of these are specific to the server environment where the original Arx runs, so is not that relevant to us.
### Install Arx dependencies
@@ -141,10 +126,7 @@ This creates the database and will step through all database migrations needed.
evennia start
-If all goes well Evennia will now start up, running Arx! You can connect to it on `localhost` (or
-`127.0.0.1` if your platform doesn't alias `localhost`), port `4000` using a Telnet client.
-Alternatively, you can use your web browser to browse to `http://localhost:4001` to see the game's
-website and get to the web client.
+If all goes well Evennia will now start up, running Arx! You can connect to it on `localhost` (or `127.0.0.1` if your platform doesn't alias `localhost`), port `4000` using a Telnet client. Alternatively, you can use your web browser to browse to `http://localhost:4001` to see the game's website and get to the web client.
When you log in you'll get the standard Evennia greeting (since the database is empty), but you can
try `help` to see that it's indeed Arx that is running.
@@ -162,101 +144,87 @@ run steps 7-8 and 10 to create and connect to your in-came Character.
3. Navigate to the `Accounts` section.
4. Add a new Account named for the new staffer. Use a place holder password and dummy e-mail
address.
-5. Flag account as `Staff` and apply the `Admin` permission group (This assumes you have already set
- up an Admin Group in Django).
+5. Flag account as `Staff` and apply the `Admin` permission group (This assumes you have already set up an Admin Group in Django).
6. Add Tags named `player` and `developer`.
-7. Log into the game using the web client (or a third-party telnet client) using your superuser
- account. Move to where you want the new staffer character to appear.
-8. In the game client, run `@create/drop :typeclasses.characters.Character`, where
- `` is usually the same name you used for the Staffer account you created in the
- Admin earlier (if you are creating a Character for your superuser, use your superuser account
-name).
- This creates a new in-game Character and places it in your current location.
+7. Log into the game using the web client (or a third-party telnet client) using your superuser account. Move to where you want the new staffer character to appear.
+8. In the game client, run `@create/drop :typeclasses.characters.Character`, where `` is usually the same name you used for the Staffer account you created in the Admin earlier (if you are creating a Character for your superuser, use your superuser account name). This creates a new in-game Character and places it in your current location.
9. Have the new Admin player log into the game.
10. Have the new Admin puppet the character with `@ic StafferName`.
11. Have the new Admin change their password - `@password = `.
-Now that you have a Character and an Account object, there's a few additional things you may need to
-do in order for some commands to function properly. You can either execute these as in-game commands
-while `ic` (controlling your character object).
+Now that you have a Character and an Account object, there's a few additional things you may need to do in order for some commands to function properly. You can either execute these as in-game commands while `ic` (controlling your character object).
-1. `py from web.character.models import RosterEntry;RosterEntry.objects.create(player=self.player,
-character=self)`
-2. `py from world.dominion.models import PlayerOrNpc, AssetOwner;dompc =
-PlayerOrNpc.objects.create(player = self.player);AssetOwner.objects.create(player=dompc)`
+ py from web.character.models import RosterEntry;RosterEntry.objects.create(player=self.player, character=self)
-Those steps will give you a 'RosterEntry', 'PlayerOrNpc', and 'AssetOwner' objects. RosterEntry
+ py from world.dominion.models import PlayerOrNpc, AssetOwner;dompc = PlayerOrNpc.objects.create(player=self.player);AssetOwner.objects.create(player=dompc)
+
+Those steps will give you 'RosterEntry', 'PlayerOrNpc', and 'AssetOwner' objects. RosterEntry
explicitly connects a character and account object together, even while offline, and contains
additional information about a character's current presence in game (such as which 'roster' they're
-in, if you choose to use an active roster of characters). PlayerOrNpc are more character extensions,
-as well as support for npcs with no in-game presence and just represented by a name which can be
-offscreen members of a character's family. It also allows for membership in Organizations.
-AssetOwner holds information about a character or organization's money and resources.
+in, if you choose to use an active roster of characters). PlayerOrNpc are more character extensions, as well as support for npcs with no in-game presence and just represented by a name which can be offscreen members of a character's family. It also allows for membership in Organizations. AssetOwner holds information about a character or organization's money and resources.
## Alternate Windows install guide
_Contributed by Pax_
-If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions
-identical to the ones above), it's possible to get Evennia/Arx running under Anaconda for Windows. The
-process is a little bit trickier.
+If for some reason you cannot use the Windows Subsystem for Linux (which would use instructions identical to the ones above), it's possible to get Evennia/Arx running under Anaconda for Windows. The process is a little bit trickier.
Make sure you have:
* Git for Windows https://git-scm.com/download/win
* Anaconda for Windows https://www.anaconda.com/distribution/
* VC++ Compiler for Python 2.7 https://aka.ms/vcpython27
-conda update conda
-conda create -n arx python=2.7
-source activate arx
+ conda update conda
+ conda create -n arx python=2.7
+ source activate arx
- Set up a convenient repository place for things.
+Set up a convenient repository place for things.
-cd ~
-mkdir Source
-cd Source
-mkdir Arx
-cd Arx
+ cd ~
+ mkdir Source
+ cd Source
+ mkdir Arx
+ cd Arx
- Replace the SSH git clone links below with your own github forks.
- If you don't plan to change Evennia at all, you can use the
- evennia/evennia.git repo instead of a forked one.
+Replace the SSH git clone links below with your own github forks.
+If you don't plan to change Evennia at all, you can use the
+evennia/evennia.git repo instead of a forked one.
-git clone git@github.com:/evennia.git
-git clone git@github.com:/arxcode.git
+ git clone git@github.com:/evennia.git
+ git clone git@github.com:/arxcode.git
- Evennia is a package itself, so we want to install it and all of its
- prerequisites, after switching to the appropriately-tagged branch for
- Arxcode.
+Evennia is a package itself, so we want to install it and all of its
+prerequisites, after switching to the appropriately-tagged branch for
+Arxcode.
-cd evennia
-git checkout tags/v0.7 -b arx-master
-pip install -e .
+ cd evennia
+ git checkout tags/v0.7 -b arx-master
+ pip install -e .
- Arx has some dependencies of its own, so now we'll go install them
- As it is not a package, we'll use the normal requirements file.
+Arx has some dependencies of its own, so now we'll go install them
+As it is not a package, we'll use the normal requirements file.
-cd ../arxcode
-pip install -r requirements.txt
+ cd ../arxcode
+ pip install -r requirements.txt
- The git repo doesn't include the empty log directory and Evennia is unhappy if you
- don't have it, so while still in the arxcode directory...
+The git repo doesn't include the empty log directory and Evennia is unhappy if you
+don't have it, so while still in the arxcode directory...
-mkdir server/logs
+ mkdir server/logs
- Now hit https://github.com/evennia/evennia/wiki/Arxcode-installing-help and
- change the setup stuff as in the 'Clean up settings' section.
+Now hit https://github.com/evennia/evennia/wiki/Arxcode-installing-help and
+change the setup stuff as in the 'Clean up settings' section.
- Then we will create our default database...
+Then we will create our default database...
-../evennia/bin/windows/evennia.bat migrate
+ ../evennia/bin/windows/evennia.bat migrate
...and do the first run. You need winpty because Windows does not have a TTY/PTY
by default, and so the Python console input commands (used for prompts on first
run) will fail and you will end up in an unhappy place. Future runs, you should
not need winpty.
-winpty ../evennia/bin/windows/evennia.bat start
+ winpty ../evennia/bin/windows/evennia.bat start
Once this is done, you should have your Evennia server running Arxcode up
- on localhost at port 4000, and the webserver at http://localhost:4001/
\ No newline at end of file
+ on localhost at port 4000, and the webserver at http://localhost:4001/.
\ No newline at end of file
diff --git a/docs/1.0-dev/_sources/Howtos/Howto-Add-Object-Weight.md.txt b/docs/1.0-dev/_sources/Howtos/Howto-Add-Object-Weight.md.txt
new file mode 100644
index 0000000000..4c88cea58c
--- /dev/null
+++ b/docs/1.0-dev/_sources/Howtos/Howto-Add-Object-Weight.md.txt
@@ -0,0 +1,113 @@
+# Give objects weight
+
+All in-game objets you can touch usually has some weight. What weight does varies from game to game. Commonly it limits how much you can carry. A heavy stone may also hurt you more than a ballon, if it falls on you. If you want to get fancy, a pressure plate may only trigger if the one stepping on it is heavy enough.
+
+```{code-block} python
+:linenos:
+:emphasize-lines: 6,8,10,12
+
+# inside your mygame/typeclasses/objects.py
+
+from evennia import DefaultObject
+from evennia import AttributeProperty
+
+class ObjectParent:
+
+ weight = AttributeProperty(default=1, autocreate=False)
+
+ @property
+ def total_weight(self):
+ return self.weight + sum(obj.total_weight for obj in self.contents)
+
+
+class Object(ObjectParent, DefaultObject):
+ # ...
+```
+
+```{sidebar} Why not mass?
+Yes, we know weight varies with gravity. 'Mass' is more scientifically correct. But 'mass' is less commonly used in RPGs, so we stick to 'weight' here. Just know if if your sci-fi characters can vacation on the Moon (1/6 gravity of Earth) you should consider using `mass` everywhere and calculate the current weight on the fly.
+```
+
+- **Line 6**: We use the `ObjectParent` mixin. Since this mixin is used for `Characters`, `Exits` and `Rooms` as well as for `Object`, it means all of those will automatically _also_ have weight!
+- **Line 8**: We use an [AttributeProperty](../Components/Attributes.md#using-attributeproperty) to set up the 'default' weight of 1 (whatever that is). Setting `autocreate=False` means no actual `Attribute` will be created until the weight is actually changed from the default of 1. See the `AttributeProperty` documentation for caveats with this.
+- **Line 10 and 11**: Using the `@property` decorator on `total_weight` means that we will be able to call `obj.total_weight` instead of `obj.total_weight()` later.
+- **Line 12**: We sum up all weights from everything "in" this object, by looping over `self.contents`. Since _all_ objects will have weight now, this should always work!
+
+Let's check out the weight of some trusty boxes
+```
+> create/drop box1
+> py self.search("box1").weight
+1
+> py self.search("box1").total_weight
+1
+```
+
+Let's put another box into the first one.
+
+```
+> create/drop box2
+> py self.search("box2").total_weight
+1
+> py self.search("box2").location = self.search("box1")
+> py self.search(box1).total_weight
+2
+```
+
+
+## Limit inventory by weight carried
+
+To limit how much you can carry, you first need to know your own strength
+
+```python
+# in mygame/typeclasses/characters.py
+
+from evennia import AttributeProperty
+
+# ...
+
+class Character(ObjectParent, DefaultCharacter):
+
+ carrying_capacity = AttributeProperty(10, autocreate=False)
+
+ @property
+ def carried_weight(self):
+ return self.total_weight - self.weight
+
+```
+
+Here we make sure to add another `AttributeProperty` telling us how much to carry. In a real game, this may be based on how strong the Character is. When we consider how much weight we already carry, we should not include _our own_ weight, so we subtract that.
+
+To honor this limit, we'll need to override the default `get` command.
+
+
+```{sidebar} Overriding default commands
+
+In this example, we implement the beginning of the `CmdGet` and then call the full `CmdGet()` at the end. This is not very efficient, because the parent `CmdGet` will again have to do the `caller.search()` again. To be more efficient, you will likely want to copy the entirety of the `CmdGet` code into your own version and modify it.
+```
+
+```python
+# in mygame/commands/command.py
+
+# ...
+from evennia import default_cmds
+
+# ...
+
+class WeightAwareCmdGet(default_cmds.CmdGet):
+
+ def func(self):
+ caller = self.caller
+ if not self.args:
+ caller.msg("Get what?")
+ return
+
+ obj = caller.search(self.args)
+
+ if (obj.weight + caller.carried_weight
+ > caller.carrying_capacity):
+ caller.msg("You can't carry that much!")
+ return
+ super().func()
+```
+
+Here we add an extra check for the weight of the thing we are trying to pick up, then we call the normal `CmdGet` with `super().func()`.
diff --git a/docs/1.0-dev/_sources/Howtos/Howto-Command-Cooldown.md.txt b/docs/1.0-dev/_sources/Howtos/Howto-Command-Cooldown.md.txt
index 70389d0291..084ab12b7e 100644
--- a/docs/1.0-dev/_sources/Howtos/Howto-Command-Cooldown.md.txt
+++ b/docs/1.0-dev/_sources/Howtos/Howto-Command-Cooldown.md.txt
@@ -1,30 +1,27 @@
-# Command Cooldown
+# Adding Command Cooldowns
+
+ > hit goblin with sword
+ You strike goblin with the sword. It dodges!
+ > hit goblin with sword
+ You are off-balance and can't attack again yet.
Some types of games want to limit how often a command can be run. If a
character casts the spell *Firestorm*, you might not want them to spam that
-command over and over. Or in an advanced combat system, a massive swing may
+command over and over. In an advanced combat system, a massive swing may
offer a chance of lots of damage at the cost of not being able to re-do it for
-a while. Such effects are called *cooldowns*.
+a while.
-This page exemplifies a very resource-efficient way to do cooldowns. A more
-'active' way is to use asynchronous delays as in the [](Howto-Command-Duration.md#blocking-commands), the two might be useful to
-combine if you want to echo some message to the user after the cooldown ends.
+Such effects are called *command cooldowns*.
-## The Cooldown Contrib
+```{sidebar}
+The [Cooldown contrib](../Contribs/Contrib-Cooldowns.md) is a ready-made solution for command cooldowns. It is based on this howto and implements a [handler](Tutorial-Peristent-Handler) on the object to conveniently manage and store the cooldowns.
+```
+This howto exemplifies a very resource-efficient way to do cooldowns. A more
+'active' way is to use asynchronous delays as in the [Command-Duration howto](./Howto-Command-Duration.md#blocking-commands) suggests. The two howto's might be useful to combine if you want to echo some message to the user after the cooldown ends.
-The [Cooldown contrib](../Contribs/Contrib-Cooldowns.md) is a ready-made solution for
-command cooldowns you can use. It implements a _handler_ on the object to
-conveniently manage and store the cooldowns in a similar manner exemplified in
-this tutorial.
+## An efficient cooldown
-## Non-persistent cooldown
-
-This little recipe will limit how often a particular command can be run. Since
-Commands are class instances, and those are cached in memory, a command
-instance will remember things you store on it. So just store the current time
-of execution! Next time the command is run, it just needs to check if it has
-that time stored, and compare it with the current time to see if a desired
-delay has passed.
+The idea is that when a [Command](../Components/Commands.md) runs, we store the time it runs. When it next runs, we check again the current time. The command is only allowed to run if enough time passed since now and the previous run. This is a _very_ efficient implementation that only checks on-demand.
```python
# in, say, mygame/commands/spells.py
@@ -49,7 +46,7 @@ class CmdSpellFirestorm(default_cmds.MuxCommand):
"Implement the spell"
now = time.time()
- last_cast = caller.ndb.firestorm_last_cast # could be None
+ last_cast = caller.db.firestorm_last_cast # could be None
if last_cast and (now - last_cast < self.rate_of_fire):
message = "You cannot cast this spell again yet."
self.caller.msg(message)
@@ -58,22 +55,21 @@ class CmdSpellFirestorm(default_cmds.MuxCommand):
# [the spell effect is implemented]
# if the spell was successfully cast, store the casting time
- self.caller.ndb.firestorm_last_cast = now
+ self.caller.db.firestorm_last_cast = now
```
-We specify `rate_of_fire` and then just check for a NAtrribute
-`firestorm_last_cast` and update it if everything works out.
+We specify `rate_of_fire` and then just check for an [Attribute](../Components/Attributes.md) `firestorm_last_cast` on the `caller.` It is either `None` (because the spell was never cast before) or an timestamp representing the last time the spell was cast.
-Simple and very effective since everything is just stored in memory. The
-drawback of this simple scheme is that it's non-persistent. If you do
-`reload`, the cache is cleaned and all such ongoing cooldowns will be
-forgotten.
+### Non-Persistent cooldown
-## Persistent cooldown
+The above implementation will survive a reload. If you don't want that, you can just switch to let `firestorm_last_cast` be a [NAtrribute](../Components/Attributes.md#in-memory-attributes-nattributes) instead. For example:
-To make a cooldown _persistent_ (so it survives a server reload), just
-use the same technique, but use [Attributes](../Components/Attributes.md) (that is, `.db` instead
-of `.ndb` storage to save the last-cast time.
+```python
+ last_cast = caller.ndb.firestorm_last_cast
+ # ...
+ self.caller.ndb.firestorm_last_cast = now
+```
+That is, use `.ndb` instead of `.db`. Since a `NAttribute`s are purely in-memory, they can be faster to read and write to than an `Attribute`. So this can be more optimal if your intervals are short and need to change often. The drawback is that they'll reset if the server reloads.
## Make a cooldown-aware command parent
@@ -154,5 +150,5 @@ you can have all fire-related spells store the cooldown with the same
`cooldown_storage_key` (like `fire_spell_last_used`). That would mean casting
of *Firestorm* would block all other fire-related spells for a while.
-Similarly, when you take that that big sword swing, other types of attacks could
+Similarly, when you take that big sword swing, other types of attacks could
be blocked before you can recover your balance.
diff --git a/docs/1.0-dev/_sources/Howtos/Howto-Command-Duration.md.txt b/docs/1.0-dev/_sources/Howtos/Howto-Command-Duration.md.txt
index 7c118bfd44..f754cfd2fc 100644
--- a/docs/1.0-dev/_sources/Howtos/Howto-Command-Duration.md.txt
+++ b/docs/1.0-dev/_sources/Howtos/Howto-Command-Duration.md.txt
@@ -1,24 +1,41 @@
-# Command Duration
+# Commands that take time to finish
-
-Before reading this tutorial, if you haven't done so already, you might want to
-read [the documentation on commands](../Components/Commands.md) to get a basic understanding of
-how commands work in Evennia.
+ > craft fine sword
+ You start crafting a fine sword.
+ > north
+ You are too focused on your crafting, and can't move!
+ You create the blade of the sword.
+ You create the pommel of the sword.
+ You finish crafting a Fine Sword.
In some types of games a command should not start and finish immediately.
+
Loading a crossbow might take a bit of time to do - time you don't have when
the enemy comes rushing at you. Crafting that armour will not be immediate
either. For some types of games the very act of moving or changing pose all
comes with a certain time associated with it.
-## The simple way to pause commands with yield
+There are two main suitable ways to introduce a 'delay' in a [Command](../Components/Commands.md)'s execution:
-Evennia allows a shortcut in syntax to create simple pauses in commands. This
-syntax uses the `yield` keyword. The `yield` keyword is used in Python to
-create generators, although you don't need to know what generators are to use
-this syntax. A short example will probably make it clear:
+- Using `yield` in the Command's `func` method.
+- Using the `evennia.utils.delay` utility function.
+
+We'll simplify both below.
+
+## Pause commands with `yield`
+
+The `yield` keyword is a reserved word in Python. It's used to create [generators](https://realpython.com/introduction-to-python-generators/), which are interesting in their own right. For the purpose of this howto though, we just need to know that Evennia will use it to 'pause' the execution of the command for a certain time.
+
+```{sidebar} This only works in Command.func!
+
+This `yield` functionality will *only* work in the `func` method of
+Commands. It works because Evennia has especially catered for it as a convenient shortcut. Trying to use it elsewhere will not work. If you want the same functionality elsewhere you should look up the [interactive decorator](../Concepts/Async-Process.md#the-interactive-decorator).
+```
+
+```{code-block} python
+:linenos:
+:emphasize-lines: 15
-```python
class CmdTest(Command):
"""
@@ -30,53 +47,55 @@ class CmdTest(Command):
"""
key = "test"
- locks = "cmd:all()"
def func(self):
self.msg("Before ten seconds...")
yield 10
self.msg("Afterwards.")
```
-> Important: The `yield` functionality will *only* work in the `func` method of
-> Commands. It only works because Evennia has especially
-> catered for it in Commands. If you want the same functionality elsewhere you
-> must use the [interactive decorator](../Concepts/Async-Process.md#the-interactive-decorator).
-The important line is the `yield 10`. It tells Evennia to "pause" the command
+- **Line 15** : This is the important line. The `yield 10` tells Evennia to "pause" the command
and to wait for 10 seconds to execute the rest. If you add this command and
run it, you'll see the first message, then, after a pause of ten seconds, the
next message. You can use `yield` several times in your command.
-This syntax will not "freeze" all commands. While the command is "pausing",
- you can execute other commands (or even call the same command again). And
- other players aren't frozen either.
+This syntax will not "freeze" all commands. While the command is "pausing", you can execute other commands (or even call the same command again). And other players aren't frozen either.
-> Note: this will not save anything in the database. If you reload the game
-> while a command is "paused", it will not resume after the server has
-> reloaded.
+> Using `yield` is non-persistent. If you `reload` the game while a command is "paused", that pause state is lost and it will _not_ resume after the server has reloaded.
+## Pause commands with `utils.delay`
-## The more advanced way with utils.delay
+The `yield` syntax is easy to read, easy to understand, easy to use. But it's non-persistent and not that flexible if you want more advanced options.
-The `yield` syntax is easy to read, easy to understand, easy to use. But it's not that flexible if
-you want more advanced options. Learning to use alternatives might be much worth it in the end.
+The `evennia.utils.delay` represents is a more powerful way to introduce delays. Unlike `yield`, it
+can be made persistent and also works outside of `Command.func`. It's however a little more cumbersome to write since unlike `yield` it will not actually stop at the line it's called.
-Below is a simple command example for adding a duration for a command to finish.
+```{code-block} python
+:linenos:
+:emphasize-lines: 14,30
-```python
from evennia import default_cmds, utils
class CmdEcho(default_cmds.MuxCommand):
"""
- wait for an echo
+ Wait for an echo
Usage:
echo
- Calls and waits for an echo
+ Calls and waits for an echo.
"""
key = "echo"
- locks = "cmd:all()"
+
+ def echo(self):
+ "Called after 10 seconds."
+ shout = self.args
+ self.caller.msg(
+ "You hear an echo: "
+ f"{shout.upper()} ... "
+ f"{shout.capitalize()} ... "
+ f"{shout.lower()}"
+ )
def func(self):
"""
@@ -86,50 +105,39 @@ class CmdEcho(default_cmds.MuxCommand):
# this waits non-blocking for 10 seconds, then calls self.echo
utils.delay(10, self.echo) # call echo after 10 seconds
- def echo(self):
- "Called after 10 seconds."
- shout = self.args
- self.caller.msg(
- f"You hear an echo: {shout.upper()} ... {shout.capitalize()} ... {shout.lower()}"
- )
```
-Import this new echo command into the default command set and reload the server. You will find that
-it will take 10 seconds before you see your shout coming back. You will also find that this is a
-*non-blocking* effect; you can issue other commands in the interim and the game will go on as usual.
-The echo will come back to you in its own time.
+Import this new echo command into the default command set and reload the server. You will find that it will take 10 seconds before you see your shout coming back.
-### About utils.delay()
+- **Line 14**: We add a new method `echo`. This is a _callback_ - a method/function we will call after a certain time.
+- **Line 30**: Here we use `utils.delay` to tell Evennia "Please wait for 10 seconds, then call "`self.echo`". Note how we pass `self.echo` and _not_ `self.echo()`! If we did the latter, `echo` would fire _immediately_. Instead we let Evennia do this call for us ten seconds later.
-`utils.delay(timedelay, callback, persistent=False, *args, **kwargs)` is a useful function. It will
-wait `timedelay` seconds, then call the `callback` function, optionally passing to it the arguments
-provided to utils.delay by way of *args and/or **kwargs`.
+You will also find that this is a *non-blocking* effect; you can issue other commands in the interim and the game will go on as usual. The echo will come back to you in its own time.
-> Note: The callback argument should be provided with a python path to the desired function, for
-instance `my_object.my_function` instead of `my_object.my_function()`. Otherwise my_function would
-get called and run immediately upon attempting to pass it to the delay function.
-If you want to provide arguments for utils.delay to use, when calling your callback function, you
-have to do it separatly, for instance using the utils.delay *args and/or **kwargs, as mentioned
-above.
+The call signature for `utils.delay` is:
-> If you are not familiar with the syntax `*args` and `**kwargs`, [see the Python documentation
-here](https://docs.python.org/2/tutorial/controlflow.html#arbitrary-argument-lists).
+```python
+utils.delay(timedelay, callback, persistent=False, *args, **kwargs)
+```
-Looking at it you might think that `utils.delay(10, callback)` in the code above is just an
-alternative to some more familiar thing like `time.sleep(10)`. This is *not* the case. If you do
-`time.sleep(10)` you will in fact freeze the *entire server* for ten seconds! The `utils.delay()`is
-a thin wrapper around a Twisted
-[Deferred](https://twistedmatrix.com/documents/11.0.0/core/howto/defer.html) that will delay
-execution until 10 seconds have passed, but will do so asynchronously, without bothering anyone else
-(not even you - you can continue to do stuff normally while it waits to continue).
+```{sidebar} *args and **kwargs
-The point to remember here is that the `delay()` call will not "pause" at that point when it is
+These are used to indicate any number of arguments or keyword-arguments should be picked up here. In code they are treated as a `tuple` and a `dict` respectively.
+
+`*args` and `**kwargs` are used in many places in Evennia. [See an online tutorial here](https://realpython.com/python-kwargs-and-args).
+```
+If you set `persistent=True`, this delay will survive a `reload`. If you pass `*args` and/or `**kwargs`, they will be passed on into the `callback`. So this way you can pass more complex arguments to the delayed function.
+
+It's important to remember that the `delay()` call will not "pause" at that point when it is
called (the way `yield` does in the previous section). The lines after the `delay()` call will
actually execute *right away*. What you must do is to tell it which function to call *after the time
has passed* (its "callback"). This may sound strange at first, but it is normal practice in
-asynchronous systems. You can also link such calls together as seen below:
+asynchronous systems. You can also link such calls together:
+
+```{code-block}
+:linenos:
+:emphasize-lines: 19,22,28,34
-```python
from evennia import default_cmds, utils
class CmdEcho(default_cmds.MuxCommand):
@@ -142,7 +150,6 @@ class CmdEcho(default_cmds.MuxCommand):
Calls and waits for an echo
"""
key = "echo"
- locks = "cmd:all()"
def func(self):
"This sets off a chain of delayed calls"
@@ -172,25 +179,36 @@ class CmdEcho(default_cmds.MuxCommand):
The above version will have the echoes arrive one after another, each separated by a two second
delay.
- > echo Hello!
- ... HELLO!
- ... Hello!
- ... hello! ...
+- **Line 19**: This sets off the chain, telling Evennia to wait 2 seconds before calling `self.echo1`.
+- **Line 22**: This is called after 2 seconds. It tells Evennia to wait another 2 seconds before calling `self.echo2`.
+- **Line 28**: This is called after yet another 2 seonds (4s total). It tells Evennia to wait another 2 seconds before calling, `self.echo3`.
+- **Line34** Called after another 2 seconds (6s total). This ends the delay-chain.
-## Blocking commands
+```
+> echo Hello!
+... HELLO!
+... Hello!
+... hello! ...
+```
-As mentioned, a great thing about the delay introduced by `yield` or `utils.delay()` is that it does
-not block. It just goes on in the background and you are free to play normally in the interim. In
-some cases this is not what you want however. Some commands should simply "block" other commands
-while they are running. If you are in the process of crafting a helmet you shouldn't be able to also
-start crafting a shield at the same time, or if you just did a huge power-swing with your weapon you
-should not be able to do it again immediately.
+```{warning} What about time.sleep?
-The simplest way of implementing blocking is to use the technique covered in the [](Howto-Command-Cooldown.md) tutorial. In that tutorial we implemented cooldowns by having the
-Command store the current time. Next time the Command was called, we compared the current time to
-the stored time to determine if enough time had passed for a renewed use. This is a *very*
-efficient, reliable and passive solution. The drawback is that there is nothing to tell the Player
-when enough time has passed unless they keep trying.
+You may be aware of the `time.sleep` function coming with Python. Doing `time.sleep(10) pauses Python for 10 seconds. **Do not use this**, it will not work with Evennia. If you use it, you will block the _entire server_ (everyone!) for ten seconds!
+
+If you want specifics, `utils.delay` is a thin wrapper around a [Twisted Deferred](https://docs.twisted.org/en/twisted-22.1.0/core/howto/defer.html). This is an [asynchronous concept](../Concepts/Async-Process.md).
+```
+
+## Making a blocking command
+
+Both `yield` or `utils.delay()` pauses the command but allows the user to use other commands while the first one waits to finish.
+
+In some cases you want to instead have that command 'block' other commands from running. An example is crafting a helmet: most likely you should not be able to start crafting a shield at the same time. Or even walk out of the smithy.
+
+The simplest way of implementing blocking is to use the technique covered in the [How to implement a Command Cooldown](./Howto-Command-Cooldown.md) tutorial. In that tutorial we cooldowns are implemented by comparing the current time with the last time the command was used. This is the best approach if you can get away with it. It could work well for our crafting example ... _if_ you don't want to automatically update the player on their progress.
+
+In short:
+ - If you are fine with the player making an active input to check their status, compare timestamps as done in the Command-cooldown tutorial. On-demand is by far the most efficent.
+ - If you want Evennia to tell the user their status without them taking a further action, you need to use `yield` , `delay` (or some other active time-keeping method).
Here is an example where we will use `utils.delay` to tell the player when the cooldown has passed:
@@ -238,11 +256,9 @@ Note how, after the cooldown, the user will get a message telling them they are
another swing.
By storing the `off_balance` flag on the character (rather than on, say, the Command instance
-itself) it can be accessed by other Commands too. Other attacks may also not work when you are off
-balance. You could also have an enemy Command check your `off_balance` status to gain bonuses, to
-take another example.
+itself) it can be accessed by other Commands too. Other attacks may also not work when you are off balance. You could also have an enemy Command check your `off_balance` status to gain bonuses, to take another example.
-## Abortable commands
+## Make a Command possible to Abort
One can imagine that you will want to abort a long-running command before it has a time to finish.
If you are in the middle of crafting your armor you will probably want to stop doing that when a
@@ -344,59 +360,4 @@ class CmdAttack(default_cmds.MuxCommand):
The above code creates a delayed crafting command that will gradually create the armour. If the
`attack` command is issued during this process it will set a flag that causes the crafting to be
-quietly canceled next time it tries to update.
-
-## Persistent delays
-
-In the latter examples above we used `.ndb` storage. This is fast and easy but it will reset all
-cooldowns/blocks/crafting etc if you reload the server. If you don't want that you can replace
-`.ndb` with `.db`. But even this won't help because the `yield` keyword is not persisent and nor is
-the use of `delay` shown above. To resolve this you can use `delay` with the `persistent=True`
-keyword. But wait! Making something persistent will add some extra complications, because now you
-must make sure Evennia can properly store things to the database.
-
-Here is the original echo-command reworked to function with persistence:
-```python
-from evennia import default_cmds, utils
-
-# this is now in the outermost scope and takes two args!
-def echo(caller, args):
- "Called after 10 seconds."
- shout = args
- caller.msg(
- f"You hear an echo: {shout.upper()} ... {shout.capitalize()} ... {shout.lower()}"
- )
-
-class CmdEcho(default_cmds.MuxCommand):
- """
- wait for an echo
-
- Usage:
- echo
-
- Calls and waits for an echo
- """
- key = "echo"
- locks = "cmd:all()"
-
- def func(self):
- """
- This is called at the initial shout.
- """
- self.caller.msg(f"You shout '{self.args}' and wait for an echo ...")
- # this waits non-blocking for 10 seconds, then calls echo(self.caller, self.args)
- utils.delay(10, echo, self.caller, self.args, persistent=True) # changes!
-
-```
-
-Above you notice two changes:
-- The callback (`echo`) was moved out of the class and became its own stand-alone function in the
-outermost scope of the module. It also now takes `caller` and `args` as arguments (it doesn't have
-access to them directly since this is now a stand-alone function).
-- `utils.delay` specifies the `echo` function (not `self.echo` - it's no longer a method!) and sends
-`self.caller` and `self.args` as arguments for it to use. We also set `persistent=True`.
-
-The reason for this change is because Evennia needs to `pickle` the callback into storage and it
-cannot do this correctly when the method sits on the command class. Now this behave the same as the
-first version except if you reload (or even shut down) the server mid-delay it will still fire the
-callback when the server comes back up (it will resume the countdown and ignore the downtime).
\ No newline at end of file
+quietly canceled next time it tries to update.
\ No newline at end of file
diff --git a/docs/1.0-dev/_sources/Howtos/Howto-Command-Prompt.md.txt b/docs/1.0-dev/_sources/Howtos/Howto-Command-Prompt.md.txt
index 217a3635ea..443c0c8e1b 100644
--- a/docs/1.0-dev/_sources/Howtos/Howto-Command-Prompt.md.txt
+++ b/docs/1.0-dev/_sources/Howtos/Howto-Command-Prompt.md.txt
@@ -1,24 +1,26 @@
-# Command Prompt
+# Adding a Command Prompt
+A *prompt* is quite common in MUDs:
-A *prompt* is quite common in MUDs. The prompt display useful details about your character that you
-are likely to want to keep tabs on at all times, such as health, magical power etc. It might also
-show things like in-game time, weather and so on. Many modern MUD clients (including Evennia's own
-webclient) allows for identifying the prompt and have it appear in a correct location (usually just
-above the input line). Usually it will remain like that until it is explicitly updated.
+ HP: 5, MP: 2, SP: 8
+ >
-## Sending a prompt
+The prompt display useful details about your character that you are likely to want to keep tabs on at all times. It could be health, magical power, gold and current location. It might also show things like in-game time, weather and so on.
+
+Traditionally, the prompt (changed or not) was returned with every reply from the server and just displayed on its own line. Many modern MUD clients (including Evennia's own webclient) allows for identifying the prompt and have it appear in a fixed location that gets updated in-place (usually just above the input line).
+
+## A fixed-location prompt
A prompt is sent using the `prompt` keyword to the `msg()` method on objects. The prompt will be
sent without any line breaks.
```python
- self.msg(prompt="HP: 5, MP: 2, SP: 8")
+self.msg(prompt="HP: 5, MP: 2, SP: 8")
```
You can combine the sending of normal text with the sending (updating of the prompt):
```python
- self.msg("This is a text", prompt="This is a prompt")
+self.msg("This is a text", prompt="This is a prompt")
```
You can update the prompt on demand, this is normally done using [OOB](../Concepts/OOB.md)-tracking of the relevant
@@ -63,18 +65,11 @@ Here is a simple example of the prompt sent/updated from a command class:
prompt = f"{hp} HP, {mp} MP, {sp} SP"
self.caller.msg(text, prompt=prompt)
```
-## A prompt sent with every command
+## A prompt with every command
-The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a
-special flag). Most MUD telnet clients will understand and allow users to catch this and keep the
-prompt in place until it updates. So *in principle* you'd not need to update the prompt every
-command.
+The prompt sent as described above uses a standard telnet instruction (the Evennia web client gets a special flag). Most MUD telnet clients will understand and allow users to catch this and keep the prompt in place until it updates. So *in principle* you'd not need to update the prompt every command.
-However, with a varying user base it can be unclear which clients are used and which skill level the
-users have. So sending a prompt with every command is a safe catch-all. You don't need to manually
-go in and edit every command you have though. Instead you edit the base command class for your
-custom commands (like `MuxCommand` in your `mygame/commands/command.py` folder) and overload the
-`at_post_cmd()` hook. This hook is always called *after* the main `func()` method of the Command.
+However, with a varying user base it can be unclear which clients are used and which skill level the users have. So sending a prompt with every command is a safe catch-all. You don't need to manually go in and edit every command you have though. Instead you edit the base command class for your custom commands (like `MuxCommand` in your `mygame/commands/command.py` folder) and overload the `at_post_cmd()` hook. This hook is always called *after* the main `func()` method of the Command.
```python
from evennia import default_cmds
@@ -91,8 +86,7 @@ class MuxCommand(default_cmds.MuxCommand):
### Modifying default commands
-If you want to add something small like this to Evennia's default commands without modifying them
-directly the easiest way is to just wrap those with a multiple inheritance to your own base class:
+If you want to add something small like this to Evennia's default commands without modifying them directly the easiest way is to just wrap those with a multiple inheritance to your own base class:
```python
# in (for example) mygame/commands/mycommands.py
diff --git a/docs/1.0-dev/_sources/Howtos/Howto-Default-Exit-Errors.md.txt b/docs/1.0-dev/_sources/Howtos/Howto-Default-Exit-Errors.md.txt
index 8d90a36e33..f2aa6557ed 100644
--- a/docs/1.0-dev/_sources/Howtos/Howto-Default-Exit-Errors.md.txt
+++ b/docs/1.0-dev/_sources/Howtos/Howto-Default-Exit-Errors.md.txt
@@ -1,36 +1,33 @@
-# Default Exit Errors
+# Return custom errors on missing Exits
-Evennia allows for exits to have any name. The command "kitchen" is a valid exit name as well as "jump out the window"
-or "north". An exit actually consists of two parts: an [Exit Object](../Components/Objects.md) and
+
+ > north
+ Ouch! You bump into a wall!
+ > out
+ But you are already outside ...?
+
+Evennia allows for exits to have any name. The command "kitchen" is a valid exit name as well as "jump out the window" or "north". An exit actually consists of two parts: an [Exit Object](../Components/Objects.md) and
an [Exit Command](../Components/Commands.md) stored on said exit object. The command has the same key and aliases as the
exit-object, which is why you can see the exit in the room and just write its name to traverse it.
-So if you try to enter the name of a non-existing exit, Evennia treats is the same way as if you were trying to
-use a non-existing command:
+So if you try to enter the name of a non-existing exit, Evennia treats is the same way as if you were trying to use a non-existing command:
> jump out the window
Command 'jump out the window' is not available. Type "help" for help.
-Many games don't need this type of freedom however. They define only the cardinal directions as valid exit names (
-Evennia's `tunnel` command also offers this functionality). In this case, the error starts to look less logical:
+Many games don't need this type of freedom. They define only the cardinal directions as valid exit names ( Evennia's `tunnel` command also offers this functionality). In this case, the error starts to look less logical:
> west
Command 'west' is not available. Maybe you meant "set" or "reset"?
-Since we for our particular game *know* that west is an exit direction, it would be better if the error message just
-told us that we couldn't go there.
+Since we for our particular game *know* that west is an exit direction, it would be better if the error message just told us that we couldn't go there.
> west
You cannot move west.
+The way to do this is to give Evennia an _alternative_ Command to use when no Exit-Command is found in the room. See [Adding Commands](Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.md) for more info about the process of adding new Commands to Evennia.
-## Adding default error commands
-
-The way to do this is to give Evennia an _alternative_ Command to use when no Exit-Command is found
-in the room. See [Adding Commands](Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.md) for more info about the
-process of adding new Commands to Evennia.
-
-In this example all we'll do is echo an error message.
+In this example we will just echo an error message, but you could do everything (maybe you lose health if you bump into a wall?)
```python
# for example in a file mygame/commands/movecommands.py
@@ -75,9 +72,7 @@ class MovementFailCmdSet(CmdSet):
self.add(CmdExitErrorSouth())
```
-We pack our commands in a new little cmdset; if we add this to our
-`CharacterCmdSet`, we can just add more errors to `MovementFailCmdSet`
-later without having to change code in two places.
+We pack our commands in a new little cmdset; if we add this to our `CharacterCmdSet`, we can just add more errors to `MovementFailCmdSet` later without having to change code in two places.
```python
# in mygame/commands/default_cmdsets.py
@@ -93,15 +88,12 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
self.add(movecommands.MovementFailCmdSet)
```
-`reload` the server. What happens henceforth is that if you are in a room with an Exitobject (let's say it's "north"),
-the proper Exit-command will _overload_ your error command (also named "north"). But if you enter a direction without
-having a matching exit for it, you will fall back to your default error commands:
+`reload` the server. What happens henceforth is that if you are in a room with an Exitobject (let's say it's "north"), the proper Exit-command will _overload_ your error command (also named "north"). But if you enter a direction without having a matching exit for it, you will fall back to your default error commands:
> east
You cannot move east.
-Further expansions by the exit system (including manipulating the way the Exit command itself is created) can be done by
-modifying the [Exit typeclass](../Components/Typeclasses.md) directly.
+Further expansions by the exit system (including manipulating the way the Exit command itself is created) can be done by modifying the [Exit typeclass](../Components/Typeclasses.md) directly.
## Why not a single command?
@@ -118,13 +110,8 @@ class CmdExitError(default_cmds.MuxCommand):
#[...]
```
-The reason is that this would *not* work. Understanding why is important.
+This would *not* work the way we want. Understanding why is important.
-Evennia's [command system](../Components/Commands.md) compares commands by key and/or aliases. If _any_ key or alias
-match, the two commands are considered _identical_. When the cmdsets merge, priority will then decide which of these
-'identical' commandss replace which.
+Evennia's [command system](../Components/Commands.md) compares commands by key and/or aliases. If _any_ key or alias match, the two commands are considered _identical_. When the cmdsets merge, priority will then decide which of these 'identical' commandss replace which.
-So the above example would work fine as long as there were _no Exits at all_ in the room. But when we enter
-a room with an exit "north", its Exit-command (which has a higher priority) will override the single `CmdExitError`
-with its alias 'north'. So the `CmdExitError` will be gone and while "north" will work, we'll again get the normal
-"Command not recognized" error for the other directions.
\ No newline at end of file
+So the above example would work fine as long as there were _no Exits at all_ in the room. But when we enter a room with an exit "north", its Exit-command (which has a higher priority) will override the single `CmdExitError` with its alias 'north'. So the `CmdExitError` will be gone and while "north" will work, we'll again get the normal "Command not recognized" error for the other directions.
\ No newline at end of file
diff --git a/docs/1.0-dev/_sources/Howtos/Howtos-Overview.md.txt b/docs/1.0-dev/_sources/Howtos/Howtos-Overview.md.txt
index ce3d0569d2..7631019372 100644
--- a/docs/1.0-dev/_sources/Howtos/Howtos-Overview.md.txt
+++ b/docs/1.0-dev/_sources/Howtos/Howtos-Overview.md.txt
@@ -5,8 +5,7 @@ All Evennia tutorials. They will often refer to the [components](../Components/C
## Beginner Tutorial
Recommended starting point! This will take you from absolute beginner to making
-a small, but full, game with Evennia. Even if you have a very different game style
-in mind for your own game, this will give you a good start.
+a small but full game with Evennia. Other tutorials and howto's tend to assume you are already familiar with the concepts explained in the Beginning tutorial.
> The latter parts of the beginner tutorial are still being worked on.
@@ -20,25 +19,26 @@ in mind for your own game, this will give you a good start.
./Beginner-Tutorial/Part5/Beginner-Tutorial-Part5-Intro
```
+
## Howto's
```{toctree}
-:maxdepth: 1
+:maxdepth: 2
Howto-Command-Prompt.md
Howto-Command-Cooldown.md
Howto-Command-Duration.md
Howto-Default-Exit-Errors.md
-
+Howto-Add-Object-Weight.md
```
## Mobs and NPCs
```{toctree}
:maxdepth: 1
-Tutorial-NPCs-listening.md
-Tutorial-Aggressive-NPCs.md
-NPC-shop-Tutorial.md
+Tutorial-NPC-Listening.md
+Tutorial-NPC-Reacting.md
+Tutorial-NPC-Merchants.md
```
## Vehicles
@@ -56,14 +56,11 @@ Tutorial-Vehicles.md
Tutorial-Persistent-Handler.md
Gametime-Tutorial.md
-Mass-and-weight-for-objects.md
Weather-Tutorial.md
Tutorial-Coordinates.md
Dynamic-In-Game-Map.md
Static-In-Game-Map.md
-Arxcode-Installation.md
Tutorial-Tweeting-Game-Stats.md
-
```
## Web-related tutorials
@@ -87,6 +84,7 @@ Tutorial-Understanding-Color-Tags.md
Evennia-for-roleplaying-sessions.md
Evennia-for-Diku-Users.md
Evennia-for-MUSH-Users.md
+Arxcode-Installation.md
```
## Old tutorials
diff --git a/docs/1.0-dev/_sources/Howtos/Mass-and-weight-for-objects.md.txt b/docs/1.0-dev/_sources/Howtos/Mass-and-weight-for-objects.md.txt
deleted file mode 100644
index b4463bfd08..0000000000
--- a/docs/1.0-dev/_sources/Howtos/Mass-and-weight-for-objects.md.txt
+++ /dev/null
@@ -1,98 +0,0 @@
-# Mass and weight for objects
-
-
-An easy addition to add dynamic variety to your world objects is to give them some mass. Why mass
-and not weight? Weight varies in setting; for example things on the Moon weigh 1/6 as much. On
-Earth's surface and in most environments, no relative weight factor is needed.
-
-In most settings, mass can be used as weight to spring a pressure plate trap or a floor giving way,
-determine a character's burden weight for travel speed... The total mass of an object can
-contribute to the force of a weapon swing, or a speeding meteor to give it a potential striking
-force.
-
-## Objects
-
-Now that we have reasons for keeping track of object mass, let's look at the default object class
-inside your mygame/typeclasses/objects.py and see how easy it is to total up mass from an object and
-its contents.
-
-```python
-# inside your mygame/typeclasses/objects.py
-
-class Object(DefaultObject):
-# [...]
- def get_mass(self):
- mass = self.attributes.get('mass', 1) # Default objects have 1 unit mass.
- return mass + sum(obj.get_mass() for obj in self.contents)
-```
-
-Adding the `get_mass` definition to the objects you want to sum up the masses for is done with
-Python's "sum" function which operates on all the contents, in this case by summing them to
-return a total mass value.
-
-If you only wanted specific object types to have mass or have the new object type in a different
-module, see [[Adding-Object-Typeclass-Tutorial]] with its Heavy class object. You could set the
-default for Heavy types to something much larger than 1 gram or whatever unit you want to use. Any
-non-default mass would be stored on the `mass` [[Attributes]] of the objects.
-
-
-## Characters and rooms
-
-You can add a `get_mass` definition to characters and rooms, also.
-
-If you were in a one metric-ton elevator with four other friends also wearing armor and carrying
-gold bricks, you might wonder if this elevator's going to move, and how fast.
-
-Assuming the unit is grams and the elevator itself weights 1,000 kilograms, it would already be
-`@set elevator/mass=1000000`, we're `@set me/mass=85000` and our armor is `@set armor/mass=50000`.
-We're each carrying 20 gold bars each `@set gold bar/mass=12400` then step into the elevator and see
-the following message in the elevator's appearance: `Elevator weight and contents should not exceed
-3 metric tons.` Are we safe? Maybe not if you consider dynamic loading. But at rest:
-
-```python
-# Elevator object knows when it checks itself:
-if self.get_mass() < 3000000:
- pass # Elevator functions as normal.
-else:
- pass # Danger! Alarm sounds, cable snaps, elevator stops...
-```
-
-## Inventory
-Example of listing mass of items in your inventory:
-
-```python
-class CmdInventory(MuxCommand):
- """
- view inventory
- Usage:
- inventory
- inv
- Switches:
- /weight to display all available channels.
- Shows your inventory: carrying, wielding, wearing, obscuring.
- """
-
- key = "inventory"
- aliases = ["inv", "i"]
- locks = "cmd:all()"
-
- def func(self):
- "check inventory"
- items = self.caller.contents
- if not items:
- string = "You are not carrying anything."
- else:
- table = prettytable.PrettyTable(["name", "desc"])
- table.header = False
- table.border = False
- for item in items:
- second = item.get_mass() \
- if "weight" in self.switches else item.db.desc
- table.add_row([
- str(item.get_display_name(self.caller.sessions)),
- second and second or "",
- ])
- string = f"|wYou are carrying:\n{table}"
- self.caller.msg(string)
-
-```
diff --git a/docs/1.0-dev/_sources/Howtos/NPC-shop-Tutorial.md.txt b/docs/1.0-dev/_sources/Howtos/NPC-shop-Tutorial.md.txt
deleted file mode 100644
index 209f2d17f3..0000000000
--- a/docs/1.0-dev/_sources/Howtos/NPC-shop-Tutorial.md.txt
+++ /dev/null
@@ -1,334 +0,0 @@
-# NPC shop Tutorial
-
-This tutorial will describe how to make an NPC-run shop. We will make use of the [EvMenu](../Components/EvMenu.md)
-system to present shoppers with a menu where they can buy things from the store's stock.
-
-Our shop extends over two rooms - a "front" room open to the shop's customers and a locked "store
-room" holding the wares the shop should be able to sell. We aim for the following features:
-
- - The front room should have an Attribute `storeroom` that points to the store room.
- - Inside the front room, the customer should have a command `buy` or `browse`. This will open a
-menu listing all items available to buy from the store room.
- - A customer should be able to look at individual items before buying.
- - We use "gold" as an example currency. To determine cost, the system will look for an Attribute
-`gold_value` on the items in the store room. If not found, a fixed base value of 1 will be assumed.
-The wealth of the customer should be set as an Attribute `gold` on the Character. If not set, they
-have no gold and can't buy anything.
- - When the customer makes a purchase, the system will check the `gold_value` of the goods and
-compare it to the `gold` Attribute of the customer. If enough gold is available, this will be
-deducted and the goods transferred from the store room to the inventory of the customer.
- - We will lock the store room so that only people with the right key can get in there.
-
-## The shop menu
-
-We want to show a menu to the customer where they can list, examine and buy items in the store. This
-menu should change depending on what is currently for sale. Evennia's *EvMenu* utility will manage
-the menu for us. It's a good idea to [read up on EvMenu](../Components/EvMenu.md) if you are not familiar with it.
-
-### Designing the menu
-
-The shopping menu's design is straightforward. First we want the main screen. You get this when you
-enter a shop and use the `browse` or `buy` command:
-
-```
-*** Welcome to ye Old Sword shop! ***
- Things for sale (choose 1-3 to inspect, quit to exit):
-_________________________________________________________
-1. A rusty sword (5 gold)
-2. A sword with a leather handle (10 gold)
-3. Excalibur (100 gold)
-```
-
-There are only three items to buy in this example but the menu should expand to however many items
-are needed. When you make a selection you will get a new screen showing the options for that
-particular item:
-
-```
-You inspect A rusty sword:
-
-This is an old weapon maybe once used by soldiers in some
-long forgotten army. It is rusty and in bad condition.
-__________________________________________________________
-1. Buy A rusty sword (5 gold)
-2. Look for something else.
-```
-
-Finally, when you buy something, a brief message should pop up:
-
-```
-You pay 5 gold and purchase A rusty sword!
-```
-or
-```
-You cannot afford 5 gold for A rusty sword!
-```
-After this you should be back to the top level of the shopping menu again and can continue browsing.
-
-### Coding the menu
-
-EvMenu defines the *nodes* (each menu screen with options) as normal Python functions. Each node
-must be able to change on the fly depending on what items are currently for sale. EvMenu will
-automatically make the `quit` command available to us so we won't add that manually. For compactness
-we will put everything needed for our shop in one module, `mygame/typeclasses/npcshop.py`.
-
-```python
-# mygame/typeclasses/npcshop.py
-
-from evennia.utils import evmenu
-
-def menunode_shopfront(caller):
- "This is the top-menu screen."
-
- shopname = caller.location.key
- wares = caller.location.db.storeroom.contents
-
- # Wares includes all items inside the storeroom, including the
- # door! Let's remove that from our for sale list.
- wares = [ware for ware in wares if ware.key.lower() != "door"]
-
- text = f"*** Welcome to {shopname}! ***\n"
- if wares:
- text += f" Things for sale (choose 1-{len(wares)} to inspect); quit to exit:"
- else:
- text += " There is nothing for sale; quit to exit."
-
- options = []
- for ware in wares:
- # add an option for every ware in store
- gold_val = ware.db.gold_value or 1
- options.append({"desc": f"{ware.key} ({gold_val} gold)",
- "goto": "menunode_inspect_and_buy"})
- return text, options
-```
-
-In this code we assume the caller to be *inside* the shop when accessing the menu. This means we can
-access the shop room via `caller.location` and get its `key` to display as the shop's name. We also
-assume the shop has an Attribute `storeroom` we can use to get to our stock. We loop over our goods
-to build up the menu's options.
-
-Note that *all options point to the same menu node* called `menunode_inspect_and_buy`! We can't know
-which goods will be available to sale so we rely on this node to modify itself depending on the
-circumstances. Let's create it now.
-
-```python
-# further down in mygame/typeclasses/npcshop.py
-
-def menunode_inspect_and_buy(caller, raw_string):
- "Sets up the buy menu screen."
-
- wares = caller.location.db.storeroom.contents
- # Don't forget, we will need to remove that pesky door again!
- wares = [ware for ware in wares if ware.key.lower() != "door"]
- iware = int(raw_string) - 1
- ware = wares[iware]
- value = ware.db.gold_value or 1
- wealth = caller.db.gold or 0
- text = f"You inspect {ware.key}:\n\n{ware.db.desc}"
-
- def buy_ware_result(caller):
- "This will be executed first when choosing to buy."
- if wealth >= value:
- rtext = f"You pay {value} gold and purchase {ware.key}!"
- caller.db.gold -= value
- ware.move_to(caller, quiet=True, move_type="buy")
- else:
- rtext = f"You cannot afford {value} gold for {ware.key}!"
- caller.msg(rtext)
-
- gold_val = ware.db.gold_value or 1
- options = ({
- "desc": f"Buy {ware.key} for {gold_val} gold",
- "goto": "menunode_shopfront",
- "exec": buy_ware_result,
- }, {
- "desc": "Look for something else",
- "goto": "menunode_shopfront",
- })
-
- return text, options
-```
-
-In this menu node we make use of the `raw_string` argument to the node. This is the text the menu
-user entered on the *previous* node to get here. Since we only allow numbered options in our menu,
-`raw_input` must be an number for the player to get to this point. So we convert it to an integer
-index (menu lists start from 1, whereas Python indices always starts at 0, so we need to subtract
-1). We then use the index to get the corresponding item from storage.
-
-We just show the customer the `desc` of the item. In a more elaborate setup you might want to show
-things like weapon damage and special stats here as well.
-
-When the user choose the "buy" option, EvMenu will execute the `exec` instruction *before* we go
-back to the top node (the `goto` instruction). For this we make a little inline function
-`buy_ware_result`. EvMenu will call the function given to `exec` like any menu node but it does not
-need to return anything. In `buy_ware_result` we determine if the customer can afford the cost and
-give proper return messages. This is also where we actually move the bought item into the inventory
-of the customer.
-
-### The command to start the menu
-
-We could *in principle* launch the shopping menu the moment a customer steps into our shop room, but
-this would probably be considered pretty annoying. It's better to create a [Command](../Components/Commands.md) for
-customers to explicitly wanting to shop around.
-
-```python
-# mygame/typeclasses/npcshop.py
-
-from evennia import Command
-
-class CmdBuy(Command):
- """
- Start to do some shopping
-
- Usage:
- buy
- shop
- browse
-
- This will allow you to browse the wares of the
- current shop and buy items you want.
- """
- key = "buy"
- aliases = ("shop", "browse")
-
- def func(self):
- "Starts the shop EvMenu instance"
- evmenu.EvMenu(self.caller,
- "typeclasses.npcshop",
- startnode="menunode_shopfront")
-```
-
-This will launch the menu. The `EvMenu` instance is initialized with the path to this very module -
-since the only global functions available in this module are our menu nodes, this will work fine
-(you could also have put those in a separate module). We now just need to put this command in a
-[CmdSet](../Components/Command-Sets.md) so we can add it correctly to the game:
-
-```python
-from evennia import CmdSet
-
-class ShopCmdSet(CmdSet):
- def at_cmdset_creation(self):
- self.add(CmdBuy())
-```
-
-## Building the shop
-
-There are really only two things that separate our shop from any other Room:
-
-- The shop has the `storeroom` Attribute set on it, pointing to a second (completely normal) room.
-- It has the `ShopCmdSet` stored on itself. This makes the `buy` command available to users entering
-the shop.
-
-For testing we could easily add these features manually to a room using `@py` or other admin
-commands. Just to show how it can be done we'll instead make a custom [Typeclass](../Components/Typeclasses.md) for
-the shop room and make a small command that builders can use to build both the shop and the
-storeroom at once.
-
-```python
-# bottom of mygame/typeclasses/npcshop.py
-
-from evennia import DefaultRoom, DefaultExit, DefaultObject
-from evennia.utils.create import create_object
-
-# class for our front shop room
-class NPCShop(DefaultRoom):
- def at_object_creation(self):
- # we could also use add(ShopCmdSet, persistent=True)
- self.cmdset.add_default(ShopCmdSet)
- self.db.storeroom = None
-
-# command to build a complete shop (the Command base class
-# should already have been imported earlier in this file)
-class CmdBuildShop(Command):
- """
- Build a new shop
-
- Usage:
- @buildshop shopname
-
- This will create a new NPCshop room
- as well as a linked store room (named
- simply -storage) for the
- wares on sale. The store room will be
- accessed through a locked door in
- the shop.
- """
- key = "@buildshop"
- locks = "cmd:perm(Builders)"
- help_category = "Builders"
-
- def func(self):
- "Create the shop rooms"
- if not self.args:
- self.msg("Usage: @buildshop ")
- return
- # create the shop and storeroom
- shopname = self.args.strip()
- shop = create_object(NPCShop,
- key=shopname,
- location=None)
- storeroom = create_object(DefaultRoom,
- key=f"{shopname}-storage",
- location=None)
- shop.db.storeroom = storeroom
- # create a door between the two
- shop_exit = create_object(DefaultExit,
- key="back door",
- aliases=["storage", "store room"],
- location=shop,
- destination=storeroom)
- storeroom_exit = create_object(DefaultExit,
- key="door",
- location=storeroom,
- destination=shop)
- # make a key for accessing the store room
- storeroom_key_name = f"{shopname}-storekey"
- storeroom_key = create_object(DefaultObject,
- key=storeroom_key_name,
- location=shop)
- # only allow chars with this key to enter the store room
- shop_exit.locks.add(f"traverse:holds({storeroom_key_name})")
-
- # inform the builder about progress
- self.caller.msg(f"The shop {shop} was created!")
-```
-
-Our typeclass is simple and so is our `buildshop` command. The command (which is for Builders only)
-just takes the name of the shop and builds the front room and a store room to go with it (always
-named `"-storage"`. It connects the rooms with a two-way exit. You need to add
-`CmdBuildShop` [to the default cmdset](Starting/Adding-Command-Tutorial#step-2-adding-the-command-to-a-
-default-cmdset) before you can use it. Once having created the shop you can now `@teleport` to it or
-`@open` a new exit to it. You could also easily expand the above command to automatically create
-exits to and from the new shop from your current location.
-
-To avoid customers walking in and stealing everything, we create a [Lock](../Components/Locks.md) on the storage
-door. It's a simple lock that requires the one entering to carry an object named
-`-storekey`. We even create such a key object and drop it in the shop for the new shop
-keeper to pick up.
-
-> If players are given the right to name their own objects, this simple lock is not very secure and
-you need to come up with a more robust lock-key solution.
-
-> We don't add any descriptions to all these objects so looking "at" them will not be too thrilling.
-You could add better default descriptions as part of the `@buildshop` command or leave descriptions
-this up to the Builder.
-
-## The shop is open for business!
-
-We now have a functioning shop and an easy way for Builders to create it. All you need now is to
-`@open` a new exit from the rest of the game into the shop and put some sell-able items in the store
-room. Our shop does have some shortcomings:
-
-- For Characters to be able to buy stuff they need to also have the `gold` Attribute set on
-themselves.
-- We manually remove the "door" exit from our items for sale. But what if there are other unsellable
-items in the store room? What if the shop owner walks in there for example - anyone in the store
-could then buy them for 1 gold.
-- What if someone else were to buy the item we're looking at just before we decide to buy it? It
-would then be gone and the counter be wrong - the shop would pass us the next item in the list.
-
-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.md) to restock the shop's
-store room regularly. This shop example could also easily be owned by a human Player (run for them
-by a hired NPC) - the shop owner would get the key to the store room and be responsible for keeping
-it well stocked.
diff --git a/docs/1.0-dev/_sources/Howtos/Static-In-Game-Map.md.txt b/docs/1.0-dev/_sources/Howtos/Static-In-Game-Map.md.txt
index 6ce98fd873..094c142836 100644
--- a/docs/1.0-dev/_sources/Howtos/Static-In-Game-Map.md.txt
+++ b/docs/1.0-dev/_sources/Howtos/Static-In-Game-Map.md.txt
@@ -412,5 +412,5 @@ easily new game defining features can be added to Evennia.
You can easily build from this tutorial by expanding the map and creating more rooms to explore. Why
not add more features to your game by trying other tutorials: [Add weather to your world](Weather-
-Tutorial), [fill your world with NPC's](./Tutorial-Aggressive-NPCs.md) or
+Tutorial), [fill your world with NPC's](./Tutorial-NPC-Reacting.md) or
[implement a combat system](./Turn-based-Combat-System.md).
diff --git a/docs/1.0-dev/_sources/Howtos/Tutorial-Aggressive-NPCs.md.txt b/docs/1.0-dev/_sources/Howtos/Tutorial-Aggressive-NPCs.md.txt
deleted file mode 100644
index f147fdd6b6..0000000000
--- a/docs/1.0-dev/_sources/Howtos/Tutorial-Aggressive-NPCs.md.txt
+++ /dev/null
@@ -1,126 +0,0 @@
-# Tutorial Aggressive NPCs
-
-
-This tutorial shows the implementation of an NPC object that responds to characters entering their
-location. In this example the NPC has the option to respond aggressively or not, but any actions
-could be triggered this way.
-
-One could imagine using a [Script](../Components/Scripts.md) that is constantly checking for newcomers. This would be
-highly inefficient (most of the time its check would fail). Instead we handle this on-demand by
-using a couple of existing object hooks to inform the NPC that a Character has entered.
-
-It is assumed that you already know how to create custom room and character typeclasses, please see
-the [Basic Game tutorial](./Tutorial-for-basic-MUSH-like-game.md) if you haven't already done this.
-
-What we will need is the following:
-
-- An NPC typeclass that can react when someone enters.
-- A custom [Room](../Components/Objects.md#rooms) typeclass that can tell the NPC that someone entered.
-- We will also tweak our default `Character` typeclass a little.
-
-To begin with, we need to create an NPC typeclass. Create a new file inside of your typeclasses
-folder and name it `npcs.py` and then add the following code:
-
-```python
-from typeclasses.characters import Character
-
-class NPC(Character):
- """
- A NPC typeclass which extends the character class.
- """
- def at_char_entered(self, character):
- """
- A simple is_aggressive check.
- Can be expanded upon later.
- """
- if self.db.is_aggressive:
- self.execute_cmd(f"say Graaah, die {character}!")
- else:
- self.execute_cmd(f"say Greetings, {character}!")
-```
-
-We will define our custom `Character` typeclass below. As for the new `at_char_entered` method we've
-just defined, we'll ensure that it will be called by the room where the NPC is located, when a
-player enters that room. You'll notice that right now, the NPC merely speaks. You can expand this
-part as you like and trigger all sorts of effects here (like combat code, fleeing, bartering or
-quest-giving) as your game design dictates.
-
-Now your `typeclasses.rooms` module needs to have the following added:
-
-```python
-# Add this import to the top of your file.
-from evennia import utils
-
- # Add this hook in any empty area within your Room class.
- def at_object_receive(self, obj, source_location):
- if utils.inherits_from(obj, 'typeclasses.npcs.NPC'): # An NPC has entered
- return
- elif utils.inherits_from(obj, 'typeclasses.characters.Character'):
- # A PC has entered.
- # Cause the player's character to look around.
- obj.execute_cmd('look')
- for item in self.contents:
- if utils.inherits_from(item, 'typeclasses.npcs.NPC'):
- # An NPC is in the room
- item.at_char_entered(obj)
-```
-
-`inherits_from` must be given the full path of the class. If the object inherited a class from your
-`world.races` module, then you would check inheritance with `world.races.Human`, for example. There
-is no need to import these prior, as we are passing in the full path. As a matter of a fact,
-`inherits_from` does not properly work if you import the class and only pass in the name of the
-class.
-
-> Note:
-[at_object_receive](https://github.com/evennia/evennia/blob/master/evennia/objects/objects.py#L1529)
-is a default hook of the `DefaultObject` typeclass (and its children). Here we are overriding this
-hook in our customized room typeclass to suit our needs.
-
-This room checks the typeclass of objects entering it (using `utils.inherits_from` and responds to
-`Characters`, ignoring other NPCs or objects. When triggered the room will look through its
-contents and inform any `NPCs inside by calling their `at_char_entered` method.
-
-You'll also see that we have added a 'look' into this code. This is because, by default, the
-`at_object_receive` is carried out *before* the character's `at_post_move` which, we will now
-overload. This means that a character entering would see the NPC perform its actions before the
-'look' command. Deactivate the look command in the default `Character` class within the
-`typeclasses.characters` module:
-
-```python
- # Add this hook in any blank area within your Character class.
- def at_post_move(self, source_location):
- """
- Default is to look around after a move
- Note: This has been moved to Room.at_object_receive
- """
- # self.execute_cmd('look')
-```
-
-Now let's create an NPC and make it aggressive. Type the following commands into your MUD client:
-```
-reload
-create/drop Orc:npcs.NPC
-```
-
-> Note: You could also give the path as `typeclasses.npcs.NPC`, but Evennia will look into the
-`typeclasses` folder automatically, so this is a little shorter.
-
-When you enter the aggressive NPC's location, it will default to using its peaceful action (say your
-name is Anna):
-
-```
-Orc says, "Greetings, Anna!"
-```
-
-Now we turn on the aggressive mode (we do it manually but it could also be triggered by some sort of
-AI code).
-
-```
-set orc/is_aggressive = True
-```
-
-Now it will perform its aggressive action whenever a character enters.
-
-```
-Orc says, "Graaah, die, Anna!"
-```
diff --git a/docs/1.0-dev/_sources/Howtos/Tutorial-NPCs-listening.md.txt b/docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Listening.md.txt
similarity index 59%
rename from docs/1.0-dev/_sources/Howtos/Tutorial-NPCs-listening.md.txt
rename to docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Listening.md.txt
index f658b8a4d3..f218ef96d6 100644
--- a/docs/1.0-dev/_sources/Howtos/Tutorial-NPCs-listening.md.txt
+++ b/docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Listening.md.txt
@@ -1,19 +1,16 @@
-# Tutorial NPCs listening
+# NPCs that listen to what is said
+ > say hi
+ You say, "hi"
+ The troll under the bridge answers, "well, well. Hello."
-This tutorial shows the implementation of an NPC object that responds to characters speaking in
-their location. In this example the NPC parrots what is said, but any actions could be triggered
-this way.
-
-It is assumed that you already know how to create custom room and character typeclasses, please see
-the [Basic Game tutorial](./Tutorial-for-basic-MUSH-like-game.md) if you haven't already done this.
-
-What we will need is simply a new NPC typeclass that can react when someone speaks.
+This howto explains how to make an NPC that reacts to characters speaking in their current location. The principle applies to other situations, such as enemies joining a fight or reacting to a character drawing a weapon.
```python
# mygame/typeclasses/npc.py
from characters import Character
+
class Npc(Character):
"""
A NPC typeclass which extends the character class.
@@ -32,11 +29,18 @@ class Npc(Character):
return f"{from_obj} said: '{message}'"
```
+We add a simple method `at_heard_say` that formats what it hears. We assume that the message that enters it is on the form `Someone says, "Hello"`, and we make sure to only get `Hello` in that example.
+
+We are not actually calling `at_heard_say` yet. We'll handle that next.
+
When someone in the room speaks to this NPC, its `msg` method will be called. We will modify the
NPCs `.msg` method to catch says so the NPC can respond.
-```python
+```{code-block} python
+:linenos:
+:emphasize-lines:
+
# mygame/typeclasses/npc.py
from characters import Character
@@ -70,26 +74,15 @@ class Npc(Character):
So if the NPC gets a say and that say is not coming from the NPC itself, it will echo it using the
`at_heard_say` hook. Some things of note in the above example:
-- The `text` input can be on many different forms depending on where this `msg` is called from.
-Instead of trying to analyze `text` in detail with a range of `if` statements we just assume the
-form we want and catch the error if it does not match. This simplifies the code considerably. It's
-called 'leap before you look' and is a Python paradigm that may feel unfamiliar if you are used to
-other languages. Here we 'swallow' the error silently, which is fine when the code checked is
-simple. If not we may want to import `evennia.logger.log_trace` and add `log_trace()` in the
-`except` clause.
-- We use `execute_cmd` to fire the `say` command back. We could also have called
-`self.location.msg_contents` directly but using the Command makes sure all hooks are called (so
-those seeing the NPC's `say` can in turn react if they want).
-- Note the comments about `super` at the end. This will trigger the 'default' `msg` (in the parent
-class) as well. It's not really necessary as long as no one puppets the NPC (by `@ic `) but
-it's wise to keep in there since the puppeting player will be totally blind if `msg()` is never
-returning anything to them!
+- **Line 15** The `text` input can be on many different forms depending on where this `msg` is called from. If you look at the [code of the 'say' command](evennia.commands.default.general.CmdSay) you'd find that it will call `.msg` with `("Hello", {"type": "say"})`. We use this knowledge to figure out if this comes from a `say` or not.
+- **Line 24**: We use `execute_cmd` to fire the NPCs own `say` command back. This works because the NPC is actually a child of `DefaultCharacter` - so it has the `CharacterCmdSet` on it! Normally you should use `execute_cmd` only sparingly; it's usually more efficient to call the actual code used by the Command directly. For this tutorial, invoking the command is shorter to write while making sure all hooks are called
+- **Line26**: Note the comments about `super` at the end. This will trigger the 'default' `msg` (in the parent class) as well. It's not really necessary as long as no one puppets the NPC (by `@ic `) but it's wise to keep in there since the puppeting player will be totally blind if `msg()` is never returning anything to them!
Now that's done, let's create an NPC and see what it has to say for itself.
```
-@reload
-@create/drop Guild Master:npc.Npc
+reload
+create/drop Guild Master:npc.Npc
```
(you could also give the path as `typeclasses.npc.Npc`, but Evennia will look into the `typeclasses`
@@ -106,5 +99,4 @@ There are many ways to implement this kind of functionality. An alternative exam
sending to an NPC and call the `at_heard_say` hook directly.
While the tutorial solution has the advantage of being contained only within the NPC class,
-combining this with using the Character class gives more direct control over how the NPC will react.
-Which way to go depends on the design requirements of your particular game.
\ No newline at end of file
+combining this with using the Character class gives more direct control over how the NPC will react. Which way to go depends on the design requirements of your particular game.
\ No newline at end of file
diff --git a/docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Merchants.md.txt b/docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Merchants.md.txt
new file mode 100644
index 0000000000..89b8d033cf
--- /dev/null
+++ b/docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Merchants.md.txt
@@ -0,0 +1,276 @@
+# NPC merchants
+
+```
+*** Welcome to ye Old Sword shop! ***
+ Things for sale (choose 1-3 to inspect, quit to exit):
+_________________________________________________________
+1. A rusty sword (5 gold)
+2. A sword with a leather handle (10 gold)
+3. Excalibur (100 gold)
+```
+
+This will introduce an NPC able to sell things. In practice this means that when you interact with them you'll get shown a _menu_ of choices. Evennia provides the [EvMenu](../Components/EvMenu.md) utility to easily create in-game menus.
+
+We will store all the merchant's wares in their inventory. This means that they may stand in an actual shop room, at a market or wander the road. We will also use 'gold' as an example currency.
+To enter the shop, you'll just need to stand in the same room and use the `buy/shop` command.
+
+## Making the merchant class
+
+The merchant will respond to you giving the `shop` or `buy` command in their presence.
+
+```python
+# in for example mygame/typeclasses/merchants.py
+
+from typeclasses.objects import Object
+from evennia import Command, CmdSet, EvMenu
+
+class CmdOpenShop(Command):
+ """
+ Open the shop!
+
+ Usage:
+ shop/buy
+
+ """
+ key = "shop"
+ aliases = ["buy"]
+
+ def func(self):
+ # this will sit on the Merchant, which is self.obj.
+ # the self.caller is the player wanting to buy stuff.
+ self.obj.open_shop(self.caller)
+
+
+class MerchantCmdSet(CmdSet):
+ def at_cmdset_creation(self):
+ self.add(CmdOpenShop())
+
+
+class NPCMerchant(Object):
+
+ def at_object_creation(self):
+ self.cmdset.add_default(MerchantCmdSet)
+
+ def open_shop(self, shopper):
+ menunodes = {} # TODO!
+ shopname = self.db.shopname or "The shop"
+ EvMenu(shopper, menunodes, startnode="shop_start",
+ shopname=shopname, shopkeeper=self, wares=self.contents)
+
+```
+
+We could also have put the commands in a separate module, but for compactness, we put it all with the merchant typeclass.
+
+Note that we make the merchant an `Object`! Since we don't give them any other commands, it makes little sense to let them be a `Character`.
+
+We make a very simple `shop`/`buy` Command and make sure to add it on the merchant in its own cmdset.
+
+We initialize `EvMenu` on the `shopper` but we haven't created any `menunodes` yet, so this will not actually do much at this point. It's important that we we pass `shopname`, `shopkeeper` and `wares` into the menu, it means they will be made available as properties on the EvMenu instance - we will be able to access them from inside the menu.
+
+## Coding the shopping menu
+
+[EvMenu](../Components/EvMenu.md) splits the menu into _nodes_ represented by Python functions. Each node represents a stop in the menu where the user has to make a choice.
+
+For simplicity, we'll code the shop interface above the `NPCMerchant` class in the same module.
+
+The start node of the shop named "ye Old Sword shop!" will look like this if there are only 3 wares to sell:
+
+```
+*** Welcome to ye Old Sword shop! ***
+ Things for sale (choose 1-3 to inspect, quit to exit):
+_________________________________________________________
+1. A rusty sword (5 gold)
+2. A sword with a leather handle (10 gold)
+3. Excalibur (100 gold)
+```
+
+
+```python
+# in mygame/typeclasses/merchants.py
+
+# top of module, above NPCMerchant class.
+
+def node_shopfront(caller, raw_string, **kwargs):
+ "This is the top-menu screen."
+
+ # made available since we passed them to EvMenu on start
+ menu = caller.ndb._evmenu
+ shopname = menu.shopname
+ shopkeeper = menu.shopkeeper
+ wares = menu.wares
+
+ text = f"*** Welcome to {shopname}! ***\n"
+ if wares:
+ text += f" Things for sale (choose 1-{len(wares)} to inspect); quit to exit:"
+ else:
+ text += " There is nothing for sale; quit to exit."
+
+ options = []
+ for ware in wares:
+ # add an option for every ware in store
+ gold_val = ware.db.gold_value or 1
+ options.append({"desc": f"{ware.key} ({gold_val} gold)",
+ "goto": ("inspect_and_buy",
+ {"selected_ware": ware})
+ })
+
+ return text, options
+```
+
+Inside the node we can access the menu on the caller as `caller.ndb._evmenu`. The extra keywords we passed into `EvMenu` are available on this menu instance. Armed with this we can easily present a shop interface. Each option will become a numbered choice on this screen.
+
+Note how we pass the `ware` with each option and label it `selected_ware`. This will be accessible in the next node's `**kwargs` argument
+
+If a player choose one of the wares, they should be able to inspect it. Here's how it should look if they selected `1` in ye Old Sword shop:
+
+```
+You inspect A rusty sword:
+
+This is an old weapon maybe once used by soldiers in some
+long forgotten army. It is rusty and in bad condition.
+__________________________________________________________
+1. Buy A rusty sword (5 gold)
+2. Look for something else.
+```
+
+If you buy, you'll see
+
+```
+You pay 5 gold and purchase A rusty sword!
+```
+or
+```
+You cannot afford 5 gold for A rusty sword!
+```
+
+Either way you should end up back at the top level of the shopping menu again and can continue browsing or quit the menu with `quit`.
+
+Here's how it looks in code:
+
+```python
+# in mygame/typeclasses/merchants.py
+
+# right after the other node
+
+def _buy_item(caller, raw_string, **kwargs):
+ "Called if buyer chooses to buy"
+ selected_ware = kwargs["selected_ware"]
+ value = selected_ware.db.gold_value or 1
+ wealth = caller.db.gold or 0
+
+ if wealth >= value:
+ rtext = f"You pay {value} gold and purchase {ware.key}!"
+ caller.db.gold -= value
+ move_to(caller, quiet=True, move_type="buy")
+ else:
+ rtext = f"You cannot afford {value} gold for {ware.key}!"
+ caller.msg(rtext)
+ # no matter what, we return to the top level of the shop
+ return "shopfront"
+
+def node_inspect_and_buy(caller, raw_string, **kwargs):
+ "Sets up the buy menu screen."
+
+ # passed from the option we chose
+ selected_ware = kwargs["selected_ware"]
+
+ value = selected_ware.db.gold_value or 1
+ text = f"You inspect {ware.key}:\n\n{ware.db.desc}"
+ gold_val = ware.db.gold_value or 1
+
+ options = ({
+ "desc": f"Buy {ware.key} for {gold_val} gold",
+ "goto": (_buy_item, kwargs)
+ }, {
+ "desc": "Look for something else",
+ "goto": "shopfront",
+ })
+ return text, options
+```
+
+In this node we grab the `selected_ware` from `kwargs` - this we pased along from the option on the previous node. We display its description and value. If the user buys, we reroute through the `_buy_item` helper function (this is not a node, it's just a callable that must return the name of the next node to go to.). In `_buy_item` we check if the buyer can affort the ware, and if it can we move it to their inventory. Either way, this method returns `shop_front` as the next node.
+
+We have been referring to two nodes here: `"shopfront"` and `"inspect_and_buy"` , we should map them to the code in the menu. Scroll down to the `NPCMerchant` class in the same module and find that unfinished `open_shop` method again:
+
+
+```python
+# in /mygame/typeclasses/merchants.py
+
+def node_shopfront(caller, raw_string, **kwargs):
+ # ...
+
+def _buy_item(caller, raw_string, **kwargs):
+ # ...
+
+def node_inspect_and_buy(caller, raw_string, **kwargs):
+ # ...
+
+class NPCMerchant(Object):
+
+ # ...
+
+ def open_shop(self, shopper):
+ menunodes = {
+ "shopfront": node_shopfront,
+ "inspect_and_buy": node_inspect_and_buy
+ }
+ shopname = self.db.shopname or "The shop"
+ EvMenu(shopper, menunodes, startnode="shop_start",
+ shopname=shopname, shopkeeper=self, wares=self.contents)
+
+```
+
+
+We now added the nodes to the Evmenu under their right labels. The merchant is now ready!
+
+
+## The shop is open for business!
+
+Make sure to `reload`.
+
+Let's try it out by creating the merchant and a few wares in-game. Remember that we also must create some gold get this economy going.
+
+```
+> set self/gold = 8
+
+> create/drop Stan S. Stanman;stan:typeclasses.merchants.NPCMerchant
+> set stan/shopname = Stan's previously owned vessles
+
+> create/drop A proud vessel;ship
+> set ship/desc = The thing has holes in it.
+> set ship/gold_value = 5
+
+> create/drop A classic speedster;rowboat
+> set rowboat/gold_value = 2
+> set rowboat/desc = It's not going anywhere fast.
+```
+
+Note that a builder without any access to Python code can now set up a personalized merchant with just in-game commands. With the shop all set up, we just need to be in the same room to start consuming!
+
+```
+> buy
+*** Welcome to Stan's previously owned vessels! ***
+ Things for sale (choose 1-3 to inspect, quit to exit):
+_________________________________________________________
+1. A proud vessel (5 gold)
+2. A classic speedster (2 gold)
+
+> 1
+
+You inspect A proud vessel:
+
+The thing has holes in it.
+__________________________________________________________
+1. Buy A proud vessel (5 gold)
+2. Look for something else.
+
+> 1
+You pay 5 gold and purchase A proud vessel!
+
+*** Welcome to Stan's previously owned vessels! ***
+ Things for sale (choose 1-3 to inspect, quit to exit):
+_________________________________________________________
+1. A classic speedster (2 gold)
+
+```
+
diff --git a/docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Reacting.md.txt b/docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Reacting.md.txt
new file mode 100644
index 0000000000..0e4fc20716
--- /dev/null
+++ b/docs/1.0-dev/_sources/Howtos/Tutorial-NPC-Reacting.md.txt
@@ -0,0 +1,88 @@
+# NPCs reacting to your presence
+
+
+ > north
+ ------------------------------------
+ Meadow
+ You are standing in a green meadow.
+ A bandit is here.
+ ------------------------------------
+ Bandit gives you a menacing look!
+
+This tutorial shows the implementation of an NPC object that responds to characters entering their
+location.
+
+What we will need is the following:
+
+- An NPC typeclass that can react when someone enters.
+- A custom [Room](../Components/Objects.md#rooms) typeclass that can tell the NPC that someone entered.
+- We will also tweak our default `Character` typeclass a little.
+
+```python
+# in mygame/typeclasses/npcs.py (for example)
+
+from typeclasses.characters import Character
+
+class NPC(Character):
+ """
+ A NPC typeclass which extends the character class.
+ """
+ def at_char_entered(self, character):
+ """
+ A simple is_aggressive check.
+ Can be expanded upon later.
+ """
+ if self.db.is_aggressive:
+ self.execute_cmd(f"say Graaah! Die, {character}!")
+ else:
+ self.execute_cmd(f"say Greetings, {character}!")
+```
+
+Here we make a simple method on the `NPC`˙. We expect it to be called when a (player-)character enters the room. We don't actually set the `is_aggressive` [Attribute](../Components/Attributes.md) beforehand; if it's not set, the NPC is simply non-hostile.
+
+Whenever _something_ enters the `Room`, its [at_object_receive](DefaultObject.at_object_receive) hook will be called. So we should override it.
+
+
+```python
+# in mygame/typeclasses/rooms.py
+
+from evennia import utils
+
+# ...
+
+class Room(ObjectParent, DefaultRoom):
+
+ # ...
+
+ def at_object_receive(self, arriving_obj, source_location):
+ if arriving_obj.account:
+ # this has an active acccount - a player character
+ for item in self.contents:
+ # get all npcs in the room and inform them
+ if utils.inherits_from(item, "typeclasses.npcs.NPC"):
+ self.at_char_entered(arriving_obj)
+
+```
+
+```{sidebar} Universal Object methods
+Remember that Rooms are `Objects`. So the same `at_object_receive` hook will fire for you when you pick something up (making you 'receive' it). Or for a box when putting something inside it.
+```
+A currently puppeted Character will have an `.account` attached to it. We use that to know that the thing arriving is a Character. We then use Evennia's [utils.inherits_from](evennia.utils.utils.inherits_from) helper utility to get every NPC in the room can each of their newly created `at_char_entered` method.
+
+Make sure to `reload`.
+
+Let's create an NPC and make it aggressive. For the sake of this example, let's assume your name is "Anna" and that there is a room to the north of your current location.
+
+ > create/drop Orc:typeclasses.npcs.NPC
+ > north
+ > south
+ Orc says, Greetings, Anna!
+
+Now let's turn the orc aggressive.
+
+ > set orc/is_aggressive = True
+ > north
+ > south
+ Orc says, Graah! Die, Anna!
+
+That's one easily aggravated Orc!
\ No newline at end of file
diff --git a/docs/1.0-dev/api/evennia.commands.default.building.html b/docs/1.0-dev/api/evennia.commands.default.building.html
index 8fa17643c5..3266b10c8d 100644
--- a/docs/1.0-dev/api/evennia.commands.default.building.html
+++ b/docs/1.0-dev/api/evennia.commands.default.building.html
@@ -592,7 +592,7 @@ You can specify the /force switch to bypass this confirmation.
@@ -633,7 +633,7 @@ You can specify the /force switch to bypass this confirmation.
-search_index_entry = {'aliases': '@delete @del', 'category': 'building', 'key': '@destroy', 'no_prefix': 'destroy delete del', 'tags': '', 'text': '\n permanently delete objects\n\n Usage:\n destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]\n\n Switches:\n override - The destroy command will usually avoid accidentally\n destroying account objects. This switch overrides this safety.\n force - destroy without confirmation.\n Examples:\n destroy house, roof, door, 44-78\n destroy 5-10, flower, 45\n destroy/force north\n\n Destroys one or many objects. If dbrefs are used, a range to delete can be\n given, e.g. 4-10. Also the end points will be deleted. This command\n displays a confirmation before destroying, to make sure of your choice.\n You can specify the /force switch to bypass this confirmation.\n '}¶
+search_index_entry = {'aliases': '@del @delete', 'category': 'building', 'key': '@destroy', 'no_prefix': 'destroy del delete', 'tags': '', 'text': '\n permanently delete objects\n\n Usage:\n destroy[/switches] [obj, obj2, obj3, [dbref-dbref], ...]\n\n Switches:\n override - The destroy command will usually avoid accidentally\n destroying account objects. This switch overrides this safety.\n force - destroy without confirmation.\n Examples:\n destroy house, roof, door, 44-78\n destroy 5-10, flower, 45\n destroy/force north\n\n Destroys one or many objects. If dbrefs are used, a range to delete can be\n given, e.g. 4-10. Also the end points will be deleted. This command\n displays a confirmation before destroying, to make sure of your choice.\n You can specify the /force switch to bypass this confirmation.\n '}¶
-search_index_entry = {'aliases': '@typeclasses @parent @type @swap @update', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass typeclasses parent type swap update', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclasses or typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object. This will also\n reset cmdsets!\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}¶
+search_index_entry = {'aliases': '@swap @typeclasses @type @update @parent', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass swap typeclasses type update parent', 'tags': '', 'text': "\n set or change an object's typeclass\n\n Usage:\n typeclass[/switch] <object> [= typeclass.path]\n typeclass/prototype <object> = prototype_key\n\n typeclasses or typeclass/list/show [typeclass.path]\n swap - this is a shorthand for using /force/reset flags.\n update - this is a shorthand for using the /force/reload flag.\n\n Switch:\n show, examine - display the current typeclass of object (default) or, if\n given a typeclass path, show the docstring of that typeclass.\n update - *only* re-run at_object_creation on this object\n meaning locks or other properties set later may remain.\n reset - clean out *all* the attributes and properties on the\n object - basically making this a new clean object. This will also\n reset cmdsets!\n force - change to the typeclass also if the object\n already has a typeclass of the same name.\n list - show available typeclasses. Only typeclasses in modules actually\n imported or used from somewhere in the code will show up here\n (those typeclasses are still available if you know the path)\n prototype - clean and overwrite the object with the specified\n prototype key - effectively making a whole new object.\n\n Example:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\n If the typeclass_path is not given, the current object's typeclass is\n assumed.\n\n View or set an object's typeclass. If setting, the creation hooks of the\n new typeclass will be run on the object. If you have clashing properties on\n the old class, use /reset. By default you are protected from changing to a\n typeclass of the same name as the one you already have - use /force to\n override this protection.\n\n The given typeclass must be identified by its location using python\n dot-notation pointing to the correct module and class. If no typeclass is\n given (or a wrong typeclass is given). Errors in the path or new typeclass\n will lead to the old typeclass being kept. The location of the typeclass\n module is searched from the default typeclass directory, as defined in the\n server settings.\n\n "}¶
@@ -1531,7 +1531,7 @@ If object is not specified, the current location is examined.
@@ -1799,7 +1799,7 @@ the cases, see the module doc.
-search_index_entry = {'aliases': '@ex @exam', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine ex exam', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n script - examine a Script\n channel - examine a Channel\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}¶
+search_index_entry = {'aliases': '@exam @ex', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine exam ex', 'tags': '', 'text': '\n get detailed information about an object\n\n Usage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\n Switch:\n account - examine an Account (same as adding *)\n object - examine an Object (useful when OOC)\n script - examine a Script\n channel - examine a Channel\n\n The examine command shows detailed game info about an\n object and optionally a specific attribute on it.\n If object is not specified, the current location is examined.\n\n Append a * before the search string to examine an account.\n\n '}¶
diff --git a/docs/1.0-dev/api/evennia.commands.default.comms.html b/docs/1.0-dev/api/evennia.commands.default.comms.html
index c2f746c095..c78ef6328f 100644
--- a/docs/1.0-dev/api/evennia.commands.default.comms.html
+++ b/docs/1.0-dev/api/evennia.commands.default.comms.html
@@ -256,7 +256,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
-search_index_entry = {'aliases': '@channels @chan', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel channels chan', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶
+search_index_entry = {'aliases': '@chan @channels', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel chan channels', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶
@@ -935,7 +935,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
@@ -955,7 +955,7 @@ ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
-search_index_entry = {'aliases': '@channels @chan', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel channels chan', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶
+search_index_entry = {'aliases': '@chan @channels', 'category': 'comms', 'key': '@channel', 'no_prefix': 'channel chan channels', 'tags': '', 'text': "\n Use and manage in-game channels.\n\n Usage:\n channel channelname <msg>\n channel channel name = <msg>\n channel (show all subscription)\n channel/all (show available channels)\n channel/alias channelname = alias[;alias...]\n channel/unalias alias\n channel/who channelname\n channel/history channelname [= index]\n channel/sub channelname [= alias[;alias...]]\n channel/unsub channelname[,channelname, ...]\n channel/mute channelname[,channelname,...]\n channel/unmute channelname[,channelname,...]\n\n channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n channel/desc channelname = description\n channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n channel/ban channelname (list bans)\n channel/ban[/quiet] channelname[, channelname, ...] = subscribername [: reason]\n channel/unban[/quiet] channelname[, channelname, ...] = subscribername\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n\n # subtopics\n\n ## sending\n\n Usage: channel channelname msg\n channel channel name = msg (with space in channel name)\n\n This sends a message to the channel. Note that you will rarely use this\n command like this; instead you can use the alias\n\n channelname <msg>\n channelalias <msg>\n\n For example\n\n public Hello World\n pub Hello World\n\n (this shortcut doesn't work for aliases containing spaces)\n\n See channel/alias for help on setting channel aliases.\n\n ## alias and unalias\n\n Usage: channel/alias channel = alias[;alias[;alias...]]\n channel/unalias alias\n channel - this will list your subs and aliases to each channel\n\n Set one or more personal aliases for referencing a channel. For example:\n\n channel/alias warrior's guild = warrior;wguild;warchannel;warrior guild\n\n You can now send to the channel using all of these:\n\n warrior's guild Hello\n warrior Hello\n wguild Hello\n warchannel Hello\n\n Note that this will not work if the alias has a space in it. So the\n 'warrior guild' alias must be used with the `channel` command:\n\n channel warrior guild = Hello\n\n Channel-aliases can be removed one at a time, using the '/unalias' switch.\n\n ## who\n\n Usage: channel/who channelname\n\n List the channel's subscribers. Shows who are currently offline or are\n muting the channel. Subscribers who are 'muting' will not see messages sent\n to the channel (use channel/mute to mute a channel).\n\n ## history\n\n Usage: channel/history channel [= index]\n\n This will display the last |c20|n lines of channel history. By supplying an\n index number, you will step that many lines back before viewing those 20 lines.\n\n For example:\n\n channel/history public = 35\n\n will go back 35 lines and show the previous 20 lines from that point (so\n lines -35 to -55).\n\n ## sub and unsub\n\n Usage: channel/sub channel [=alias[;alias;...]]\n channel/unsub channel\n\n This subscribes you to a channel and optionally assigns personal shortcuts\n for you to use to send to that channel (see aliases). When you unsub, all\n your personal aliases will also be removed.\n\n ## mute and unmute\n\n Usage: channel/mute channelname\n channel/unmute channelname\n\n Muting silences all output from the channel without actually\n un-subscribing. Other channel members will see that you are muted in the /who\n list. Sending a message to the channel will automatically unmute you.\n\n ## create and destroy\n\n Usage: channel/create channelname[;alias;alias[:typeclass]] [= description]\n channel/destroy channelname [= reason]\n\n Creates a new channel (or destroys one you control). You will automatically\n join the channel you create and everyone will be kicked and loose all aliases\n to a destroyed channel.\n\n ## lock and unlock\n\n Usage: channel/lock channelname = lockstring\n channel/unlock channelname = lockstring\n\n Note: this is an admin command.\n\n A lockstring is on the form locktype:lockfunc(). Channels understand three\n locktypes:\n listen - who may listen or join the channel.\n send - who may send messages to the channel\n control - who controls the channel. This is usually the one creating\n the channel.\n\n Common lockfuncs are all() and perm(). To make a channel everyone can\n listen to but only builders can talk on, use this:\n\n listen:all()\n send: perm(Builders)\n\n ## boot and ban\n\n Usage:\n channel/boot[/quiet] channelname[,channelname,...] = subscribername [: reason]\n channel/ban channelname[, channelname, ...] = subscribername [: reason]\n channel/unban channelname[, channelname, ...] = subscribername\n channel/unban channelname\n channel/ban channelname (list bans)\n\n Booting will kick a named subscriber from channel(s) temporarily. The\n 'reason' will be passed to the booted user. Unless the /quiet switch is\n used, the channel will also be informed of the action. A booted user is\n still able to re-connect, but they'll have to set up their aliases again.\n\n Banning will blacklist a user from (re)joining the provided channels. It\n will then proceed to boot them from those channels if they were connected.\n The 'reason' and `/quiet` works the same as for booting.\n\n Example:\n boot mychannel1 = EvilUser : Kicking you to cool down a bit.\n ban mychannel1,mychannel2= EvilUser : Was banned for spamming.\n\n "}¶
diff --git a/docs/1.0-dev/api/evennia.commands.default.general.html b/docs/1.0-dev/api/evennia.commands.default.general.html
index 8433792efe..7ee6533e6a 100644
--- a/docs/1.0-dev/api/evennia.commands.default.general.html
+++ b/docs/1.0-dev/api/evennia.commands.default.general.html
@@ -268,7 +268,7 @@ for everyone to use, you need build privileges and the alias command.
@@ -300,7 +300,7 @@ for everyone to use, you need build privileges and the alias command.
-search_index_entry = {'aliases': 'nickname nicks', 'category': 'general', 'key': 'nick', 'no_prefix': ' nickname nicks', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}¶
+search_index_entry = {'aliases': 'nicks nickname', 'category': 'general', 'key': 'nick', 'no_prefix': ' nicks nickname', 'tags': '', 'text': '\n define a personal alias/nick by defining a string to\n match and replace it with another on the fly\n\n Usage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\n Switches:\n inputline - replace on the inputline (default)\n object - replace on object-lookup\n account - replace on account-lookup\n list - show all defined aliases (also "nicks" works)\n delete - remove nick by index in /list\n clearall - clear all nicks\n\n Examples:\n nick hi = say Hello, I\'m Sarah!\n nick/object tom = the tall man\n nick build $1 $2 = create/drop $1;$2\n nick tell $1 $2=page $1=$2\n nick tm?$1=page tallman=$1\n nick tm\\=$1=page tallman=$1\n\n A \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\n Put the last $-marker without an ending space to catch all remaining text. You\n can also use unix-glob matching for the left-hand side <string>:\n\n * - matches everything\n ? - matches 0 or 1 single characters\n [abcd] - matches these chars in any order\n [!abcd] - matches everything not among these chars\n \\= - escape literal \'=\' you want in your <string>\n\n Note that no objects are actually renamed or changed by this command - your nicks\n are only available to you. If you want to permanently add keywords to an object\n for everyone to use, you need build privileges and the alias command.\n\n '}¶
-search_index_entry = {'aliases': 'emote :', 'category': 'general', 'key': 'pose', 'no_prefix': ' emote :', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}¶
+search_index_entry = {'aliases': ': emote', 'category': 'general', 'key': 'pose', 'no_prefix': ' : emote', 'tags': '', 'text': "\n strike a pose\n\n Usage:\n pose <pose text>\n pose's <pose text>\n\n Example:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\n Describe an action being taken. The pose text will\n automatically begin with your name.\n "}¶
@@ -773,7 +773,7 @@ which permission groups you are a member of.
@@ -804,7 +804,7 @@ which permission groups you are a member of.
-search_index_entry = {'aliases': 'groups hierarchy', 'category': 'general', 'key': 'access', 'no_prefix': ' groups hierarchy', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}¶
+search_index_entry = {'aliases': 'hierarchy groups', 'category': 'general', 'key': 'access', 'no_prefix': ' hierarchy groups', 'tags': '', 'text': '\n show your current game access\n\n Usage:\n access\n\n This command shows you the permission hierarchy and\n which permission groups you are a member of.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.commands.default.system.html b/docs/1.0-dev/api/evennia.commands.default.system.html
index 972851c8a4..e450185933 100644
--- a/docs/1.0-dev/api/evennia.commands.default.system.html
+++ b/docs/1.0-dev/api/evennia.commands.default.system.html
@@ -683,7 +683,7 @@ See |luhttps://ww
@@ -729,7 +729,7 @@ to all the variables defined therein.
-search_index_entry = {'aliases': '@delays @task', 'category': 'system', 'key': '@tasks', 'no_prefix': 'tasks delays task', 'tags': '', 'text': "\n Display or terminate active tasks (delays).\n\n Usage:\n tasks[/switch] [task_id or function_name]\n\n Switches:\n pause - Pause the callback of a task.\n unpause - Process all callbacks made since pause() was called.\n do_task - Execute the task (call its callback).\n call - Call the callback of this task.\n remove - Remove a task without executing it.\n cancel - Stop a task from automatically executing.\n\n Notes:\n A task is a single use method of delaying the call of a function. Calls are created\n in code, using `evennia.utils.delay`.\n See |luhttps://www.evennia.com/docs/latest/Command-Duration.html|ltthe docs|le for help.\n\n By default, tasks that are canceled and never called are cleaned up after one minute.\n\n Examples:\n - `tasks/cancel move_callback` - Cancels all movement delays from the slow_exit contrib.\n In this example slow exits creates it's tasks with\n `utils.delay(move_delay, move_callback)`\n - `tasks/cancel 2` - Cancel task id 2.\n\n "}¶
+search_index_entry = {'aliases': '@task @delays', 'category': 'system', 'key': '@tasks', 'no_prefix': 'tasks task delays', 'tags': '', 'text': "\n Display or terminate active tasks (delays).\n\n Usage:\n tasks[/switch] [task_id or function_name]\n\n Switches:\n pause - Pause the callback of a task.\n unpause - Process all callbacks made since pause() was called.\n do_task - Execute the task (call its callback).\n call - Call the callback of this task.\n remove - Remove a task without executing it.\n cancel - Stop a task from automatically executing.\n\n Notes:\n A task is a single use method of delaying the call of a function. Calls are created\n in code, using `evennia.utils.delay`.\n See |luhttps://www.evennia.com/docs/latest/Command-Duration.html|ltthe docs|le for help.\n\n By default, tasks that are canceled and never called are cleaned up after one minute.\n\n Examples:\n - `tasks/cancel move_callback` - Cancels all movement delays from the slow_exit contrib.\n In this example slow exits creates it's tasks with\n `utils.delay(move_delay, move_callback)`\n - `tasks/cancel 2` - Cancel task id 2.\n\n "}¶
diff --git a/docs/1.0-dev/api/evennia.commands.default.tests.html b/docs/1.0-dev/api/evennia.commands.default.tests.html
index c6c8d54e6c..10278ee12a 100644
--- a/docs/1.0-dev/api/evennia.commands.default.tests.html
+++ b/docs/1.0-dev/api/evennia.commands.default.tests.html
@@ -902,7 +902,7 @@ main test suite started with
Test the batch processor.
-red_button = <module 'evennia.contrib.tutorials.red_button.red_button' from '/tmp/tmpetopgdxv/ba9ba34cd61c07c8a77381803c025c11cf345b21/evennia/contrib/tutorials/red_button/red_button.py'>¶
+red_button = <module 'evennia.contrib.tutorials.red_button.red_button' from '/tmp/tmp5j5rru7h/a15a8ae42888846c44f33882b196663ac29d8cb3/evennia/contrib/tutorials/red_button/red_button.py'>¶
@@ -157,7 +157,7 @@ there is no object yet before the account has logged in)
-search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'no_prefix': ' conn con co', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶
+search_index_entry = {'aliases': 'con conn co', 'category': 'general', 'key': 'connect', 'no_prefix': ' con conn co', 'tags': '', 'text': '\n connect to the game\n\n Usage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\n Use the create command to first create an account before logging in.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶
-search_index_entry = {'aliases': 'cre cr', 'category': 'general', 'key': 'create', 'no_prefix': ' cre cr', 'tags': '', 'text': '\n create a new account account\n\n Usage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\n This creates a new account account.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶
+search_index_entry = {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'no_prefix': ' cr cre', 'tags': '', 'text': '\n create a new account account\n\n Usage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\n This creates a new account account.\n\n If you have spaces in your name, enclose it in double quotes.\n '}¶
@@ -286,7 +286,7 @@ All it does is display the connect screen.
@@ -312,7 +312,7 @@ All it does is display the connect screen.
-search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' look l', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
+search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' l look', 'tags': '', 'text': '\n look when in unlogged-in state\n\n Usage:\n look\n\n This is an unconnected version of the look command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html
index 16788898f1..9288c091b2 100644
--- a/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html
+++ b/docs/1.0-dev/api/evennia.contrib.base_systems.email_login.email_login.html
@@ -139,7 +139,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE.
@@ -169,7 +169,7 @@ there is no object yet before the account has logged in)
-search_index_entry = {'aliases': 'conn con co', 'category': 'general', 'key': 'connect', 'no_prefix': ' conn con co', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}¶
+search_index_entry = {'aliases': 'con conn co', 'category': 'general', 'key': 'connect', 'no_prefix': ' con conn co', 'tags': '', 'text': '\n Connect to the game.\n\n Usage (at login screen):\n connect <email> <password>\n\n Use the create command to first create an account before logging in.\n '}¶
@@ -191,7 +191,7 @@ there is no object yet before the account has logged in)
@@ -317,7 +317,7 @@ All it does is display the connect screen.
-search_index_entry = {'aliases': 'look l', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' look l', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
+search_index_entry = {'aliases': 'l look', 'category': 'general', 'key': '__unloggedin_look_command', 'no_prefix': ' l look', 'tags': '', 'text': '\n This is an unconnected version of the `look` command for simplicity.\n\n This is called by the server and kicks everything in gear.\n All it does is display the connect screen.\n '}¶
-search_index_entry = {'aliases': 'delchanalias delaliaschan', 'category': 'comms', 'key': 'delcom', 'no_prefix': ' delchanalias delaliaschan', 'tags': '', 'text': "\n remove a channel alias and/or unsubscribe from channel\n\n Usage:\n delcom <alias or channel>\n delcom/all <channel>\n\n If the full channel name is given, unsubscribe from the\n channel. If an alias is given, remove the alias but don't\n unsubscribe. If the 'all' switch is used, remove all aliases\n for that channel.\n "}¶
+search_index_entry = {'aliases': 'delaliaschan delchanalias', 'category': 'comms', 'key': 'delcom', 'no_prefix': ' delaliaschan delchanalias', 'tags': '', 'text': "\n remove a channel alias and/or unsubscribe from channel\n\n Usage:\n delcom <alias or channel>\n delcom/all <channel>\n\n If the full channel name is given, unsubscribe from the\n channel. If an alias is given, remove the alias but don't\n unsubscribe. If the 'all' switch is used, remove all aliases\n for that channel.\n "}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html
index 5835b01d92..c5d086c488 100644
--- a/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html
+++ b/docs/1.0-dev/api/evennia.contrib.full_systems.evscaperoom.commands.html
@@ -211,7 +211,7 @@ the operation will be general or on the room.
-search_index_entry = {'aliases': 'quit chicken out q abort', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' quit chicken out q abort', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}¶
+search_index_entry = {'aliases': 'abort quit chicken out q', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' abort quit chicken out q', 'tags': '', 'text': '\n Give up\n\n Usage:\n give up\n\n Abandons your attempts at escaping and of ever winning the pie-eating contest.\n\n '}¶
-search_index_entry = {'aliases': 'pose :', 'category': 'general', 'key': 'emote', 'no_prefix': ' pose :', 'tags': '', 'text': '\n Perform a free-form emote. Use /me to\n include yourself in the emote and /name\n to include other objects or characters.\n Use "..." to enact speech.\n\n Usage:\n emote <emote>\n :<emote\n\n Example:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n '}¶
+search_index_entry = {'aliases': ': pose', 'category': 'general', 'key': 'emote', 'no_prefix': ' : pose', 'tags': '', 'text': '\n Perform a free-form emote. Use /me to\n include yourself in the emote and /name\n to include other objects or characters.\n Use "..." to enact speech.\n\n Usage:\n emote <emote>\n :<emote\n\n Example:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n '}¶
@@ -490,7 +490,7 @@ looks and what actions is available.
-search_index_entry = {'aliases': 'unfocus examine e ex', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' unfocus examine e ex', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}¶
+search_index_entry = {'aliases': 'e ex unfocus examine', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' e ex unfocus examine', 'tags': '', 'text': '\n Focus your attention on a target.\n\n Usage:\n focus <obj>\n\n Once focusing on an object, use look to get more information about how it\n looks and what actions is available.\n\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html b/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html
index f934d57a9e..c485bd9dc5 100644
--- a/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html
+++ b/docs/1.0-dev/api/evennia.contrib.rpg.dice.dice.html
@@ -305,7 +305,7 @@ everyone but the person rolling.
@@ -331,7 +331,7 @@ everyone but the person rolling.
-search_index_entry = {'aliases': 'roll @dice', 'category': 'general', 'key': 'dice', 'no_prefix': ' roll dice', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}¶
+search_index_entry = {'aliases': '@dice roll', 'category': 'general', 'key': 'dice', 'no_prefix': ' dice roll', 'tags': '', 'text': "\n roll dice\n\n Usage:\n dice[/switch] <nr>d<sides> [modifier] [success condition]\n\n Switch:\n hidden - tell the room the roll is being done, but don't show the result\n secret - don't inform the room about neither roll nor result\n\n Examples:\n dice 3d6 + 4\n dice 1d100 - 2 < 50\n\n This will roll the given number of dice with given sides and modifiers.\n So e.g. 2d6 + 3 means to 'roll a 6-sided die 2 times and add the result,\n then add 3 to the total'.\n Accepted modifiers are +, -, * and /.\n A success condition is given as normal Python conditionals\n (<,>,<=,>=,==,!=). So e.g. 2d6 + 3 > 10 means that the roll will succeed\n only if the final result is above 8. If a success condition is given, the\n outcome (pass/fail) will be echoed along with how much it succeeded/failed\n with. The hidden/secret switches will hide all or parts of the roll from\n everyone but the person rolling.\n "}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rpsystem.html b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rpsystem.html
index d9ebb1ed4d..bd424df5b5 100644
--- a/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rpsystem.html
+++ b/docs/1.0-dev/api/evennia.contrib.rpg.rpsystem.rpsystem.html
@@ -865,7 +865,7 @@ Using the command without arguments will list all current recogs.
@@ -892,7 +892,7 @@ Using the command without arguments will list all current recogs.
-search_index_entry = {'aliases': 'recognize forget', 'category': 'general', 'key': 'recog', 'no_prefix': ' recognize forget', 'tags': '', 'text': '\n Recognize another person in the same room.\n\n Usage:\n recog\n recog sdesc as alias\n forget alias\n\n Example:\n recog tall man as Griatch\n forget griatch\n\n This will assign a personal alias for a person, or forget said alias.\n Using the command without arguments will list all current recogs.\n\n '}¶
+search_index_entry = {'aliases': 'forget recognize', 'category': 'general', 'key': 'recog', 'no_prefix': ' forget recognize', 'tags': '', 'text': '\n Recognize another person in the same room.\n\n Usage:\n recog\n recog sdesc as alias\n forget alias\n\n Example:\n recog tall man as Griatch\n forget griatch\n\n This will assign a personal alias for a person, or forget said alias.\n Using the command without arguments will list all current recogs.\n\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html b/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html
index 52580ca17c..bf65957899 100644
--- a/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html
+++ b/docs/1.0-dev/api/evennia.contrib.tutorials.red_button.red_button.html
@@ -153,7 +153,7 @@ such as when closing the lid and un-blinding a character.
-search_index_entry = {'aliases': 'get feel listen l examine ex', 'category': 'general', 'key': 'look', 'no_prefix': ' get feel listen l examine ex', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}¶
+search_index_entry = {'aliases': 'ex get listen feel l examine', 'category': 'general', 'key': 'look', 'no_prefix': ' ex get listen feel l examine', 'tags': '', 'text': "\n Looking around in darkness\n\n Usage:\n look <obj>\n\n ... not that there's much to see in the dark.\n\n "}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html
index 3be4d9fdc6..962c0ed952 100644
--- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html
+++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.objects.html
@@ -425,7 +425,7 @@ of the object. We overload it with our own version.
@@ -805,7 +805,7 @@ parry - forgoes your attack but will make you harder to hit on next
-search_index_entry = {'aliases': 'bash thrust defend chop hit kill slash stab fight pierce parry', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' bash thrust defend chop hit kill slash stab fight pierce parry', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}¶
+search_index_entry = {'aliases': 'defend thrust fight hit parry stab bash chop slash pierce kill', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' defend thrust fight hit parry stab bash chop slash pierce kill', 'tags': '', 'text': '\n Attack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\n stab - (thrust) makes a lot of damage but is harder to hit with.\n slash - is easier to land, but does not make as much damage.\n parry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n '}¶
diff --git a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html
index cb90a14d3e..b1814d672d 100644
--- a/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html
+++ b/docs/1.0-dev/api/evennia.contrib.tutorials.tutorial_world.rooms.html
@@ -968,7 +968,7 @@ to find something.
-search_index_entry = {'aliases': 'a n __nomatch_command abort yes y no', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' a n __nomatch_command abort yes y no', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}¶
+search_index_entry = {'aliases': 'yes abort a y __nomatch_command no n', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' yes abort a y __nomatch_command no n', 'tags': '', 'text': '\n Handle a prompt for yes or no. Press [return] for the default choice.\n\n '}¶
diff --git a/docs/1.0-dev/api/evennia.utils.evmore.html b/docs/1.0-dev/api/evennia.utils.evmore.html
index 4c627e8865..322c7dc7db 100644
--- a/docs/1.0-dev/api/evennia.utils.evmore.html
+++ b/docs/1.0-dev/api/evennia.utils.evmore.html
@@ -137,7 +137,7 @@ the caller.msg() construct every time the page is updated.
@@ -163,7 +163,7 @@ the caller.msg() construct every time the page is updated.
-search_index_entry = {'aliases': 'a end quit n e q abort t p previous next top', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' a end quit n e q abort t p previous next top', 'tags': '', 'text': '\n Manipulate the text paging. Catch no-input with aliases.\n '}¶
+search_index_entry = {'aliases': 'e quit p t abort a previous end next q top n', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' e quit p t abort a previous end next q top n', 'tags': '', 'text': '\n Manipulate the text paging. Catch no-input with aliases.\n '}¶
diff --git a/docs/1.0-dev/index.html b/docs/1.0-dev/index.html
index ede0f562b3..ed64819776 100644
--- a/docs/1.0-dev/index.html
+++ b/docs/1.0-dev/index.html
@@ -215,16 +215,17 @@ or the original github wiki. You have been warned.