from evennia import DefaultCharacter
+from evennia import DefaultCharacter
+
+class EvAdventureCharacter(DefaultCharacter):
+ # stuff
-class EvAdventureCharacter(DefaultCharacter):
- # stuff
-
class EvAdventureNPC(EvAdventureCharacter):
- # more stuff
-
-class EvAdventureMob(EvAdventureNPC):
- # more stuff
+ # more stuff
+
+class EvAdventureMob(EvAdventureNPC):
+ # more stuff
All code we put on the Character class would now be inherited to NPC and Mob automatically.
However, in Knave, NPCs and particularly monsters are not using the same rules as PCs - they are simplified to use a Hit-Die (HD) concept. So while still character-like, NPCs should be separate from PCs like this:
-from evennia import DefaultCharacter
+from evennia import DefaultCharacter
-class EvAdventureCharacter(DefaultCharacter):
- # stuff
+class EvAdventureCharacter(DefaultCharacter):
+ # stuff
class EvAdventureNPC(DefaultCharacter):
- # separate stuff
-
+ # separate stuff
+
class EvAdventureMob(EvadventureNPC):
# more separate stuff
@@ -97,17 +97,17 @@ RPG, it will hold everything relevant to that PC.
All can get looted when defeated.
We don’t want to code this separately for every class but we no longer have a common parent class to put it on. So instead we’ll use the concept of a mixin class:
-
from evennia import DefaultCharacter
+from evennia import DefaultCharacter
class LivingMixin:
# stuff common for all living things
-class EvAdventureCharacter(LivingMixin, DefaultCharacter):
- # stuff
+class EvAdventureCharacter(LivingMixin, DefaultCharacter):
+ # stuff
class EvAdventureNPC(LivingMixin, DefaultCharacter):
- # stuff
-
+ # stuff
+
class EvAdventureMob(LivingMixin, EvadventureNPC):
# more stuff
@@ -116,7 +116,7 @@ RPG, it will hold everything relevant to that PC.
In evennia/contrib/tutorials/evadventure/characters.py
is an example of a character class structure.
-
Above, the LivingMixin class cannot work on its own - it just ‘patches’ the other classes with some extra functionality all living things should be able to do. This is an example of multiple inheritance. It’s useful to know about, but one should not over-do multiple inheritance since it can also get confusing to follow the code.
+
Above, the LivingMixin class cannot work on its own - it just ‘patches’ the other classes with some extra functionality all living things should be able to do. This is an example of multiple inheritance. The order of inheritance matters here - the LivingMixin must come before DefaultCharacter (or EvAdventureNPC etc) so that its methods are found first when called.Multiple inheritance is a powerful tool in object-oriented programming, and useful to know about. Be careful to over-use it, however. If you have too many mixins it can get hard to follow which method comes from where.
3.2. Living mixin class
@@ -124,14 +124,14 @@ is an example of a character class structure.
Create a new module mygame/evadventure/characters.py
Let’s get some useful common methods all living things should have in our game.
-# in mygame/evadventure/characters.py
+# in mygame/evadventure/characters.py
-from .rules import dice
+from .rules import dice
class LivingMixin:
# makes it easy for mobs to know to attack PCs
- is_pc = False
+ is_pc = False
@property
def hurt_level(self):
@@ -156,49 +156,49 @@ is an example of a character class structure.
elif percent == 0:
return "|RCollapsed!|n"
- def heal(self, hp):
- """
+ def heal(self, hp):
+ """
Heal hp amount of health, not allowing to exceed our max hp
-
- """
- damage = self.hp_max - self.hp
- healed = min(damage, hp)
- self.hp += healed
-
- self.msg(f"You heal for {healed} HP.")
-
+
+ """
+ damage = self.hp_max - self.hp
+ healed = min(damage, hp)
+ self.hp += healed
+
+ self.msg(f"You heal for {healed} HP.")
+
def at_pay(self, amount):
"""When paying coins, make sure to never detract more than we have"""
amount = min(amount, self.coins)
self.coins -= amount
return amount
-
- def at_attacked(self, attacker, **kwargs):
+
+ def at_attacked(self, attacker, **kwargs):
"""Called when being attacked and combat starts."""
pass
-
+
def at_damage(self, damage, attacker=None):
"""Called when attacked and taking damage."""
- self.hp -= damage
-
- def at_defeat(self):
+ self.hp -= damage
+
+ def at_defeat(self):
"""Called when defeated. By default this means death."""
self.at_death()
-
+
def at_death(self):
"""Called when this thing dies."""
# this will mean different things for different living things
- pass
-
+ pass
+
def at_do_loot(self, looted):
- """Called when looting another entity"""
+ """Called when looting another entity"""
looted.at_looted(self)
-
+
def at_looted(self, looter):
- """Called when looted by another entity"""
-
- # default to stealing some coins
- max_steal = dice.roll("1d10")
+ """Called when looted by another entity"""
+
+ # default to stealing some coins
+ max_steal = dice.roll("1d10")
stolen = self.at_pay(max_steal)
looter.coins += stolen
@@ -213,28 +213,28 @@ is an example of a character class structure.
# in mygame/evadventure/characters.py
from evennia import DefaultCharacter, AttributeProperty
-from .rules import dice
+from .rules import dice
class LivingMixin:
- # ...
+ # ...
class EvAdventureCharacter(LivingMixin, DefaultCharacter):
- """
- A character to use for EvAdventure.
+ """
+ A character to use for EvAdventure.
"""
- is_pc = True
+ is_pc = True
- strength = AttributeProperty(1)
+ strength = AttributeProperty(1)
dexterity = AttributeProperty(1)
constitution = AttributeProperty(1)
intelligence = AttributeProperty(1)
wisdom = AttributeProperty(1)
charisma = AttributeProperty(1)
-
- hp = AttributeProperty(8)
+
+ hp = AttributeProperty(8)
hp_max = AttributeProperty(8)
-
+
level = AttributeProperty(1)
xp = AttributeProperty(0)
coins = AttributeProperty(0)
@@ -249,13 +249,13 @@ is an example of a character class structure.
"$You() $conj(collapse) in a heap, alive but beaten.",
from_obj=self)
self.heal(self.hp_max)
-
+
def at_death(self):
"""We rolled 'dead' on the death table."""
self.location.msg_contents(
"$You() collapse in a heap, embraced by death.",
- from_obj=self)
- # TODO - go back into chargen to make a new character!
+ from_obj=self)
+ # TODO - go back into chargen to make a new character!
We make an assumption about our rooms here - that they have a property .allow_death. We need to make a note to actually add such a property to rooms later!
@@ -287,26 +287,26 @@ is an example of a character class structure.
3.3.2. Backtracking
We make our first use of the rules.dice roller to roll on the death table! As you may recall, in the previous lesson, we didn’t know just what to do when rolling ‘dead’ on this table. Now we know - we should be calling at_death on the character. So let’s add that where we had TODOs before:
-# mygame/evadventure/rules.py
+# mygame/evadventure/rules.py
class EvAdventureRollEngine:
-
- # ...
- def roll_death(self, character):
+ # ...
+
+ def roll_death(self, character):
ability_name = self.roll_random_table("1d8", death_table)
if ability_name == "dead":
# kill the character!
character.at_death() # <------ TODO no more
- else:
- # ...
-
- if current_ability < -10:
+ else:
+ # ...
+
+ if current_ability < -10:
# kill the character!
character.at_death() # <------- TODO no more
else:
- # ...
+ # ...
@@ -340,12 +340,12 @@ instead.
Create a new module mygame/evadventure/tests/test_characters.py
For testing, we just need to create a new EvAdventure character and check that calling the methods on it doesn’t error out.
-# mygame/evadventure/tests/test_characters.py
+# mygame/evadventure/tests/test_characters.py
from evennia.utils import create
-from evennia.utils.test_resources import BaseEvenniaTest
+from evennia.utils.test_resources import BaseEvenniaTest
-from ..characters import EvAdventureCharacter
+from ..characters import EvAdventureCharacter
class TestCharacters(BaseEvenniaTest):
def setUp(self):
@@ -353,28 +353,28 @@ instead.
self.character = create.create_object(EvAdventureCharacter, key="testchar")
def test_heal(self):
- self.character.hp = 0
- self.character.hp_max = 8
-
+ self.character.hp = 0
+ self.character.hp_max = 8
+
self.character.heal(1)
self.assertEqual(self.character.hp, 1)
# make sure we can't heal more than max
self.character.heal(100)
self.assertEqual(self.character.hp, 8)
-
+
def test_at_pay(self):
- self.character.coins = 100
-
+ self.character.coins = 100
+
result = self.character.at_pay(60)
- self.assertEqual(result, 60)
+ self.assertEqual(result, 60)
self.assertEqual(self.character.coins, 40)
-
- # can't get more coins than we have
+
+ # can't get more coins than we have
result = self.character.at_pay(100)
self.assertEqual(result, 40)
self.assertEqual(self.character.coins, 0)
-
- # tests for other methods ...
+
+ # tests for other methods ...
@@ -391,11 +391,11 @@ instead.
# mygame/evadventure/characters.py
from evennia import DefaultCharacter, AttributeProperty
-# ...
+# ...
class EvAdventureCharacter(LivingMixin, DefaultCharacter):
-
- # ...
+
+ # ...
charclass = AttributeProperty("Fighter")
charrace = AttributeProperty("Human")
@@ -410,7 +410,7 @@ instead.
3.7. Summary
With the EvAdventureCharacter class in place, we have a better understanding of how our PCs will look like under Knave.
For now, we only have bits and pieces and haven’t been testing this code in-game. But if you want you can swap yourself into EvAdventureCharacter right now. Log into your game and run the command
-type self = evadventure.characters.EvAdventureCharacter
+type self = evadventure.characters.EvAdventureCharacter
If all went well, ex self will now show your typeclass as being EvAdventureCharacter. Check out your strength with
diff --git a/docs/latest/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.html b/docs/latest/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.html
index 8313c16654..96f0833bed 100644
--- a/docs/latest/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.html
+++ b/docs/latest/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.html
@@ -205,7 +205,7 @@ You use Potion def get_sides(self, combatant):
"""
Get a listing of the two 'sides' of this combat,
- m the perspective of the provided combatant.
+ from the perspective of the provided combatant.
"""
if self.obj.allow_pvp:
# in pvp, everyone else is an ememy
@@ -257,7 +257,7 @@ You use Potion We use the advantage/disadvantage_matrix Attributes to track who has advantage against whom.
In the has dis/advantage methods we pop the target from the matrix which will result either in the value True or False (the default value we give to pop if the target is not in the matrix). This means that the advantage, once gained, can only be used once.
We also consider everyone to have advantage against fleeing combatants.
@@ -498,7 +498,7 @@ This is new compared to the base handler.
49 surviving_combatant = None
50 allies, enemies = (), ()
51 else:
-52 # grab a random survivor and check of they have any living enemies.
+52 # grab a random survivor and check if they have any living enemies.
53 surviving_combatant = random.choice(list(self.combatants.keys()))
54 allies, enemies = self.get_sides(surviving_combatant)
55
@@ -764,7 +764,7 @@ This is new compared to the base handler.
11.4.5. Stepping through the wizard
-Our particualr menu is very symmetric - you select an option and then you will just select a series of option before you come back. So we will make another goto-function to help us easily do this. To understand, let’s first show how we plan to use this:
+Our particular menu is very symmetric - you select an option and then you will just select a series of option before you come back. So we will make another goto-function to help us easily do this. To understand, let’s first show how we plan to use this:
# in the base combat-node function (just shown as an example)
options = [
@@ -844,7 +844,7 @@ This is new compared to the base handler.
# ...
-_get_default_wizard_options(caller, **kwargs):
+def _get_default_wizard_options(caller, **kwargs):
return [
{
"key": "back",
@@ -956,7 +956,6 @@ This is new compared to the base handler.
Ability.DEX,
Ability.CON,
Ability.INT,
- Ability.INT,
Ability.WIS,
Ability.CHA,
)
@@ -1001,7 +1000,7 @@ This is new compared to the base handler.
Our equipment handler has the very useful help method .get_usable_objects_from_backpack. We just call this to get a list of all the items we want to choose. Otherwise this node should look pretty familiar by now.
-The node_choose_wiqld_item is very similar, except it uses caller.equipment.get_wieldable_objects_from_backpack() instead. We’ll leave the implementation of this up to the reader.
+The node_choose_wield_item is very similar, except it uses caller.equipment.get_wieldable_objects_from_backpack() instead. We’ll leave the implementation of this up to the reader.
11.4.9. The main menu node
@@ -1103,12 +1102,12 @@ This is new compared to the base handler.
This starts off the _step_wizard for each action choice. It also lays out the action_dict for every action, leaving None values for the fields that will be set by the following nodes.
-Note how we add the "repeat" key to some actions. Having them automatically repeat means the player don’t have to insert the same action every time.
+Note how we add the "repeat" key to some actions. Having them automatically repeat means the player doesn’t have to insert the same action every time.
11.5. Attack Command
-We will only need one single Command to run the Turnbased combat system. This is the attack command. Once you use it once, you will be in the menu.
+We will only need one single command to run the turnbased combat system. This is the attack command. Once you use it once, you will be in the menu.
# in evadventure/combat_turnbased.py
from evennia import Command, CmdSet, EvMenu
@@ -1319,7 +1318,7 @@ This is new compared to the base handler.
If editing this in an IDE, you may get errors on the player = caller line. This is because caller is not defined anywhere in this file. Instead caller (the one running the script) is injected by the batchcode runner.
But apart from the # HEADER and # CODE specials, this just a series of normal Evennia api calls.
Log into the game with a developer/superuser account and run
-> batchcmd evadventure.batchscripts.turnbased_combat_demo
+> batchcode evadventure.batchscripts.turnbased_combat_demo
This should place you in the arena with the dummy (if not, check for errors in the output! Use objects and delete commands to list and delete objects if you need to start over.)
@@ -1327,7 +1326,7 @@ This is new compared to the base handler.
11.9. Conclusions
-At this point we have coverered some ideas on how to implement both twitch- and turnbased combat systems. Along the way you have been exposed to many concepts such as classes, scripts and handlers, Commands, EvMenus and more.
+At this point we have covered some ideas on how to implement both twitch- and turnbased combat systems. Along the way you have been exposed to many concepts such as classes, scripts and handlers, Commands, EvMenus and more.
Before our combat system is actually usable, we need our enemies to actually fight back. We’ll get to that next.
diff --git a/docs/latest/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.html b/docs/latest/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.html
index 50dc67221c..76c86200de 100644
--- a/docs/latest/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.html
+++ b/docs/latest/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.html
@@ -297,7 +297,7 @@ a given combatant has advantage.
37
38 """
39 if action_dict["key"] not in self.action_classes:
-40 self.obj.msg("This is an unkown action!")
+40 self.obj.msg("This is an unknown action!")
41 return
42
43 # store action dict and schedule it to run in dt time
@@ -323,7 +323,7 @@ a given combatant has advantage.
Line 43: We simply store the given action dict in the Attribute action_dict on the handler. Simple and effective!
Line 44: When you enter e.g. attack, you expect in this type of combat to see the attack command repeat automatically even if you don’t enter anything more. To this end we are looking for a new key in action dicts, indicating that this action should repeat with a certain rate (dt, given in seconds). We make this compatible with all action dicts by simply assuming it’s zero if not specified.
-evennia.utils.utils.repeat and evennia.utils.utils.unrepeat are convenient shortcuts to the TickerHandler. You tell repeat to call a given method/function at a certain rate. What you get back is a reference that you can then later use to ‘un-repeat’ (stop the repeating) later. We make sure to store this reference (we don’t care exactly how it looks, just that we need to store it) in the current_ticket_ref Attribute (Line 26).
+evennia.utils.utils.repeat and evennia.utils.utils.unrepeat are convenient shortcuts to the TickerHandler. You tell repeat to call a given method/function at a certain rate. What you get back is a reference that you can then later use to ‘un-repeat’ (stop the repeating) later. We make sure to store this reference (we don’t care exactly how it looks, just that we need to store it) in the current_ticker_ref Attribute (Line 26).
Line 48: Whenever we queue a new action (it may replace an existing one) we must make sure to kill (un-repeat) any old repeats that are ongoing. Otherwise we would get old actions firing over and over and new ones starting alongside them.
Line 49: If dt is set, we call repeat to set up a new repeat action at the given rate. We store this new reference. After dt seconds, the .execute_next_action method will fire (we’ll create that in the next section).
@@ -340,30 +340,30 @@ a given combatant has advantage.
7 # ...
8
9 def execute_next_action(self):
-10 """
-11 Triggered after a delay by the command
-12 """
-13 combatant = self.obj
-14 action_dict = self.action_dict
-15 action_class = self.action_classes[action_dict["key"]]
-16 action = action_class(self, combatant, action_dict)
-17
-18 if action.can_use():
-19 action.execute()
-20 action.post_execute()
-21
-22 if not action_dict.get("repeat", True):
-23 # not a repeating action, use the fallback (normally the original attack)
-24 self.action_dict = self.fallback_action_dict
-25 self.queue_action(self.fallback_action_dict)
-26
-27 self.check_stop_combat()
+10 """
+11 Triggered after a delay by the command
+12 """
+13 combatant = self.obj
+14 action_dict = self.action_dict
+15 action_class = self.action_classes[action_dict["key"]]
+16 action = action_class(self, combatant, action_dict)
+17
+18 if action.can_use():
+19 action.execute()
+20 action.post_execute()
+21
+22 if not action_dict.get("repeat", True):
+23 # not a repeating action, use the fallback (normally the original attack)
+24 self.action_dict = self.fallback_action_dict
+25 self.queue_action(self.fallback_action_dict)
+26
+27 self.check_stop_combat()
This is the method called after dt seconds in queue_action.
Line 5: We defined a ‘fallback action’. This is used after a one-time action (one that should not repeat) has completed.
-Line 15: We take the 'key' from the action-dict and use the action_classes mapping to get an action class (e.g. ACtionAttack we defined here).
+Line 15: We take the 'key' from the action_dict and use the action_classes mapping to get an action class (e.g. ActionAttack we defined here).
Line 16: Here we initialize the action class with the actual current data - the combatant and the action_dict. This calls the __init__ method on the class and makes the action ready to use.
-This looks much longer, but that is only because the stunt command should understand many different input structures depending on if you are trying to create a advantage or disadvantage, and if an ally or enemy should receive the effect of the stunt.
+This looks much longer, but that is only because the stunt command should understand many different input structures depending on if you are trying to create an advantage or disadvantage, and if an ally or enemy should receive the effect of the stunt.
Note the enums.ABILITY_REVERSE_MAP (created in the Utilities lesson) being useful to convert your input of ‘str’ into Ability.STR needed by the action dict.
Once we’ve sorted out the string parsing, the func is simple - we find the target and recipient and use them to build the needed action-dict to queue.
@@ -777,8 +777,8 @@ You (Wounded)class CmdWield(_BaseTwitchCombatCommand):
"""
- Wield a weapon or spell-rune. You will the wield the item,
- swapping with any other item(s) you were wielded before.
+ Wield a weapon or spell-rune. You wield the item,
+ swapping with any other item(s) you were wielding before.
Usage:
wield <weapon or spell>
@@ -906,7 +906,7 @@ You (Wounded)# ...
-class TestEvAdventureTwitchCombat(EvenniaCommandTestMixin)
+class TestEvAdventureTwitchCombat(EvenniaCommandTestMixin):
def setUp(self):
self.combathandler = (
@@ -917,12 +917,12 @@ You (Wounded)@patch("evadventure.combat_twitch.unrepeat", new=Mock())
@patch("evadventure.combat_twitch.repeat", new=Mock())
def test_hold_command(self):
- self.call(combat_twitch, CmdHold(), "", "You hold back, doing nothing")
+ self.call(combat_twitch, CmdHold(), "", "You hold back, doing nothing.")
self.assertEqual(self.combathandler.action_dict, {"key": "hold"})
-The EvenniaCommandTestMixin as a few default objects, including self.char1, which we make use of here.
+The EvenniaCommandTestMixin has a few default objects, including self.char1, which we make use of here.
The two @patch lines are Python decorators that ‘patch’ the test_hold_command method. What they do is basically saying “in the following method, whenever any code tries to access evadventure.combat_twitch.un/repeat, just return a Mocked object instead”.
We do this patching as an easy way to avoid creating timers in the unit test - these timers would finish after the test finished (which includes deleting its objects) and thus fail.
Inside the test, we use the self.call() method to explicitly fire the Command (with no argument) and check that the output is what we expect. Lastly we check that the combathandler is set up correctly, having stored the action-dict on itself.
diff --git a/docs/latest/Setup/Installation-Troubleshooting.html b/docs/latest/Setup/Installation-Troubleshooting.html
index 04ec6e8afd..b1d539fa51 100644
--- a/docs/latest/Setup/Installation-Troubleshooting.html
+++ b/docs/latest/Setup/Installation-Troubleshooting.html
@@ -56,15 +56,15 @@ you can run evennia
Linux/Unix
Windows (Win7, Win8, Win10, Win11)
Mac OSX (>10.5 recommended)
-Python (3.10, 3.11 and 3.12 are tested. 3.12 is recommended)
-Twisted (v23.10+)
+Python (3.11, 3.12 and 3.13 are tested. 3.13 is recommended)
+Twisted (v24.11+)
ZopeInterface (v3.0+) - usually included in Twisted packages
Linux/Mac users may need the gcc and python-dev packages or equivalent.
Windows users need MS Visual C++ and maybe pypiwin32.
-Django (v4.2+), be warned that latest dev version is usually untested with Evennia.
+Django (v5.2+), be warned that latest dev version is usually untested with Evennia.
GIT - version control software used if you want to install the sources (but also useful to track your own code)
diff --git a/docs/latest/_modules/evennia/utils/funcparser.html b/docs/latest/_modules/evennia/utils/funcparser.html
index 613b6e3c3b..c588f7a9f4 100644
--- a/docs/latest/_modules/evennia/utils/funcparser.html
+++ b/docs/latest/_modules/evennia/utils/funcparser.html
@@ -1256,12 +1256,12 @@
- "$search(beach, category=outdoors, type=tag)
"""
- # clean out funcparser-specific kwargs so we can use the kwargs for
+ # clean out funcparser and protfunc_parser-specific kwargs so we can use the kwargs for
# searching
search_kwargs = {
key: value
for key, value in kwargs.items()
- if key not in ("funcparser", "raise_errors", "type", "return_list")
+ if key not in ("funcparser", "raise_errors", "type", "return_list", "prototype")
}
return_list = str(kwargs.pop("return_list", "false")).lower() == "true"
diff --git a/docs/latest/_modules/evennia/utils/test_resources.html b/docs/latest/_modules/evennia/utils/test_resources.html
index 0d6a5a9de6..107296b892 100644
--- a/docs/latest/_modules/evennia/utils/test_resources.html
+++ b/docs/latest/_modules/evennia/utils/test_resources.html
@@ -518,7 +518,6 @@
cmdobj.raw_string = raw_string if raw_string is not None else cmdobj.key + " " + input_args
cmdobj.obj = obj or (caller if caller else self.char1)
inputs = inputs or []
-
# set up receivers
receiver_mapping = {}
if isinstance(msg, dict):
@@ -543,42 +542,40 @@
# cmdhandler. This will have the mocked .msg be called as part of the
# execution. Mocks remembers what was sent to them so we will be able
# to retrieve what was sent later.
- try:
- if cmdobj.at_pre_cmd():
- return
- cmdobj.parse()
- ret = cmdobj.func()
-
- # handle func's with yield in them (making them generators)
- if isinstance(ret, types.GeneratorType):
- while True:
- try:
- inp = inputs.pop() if inputs else None
- if inp:
- try:
- # this mimics a user's reply to a prompt
- ret.send(inp)
- except TypeError:
+ if not cmdobj.at_pre_cmd():
+ try:
+ cmdobj.parse()
+ ret = cmdobj.func()
+ # handle func's with yield in them (making them generators)
+ if isinstance(ret, types.GeneratorType):
+ while True:
+ try:
+ inp = inputs.pop() if inputs else None
+ if inp:
+ try:
+ # this mimics a user's reply to a prompt
+ ret.send(inp)
+ except TypeError:
+ next(ret)
+ ret = ret.send(inp)
+ else:
+ # non-input yield, like yield(10). We don't pause
+ # but fire it immediately.
next(ret)
- ret = ret.send(inp)
- else:
- # non-input yield, like yield(10). We don't pause
- # but fire it immediately.
- next(ret)
- except StopIteration:
- break
+ except StopIteration:
+ break
- cmdobj.at_post_cmd()
- except StopIteration:
- pass
- except InterruptCommand:
- pass
+ cmdobj.at_post_cmd()
+ except StopIteration:
+ pass
+ except InterruptCommand:
+ pass
- for inp in inputs:
- # if there are any inputs left, we may have a non-generator
- # input to handle (get_input/ask_yes_no that uses a separate
- # cmdset rather than a yield
- caller.execute_cmd(inp)
+ for inp in inputs:
+ # if there are any inputs left, we may have a non-generator
+ # input to handle (get_input/ask_yes_no that uses a separate
+ # cmdset rather than a yield
+ caller.execute_cmd(inp)
# At this point the mocked .msg methods on each receiver will have
# stored all calls made to them (that's a basic function of the Mock
diff --git a/docs/latest/_modules/evennia/web/admin/objects.html b/docs/latest/_modules/evennia/web/admin/objects.html
index dfe4c32f96..9451467d7a 100644
--- a/docs/latest/_modules/evennia/web/admin/objects.html
+++ b/docs/latest/_modules/evennia/web/admin/objects.html
@@ -360,7 +360,7 @@
urls = super().get_urls()
custom_urls = [
path(
- "account-object-link/<int:pk>",
+ "account-object-link/<int:object_id>",
self.admin_site.admin_view(self.link_object_to_account),
name="object-account-link",
)
diff --git a/docs/latest/_modules/evennia/web/api/tests.html b/docs/latest/_modules/evennia/web/api/tests.html
index 9b522c6554..d12f111b96 100644
--- a/docs/latest/_modules/evennia/web/api/tests.html
+++ b/docs/latest/_modules/evennia/web/api/tests.html
@@ -241,18 +241,18 @@
with self.subTest(msg=f"Testing {view.view_name}"):
view_url = reverse(f"api:{view.view_name}", kwargs={"pk": view.obj.pk})
# check failures from not sending required fields
- response = self.client.post(view_url)
+ response = self.client.put(view_url)
self.assertEqual(response.status_code, 400, f"Response was: {response.data}")
# test adding an attribute
self.assertEqual(view.obj.db.some_test_attr, None)
attr_name = "some_test_attr"
attr_data = {"db_key": attr_name, "db_value": "test_value"}
- response = self.client.post(view_url, data=attr_data)
+ response = self.client.put(view_url, data=attr_data)
self.assertEqual(response.status_code, 200, f"Response was: {response.data}")
self.assertEqual(view.obj.attributes.get(attr_name), "test_value")
# now test removing it
attr_data = {"db_key": attr_name}
- response = self.client.post(view_url, data=attr_data)
+ response = self.client.put(view_url, data=attr_data)
self.assertEqual(response.status_code, 200, f"Response was: {response.data}")
self.assertEqual(view.obj.attributes.get(attr_name), None)
diff --git a/docs/latest/_modules/evennia/web/api/views.html b/docs/latest/_modules/evennia/web/api/views.html
index 327381eabf..2cb45fdfec 100644
--- a/docs/latest/_modules/evennia/web/api/views.html
+++ b/docs/latest/_modules/evennia/web/api/views.html
@@ -107,7 +107,7 @@
[docs]
-
@action(detail=True, methods=["put", "post"])
+
@action(detail=True, methods=["put"])
def set_attribute(self, request, pk=None):
"""
This action will set an attribute if the db_value is defined, or remove
diff --git a/docs/latest/_modules/functools.html b/docs/latest/_modules/functools.html
index 43b47b3a8b..dc0148c7ed 100644
--- a/docs/latest/_modules/functools.html
+++ b/docs/latest/_modules/functools.html
@@ -988,8 +988,7 @@
class singledispatchmethod:
"""Single-dispatch generic method descriptor.
-
Supports wrapping existing descriptors and handles non-descriptor
-
callables as instance methods.
+
Supports wrapping existing descriptors.
"""
def __init__(self, func):
diff --git a/docs/latest/_modules/inspect.html b/docs/latest/_modules/inspect.html
index 4b25009801..e5621a06ba 100644
--- a/docs/latest/_modules/inspect.html
+++ b/docs/latest/_modules/inspect.html
@@ -1197,7 +1197,7 @@
"""Provide a tokeneater() method to detect the end of a code block."""
def __init__(self):
self.indent = 0
-
self.islambda = False
+
self.singleline = False
self.started = False
self.passline = False
self.indecorator = False
@@ -1206,19 +1206,24 @@
def tokeneater(self, type, token, srowcol, erowcol, line):
if not self.started and not self.indecorator:
+
if type in (tokenize.INDENT, tokenize.COMMENT, tokenize.NL):
+
pass
+
elif token == "async":
+
pass
# skip any decorators
-
if token == "@":
+
elif token == "@":
self.indecorator = True
-
# look for the first "def", "class" or "lambda"
-
elif token in ("def", "class", "lambda"):
-
if token == "lambda":
-
self.islambda = True
+
else:
+
# For "def" and "class" scan to the end of the block.
+
# For "lambda" and generator expression scan to
+
# the end of the logical line.
+
self.singleline = token not in ("def", "class")
self.started = True
self.passline = True # skip to the end of the line
elif type == tokenize.NEWLINE:
self.passline = False # stop skipping when a NEWLINE is seen
self.last = srowcol[0]
-
if self.islambda: # lambdas always end at the first NEWLINE
+
if self.singleline:
raise EndOfBlock
# hitting a NEWLINE when in a decorator without args
# ends the decorator
@@ -2053,17 +2058,21 @@
if meth is None:
return None
+
# NOTE: The meth may wraps a non-user-defined callable.
+
# In this case, we treat the meth as non-user-defined callable too.
+
# (e.g. cls.__new__ generated by @warnings.deprecated)
+
unwrapped_meth = None
if follow_wrapper_chains:
-
meth = unwrap(meth, stop=(lambda m: hasattr(m, "__signature__")
+
unwrapped_meth = unwrap(meth, stop=(lambda m: hasattr(m, "__signature__")
or _signature_is_builtin(m)))
-
if isinstance(meth, _NonUserDefinedCallables):
+
+
if (isinstance(meth, _NonUserDefinedCallables)
+
or isinstance(unwrapped_meth, _NonUserDefinedCallables)):
# Once '__signature__' will be added to 'C'-level
# callables, this check won't be necessary
return None
if method_name != '__new__':
meth = _descriptor_get(meth, cls)
-
if follow_wrapper_chains:
-
meth = unwrap(meth, stop=lambda m: hasattr(m, "__signature__"))
return meth
diff --git a/docs/latest/_modules/traceback.html b/docs/latest/_modules/traceback.html
index 755a1ec0c2..55df8cee41 100644
--- a/docs/latest/_modules/traceback.html
+++ b/docs/latest/_modules/traceback.html
@@ -569,7 +569,7 @@
colorize = kwargs.get("colorize", False)
row = []
filename = frame_summary.filename
-
if frame_summary.filename.startswith("<stdin>-"):
+
if frame_summary.filename.startswith("<stdin-") and frame_summary.filename.endswith('>'):
filename = "<stdin>"
if colorize:
row.append(' File {}"{}"{}, line {}{}{}, in {}{}{}\n'.format(
diff --git a/docs/latest/_modules/weakref.html b/docs/latest/_modules/weakref.html
index 23fa0bc73b..db84eb5400 100644
--- a/docs/latest/_modules/weakref.html
+++ b/docs/latest/_modules/weakref.html
@@ -127,8 +127,6 @@
__hash__ = ref.__hash__
-
-
[docs]
class WeakValueDictionary(_collections_abc.MutableMapping):
"""Mapping class that references values weakly.
@@ -141,8 +139,6 @@
# objects are unwrapped on the way out, and we always wrap on the
# way in).
-
-
[docs]
def __init__(self, other=(), /, **kw):
def remove(wr, selfref=ref(self), _atomic_removal=_remove_dead_weakref):
self = selfref()
@@ -158,8 +154,7 @@
self._pending_removals = []
self._iterating = set()
self.data = {}
-
self.update(other, **kw)
-
+
self.update(other, **kw)
def _commit_removals(self, _atomic_removal=_remove_dead_weakref):
pop = self._pending_removals.pop
@@ -209,8 +204,6 @@
self._commit_removals()
self.data[key] = KeyedRef(value, self._remove, key)
-
-
[docs]
def copy(self):
if self._pending_removals:
self._commit_removals()
@@ -220,8 +213,7 @@
o = wr()
if o is not None:
new[key] = o
-
return new
-
+
return new
__copy__ = copy
@@ -237,8 +229,6 @@
new[deepcopy(key, memo)] = o
return new
-
-
[docs]
def get(self, key, default=None):
if self._pending_removals:
self._commit_removals()
@@ -252,11 +242,8 @@
# This should only happen
return default
else:
-
return o
+
return o
-
-
-
[docs]
def items(self):
if self._pending_removals:
self._commit_removals()
@@ -264,24 +251,18 @@
for k, wr in self.data.items():
v = wr()
if v is not None:
-
yield k, v
+
yield k, v
-
-
-
[docs]
def keys(self):
if self._pending_removals:
self._commit_removals()
with _IterationGuard(self):
for k, wr in self.data.items():
if wr() is not None:
-
yield k
-
+
yield k
__iter__ = keys
-
-
[docs]
def itervaluerefs(self):
"""Return an iterator that yields the weak references to the values.
@@ -295,11 +276,8 @@
if self._pending_removals:
self._commit_removals()
with _IterationGuard(self):
-
yield from self.data.values()
+
yield from self.data.values()
-
-
-
[docs]
def values(self):
if self._pending_removals:
self._commit_removals()
@@ -307,11 +285,8 @@
for wr in self.data.values():
obj = wr()
if obj is not None:
-
yield obj
+
yield obj
-
-
-
[docs]
def popitem(self):
if self._pending_removals:
self._commit_removals()
@@ -319,11 +294,8 @@
key, wr = self.data.popitem()
o = wr()
if o is not None:
-
return key, o
+
return key, o
-
-
-
[docs]
def pop(self, key, *args):
if self._pending_removals:
self._commit_removals()
@@ -337,11 +309,8 @@
else:
raise KeyError(key)
else:
-
return o
+
return o
-
-
-
[docs]
def setdefault(self, key, default=None):
try:
o = self.data[key]()
@@ -353,11 +322,8 @@
self.data[key] = KeyedRef(default, self._remove, key)
return default
else:
-
return o
+
return o
-
-
-
[docs]
def update(self, other=None, /, **kwargs):
if self._pending_removals:
self._commit_removals()
@@ -368,11 +334,8 @@
for key, o in other.items():
d[key] = KeyedRef(o, self._remove, key)
for key, o in kwargs.items():
-
d[key] = KeyedRef(o, self._remove, key)
+
d[key] = KeyedRef(o, self._remove, key)
-
-
-
[docs]
def valuerefs(self):
"""Return a list of weak references to the values.
@@ -385,8 +348,7 @@
"""
if self._pending_removals:
self._commit_removals()
-
return list(self.data.values())
-
+
return list(self.data.values())
def __ior__(self, other):
self.update(other)
@@ -405,8 +367,7 @@
c.update(other)
c.update(self)
return c
-
return NotImplemented
-
+
return NotImplemented
class KeyedRef(ref):
@@ -431,7 +392,7 @@
-
[docs]
+
[docs]
class WeakKeyDictionary(_collections_abc.MutableMapping):
""" Mapping class that references keys weakly.
@@ -444,7 +405,7 @@
"""
-
[docs]
+
[docs]
def __init__(self, dict=None):
self.data = {}
def remove(k, selfref=ref(self)):
@@ -510,7 +471,7 @@
self.data[ref(key, self._remove)] = value
-
[docs]
+
[docs]
def copy(self):
new = WeakKeyDictionary()
with _IterationGuard(self):
@@ -534,7 +495,7 @@
return new
-
[docs]
+
[docs]
def get(self, key, default=None):
return self.data.get(ref(key),default)
@@ -547,7 +508,7 @@
return wr in self.data
-
[docs]
+
[docs]
def items(self):
with _IterationGuard(self):
for wr, value in self.data.items():
@@ -557,7 +518,7 @@
-
[docs]
+
[docs]
def keys(self):
with _IterationGuard(self):
for wr in self.data:
@@ -569,7 +530,7 @@
__iter__ = keys
-
[docs]
+
[docs]
def values(self):
with _IterationGuard(self):
for wr, value in self.data.items():
@@ -578,7 +539,7 @@
-
[docs]
+
[docs]
def keyrefs(self):
"""Return a list of weak references to the keys.
@@ -593,7 +554,7 @@
-
[docs]
+
[docs]
def popitem(self):
self._dirty_len = True
while True:
@@ -604,20 +565,20 @@
-
[docs]
+
[docs]
def pop(self, key, *args):
self._dirty_len = True
return self.data.pop(ref(key), *args)
-
[docs]
+
[docs]
def setdefault(self, key, default=None):
return self.data.setdefault(ref(key, self._remove),default)
-
[docs]
+
[docs]
def update(self, dict=None, /, **kwargs):
d = self.data
if dict is not None:
diff --git a/docs/latest/_sources/Coding/Changelog.md.txt b/docs/latest/_sources/Coding/Changelog.md.txt
index 0ddffe466f..830a97766c 100644
--- a/docs/latest/_sources/Coding/Changelog.md.txt
+++ b/docs/latest/_sources/Coding/Changelog.md.txt
@@ -2,11 +2,55 @@
## Main branch
+- Security dependency updates: Django >5.2.8 (<5.3), Django RestFramework 3.16
+- [Feat][pull3599]: Make at_pre_cmd
+- [Fix]: API /openapi/setattribute endpoints were both POST and PUT, causing schema
+ errors; now changed to PUT only. (Griatch)
- [Fix][pull3799]: Fix typo in `basic_tc.py` contrib for beginner tutorial (Tharic99)
+- [Fix][pull3806]: EvMore wouldn't pass Session to next cmd when exiting (gas-public-wooden-clean)
+- [Fix][pull3809]: Admin page - Repair link to Account button (UserlandAlchemist)
+- [Fix][pull3811]: Website login banner shows before login attempt (UserlandAlchemist)
+- [Fix][pull3817]: `ingame_reports` i18n fix (peddn)
+- [Fix][pull3818]: Update spawn hook to use `new_prototype` (InspectorCaracal)
+- [Fix][pull3815]: Performance improvement in large cmdset mergers (blongden)
+- [Fix][pull3831]: Performance optimization in ANSIString, performance boost for large colored
+ strings (count-infinity)
+- [Fix][pull3832]: Fix typo in prototype causing homogenized locks to use
+ fallbacks incorrectly (count-infinity)
+- [Fix][pull3834]: Fix so `$obj(#123)` inline function works in prototype spawning (count-infinity)
+- [Fix][pull3836]: Correctly handling calling `create_object` with `key=None` (count-infinity)
+- [Fix][pull3852]: Django 5.2+ was not properly detected. Fixing distutils being
+ removed in py3.12 for new installs (count-infinity)
+- [Fix][pull3845]: Fix exponential ANSI markup explosions when slicing
+ ANSIString after reset (speeds up EvForm other string ops, fixes compatibility) (count-infinity)
+- [Fix][pull3853]: Properly handle multimatch separations with native dashes, like
+ 't-shirt-1' (count-infinity)
+- [Doc][pull3801]: Move Evennia doc build system to latest Sphinx/myST
+ (PowershellNinja, also honorary mention to electroglyph)
- [Doc][pull3800]: Describe support for Telnet SSH in HAProxy documentation (holl0wstar)
+- [Doc][pull3825]: Update Portuguese translation (marado)
+- [Doc][pull3826]: Fix broken links in README (marado)
+- Docs: marado, Griatch, Hasna878, count-infinity
-[pull3799]: https://github.com/evennia/evennia/issues/3799
-[pull3800]: https://github.com/evennia/evennia/issues/3800
+[pull3799]: https://github.com/evennia/evennia/pull/3799
+[pull3800]: https://github.com/evennia/evennia/pull/3800
+[pull3801]: https://github.com/evennia/evennia/pull/3801
+[pull3806]: https://github.com/evennia/evennia/pull/3806
+[pull3809]: https://github.com/evennia/evennia/pull/3809
+[pull3811]: https://github.com/evennia/evennia/pull/3811
+[pull3815]: https://github.com/evennia/evennia/pull/3815
+[pull3817]: https://github.com/evennia/evennia/pull/3817
+[pull3818]: https://github.com/evennia/evennia/pull/3818
+[pull3825]: https://github.com/evennia/evennia/pull/3825
+[pull3826]: https://github.com/evennia/evennia/pull/3826
+[pull3831]: https://github.com/evennia/evennia/pull/3831
+[pull3832]: https://github.com/evennia/evennia/pull/3832
+[pull3834]: https://github.com/evennia/evennia/pull/3834
+[pull3836]: https://github.com/evennia/evennia/pull/3836
+[pull3599]: https://github.com/evennia/evennia/pull/3599
+[pull3852]: https://github.com/evennia/evennia/pull/3852
+[pull3853]: https://github.com/evennia/evennia/pull/3853
+[pull3854]: https://github.com/evennia/evennia/pull/3853
## Evennia 5.0.1
@@ -31,10 +75,10 @@ This upgrade requires running `evennia migrate` on your existing database
- Feat (backwards incompatible): RUN MIGRATIONS (`evennia migrate`): Now requiring Django 5.1 (Griatch)
- Feat (backwards incompatible): Drop support and testing for Python 3.10 (Griatch)
-- [Feat][pull3719]: Support Python 3.13. (0xDEADFED5)
+- [Feat][pull3719]: Support Python 3.13. (electroglyph)
- [Feat][pull3633]: Default object's default descs are now taken from a `default_description`
class variable instead of the `desc` Attribute always being set (count-infinity)
-- [Feat][pull3718]: Remove twistd.bat creation for Windows, should not be needed anymore (0xDEADFED5)
+- [Feat][pull3718]: Remove twistd.bat creation for Windows, should not be needed anymore (electroglyph)
- [Feat][pull3756]: Updated German translation (JohnFi)
- [Feat][pull3757]: Add more i18n strings to `DefaultObject` for easier translation (JohnFi)
- [Feat][pull3783]: Support users of `ruff` linter by adding compatible config in `pyproject.toml` (jaborsh)
@@ -50,8 +94,8 @@ This upgrade requires running `evennia migrate` on your existing database
- [Fix][pull3690]: In searches, allow special 'here' and 'me' keywords only be valid queries
unless current location and/or caller is in valid search candidates respectively (InspectorCaracal)
- [Fix][pull3694]: Funcparser swallowing rest of line after a `\`-escape (count-infinity)
-- [Fix][pull3705]: Properly serialize `IntFlag` enum types (0xDEADFED5)
-- [Fix][pull3707]: Correct links in `about` command (0xDEADFED5)
+- [Fix][pull3705]: Properly serialize `IntFlag` enum types (electroglyph)
+- [Fix][pull3707]: Correct links in `about` command (electroglyph)
- [Fix][pull3710]: Clean reduntant session clearin in `at_server_cold_start` (InspectorCaracal)
- [Fix][pull3711]: Usability improvements in the Discord integration (InspectorCaracal)
- [Fix][pull3721]: Avoid loading cmdsets that don't need to be checked, avoiding
@@ -67,7 +111,7 @@ This upgrade requires running `evennia migrate` on your existing database
- [Fix][pull3743]: Log full stack trace on failed object creation (aMiss-aWry)
- [Fix][pull3747]: TutorialWorld bridge-room didn't correctly randomize weather effects (SpyrosRoum)
- [Fix][pull3765]: Storing TickerHandler `store_key` in a db attribute would not
- work correctly (0xDEADFED5)
+ work correctly (electroglyph)
- [Fix][pull3753]: Make sure `AttributeProperty`s are initialized with default values also in parent class (JohnFi)
- [Fix][pull3751]: The `access` and `inventory` commands would traceback if run on a character without an Account (EliasWatson)
- [Fix][pull3768]: Make sure the `CmdCopy` command copies object categories,
@@ -82,7 +126,7 @@ This upgrade requires running `evennia migrate` on your existing database
it caused an OnDemandHandler save error on reload. Will now clean up on save. (Griatch)
used as the task's category (Griatch)
- Fix: Correct aws contrib's use of legacy django string utils (Griatch)
-- [Docs]: Fixes from InspectorCaracal, Griatch, ChrisLR, JohnFi, 0xDEADFED5, jaborsh, Problematic, BlaneWins
+- [Docs]: Fixes from InspectorCaracal, Griatch, ChrisLR, JohnFi, electroglyph, jaborsh, Problematic, BlaneWins
[pull3633]: https://github.com/evennia/evennia/pull/3633
[pull3677]: https://github.com/evennia/evennia/pull/3677
@@ -204,7 +248,7 @@ Sep 29, 2024
- Feat: Support `scripts key:typeclass` to create global scripts
with dynamic keys (rather than just relying on typeclass' key) (Griatch)
-- [Feat][pull3595]: Tweak Sqlite3 PRAGMAs for better performance (0xDEADFED5)
+- [Feat][pull3595]: Tweak Sqlite3 PRAGMAs for better performance (electroglyph)
- Feat: Make Sqlite3 PRAGMAs configurable via settings (Griatch)
- [Feat][pull3592]: Revised German locationlization ('Du' instead of 'Sie',
cleanup) (Drakon72)
@@ -213,7 +257,7 @@ with dynamic keys (rather than just relying on typeclass' key) (Griatch)
- [Feat][pull3588]: New `DefaultObject` hooks: `at_object_post_creation`, called once after
first creation but after any prototypes have been applied, and
`at_object_post_spawn(prototype)`, called only after creation/update with a prototype (InspectorCaracal)
-- [Fix][pull3594]: Update/clean some Evennia dependencies (0xDEADFED5)
+- [Fix][pull3594]: Update/clean some Evennia dependencies (electroglyph)
- [Fix][issue3556]: Better error if trying to treat ObjectDB as a typeclass (Griatch)
- [Fix][issue3590]: Make `examine` command properly show `strattr` type
Attribute values (Griatch)
@@ -227,7 +271,7 @@ did not add it to the handler's object (Griatch)
- [Fix][pull3605]: Correctly pass node kwargs through `@list_node` decorated evmenu nodes
(InspectorCaracal)
- [Fix][pull3597]: Address timing issue for testing `new_task_waiting_input `on
- Windows (0xDEADFED5)
+ Windows (electroglyph)
- [Fix][pull3611]: Fix and update for Reports contrib (InspectorCaracal)
- [Fix][pull3625]: Lycanthropy tutorial page had some issues (feyrkh)
- [Fix][pull3622]: Fix for examine command tracebacking with strvalue error
@@ -273,10 +317,10 @@ Aug 11, 2024
- [Feat][pull3531]: New contrib; `in-game reports` for handling user reports,
bugs etc in-game (InspectorCaracal)
- [Feat][pull3586]: Add ANSI color support `|U`, `|I`, `|i`, `|s`, `|S` for
-underline reset, italic/reset and strikethrough/reset (0xDEADFED5)
+underline reset, italic/reset and strikethrough/reset (electroglyph)
- Feat: Add `Trait.traithandler` back-reference so custom Traits from the Traits
contrib can find and reference other Traits. (Griatch)
-- [Feat][pull3582]: Add true-color parsing/fallback for ANSIString (0xDEADFED5)
+- [Feat][pull3582]: Add true-color parsing/fallback for ANSIString (electroglyph)
- [Fix][pull3571]: Better visual display of partial multimatch search results
(InspectorCaracal)
- [Fix][issue3378]: Prototype 'alias' key was not properly homogenized to a list
@@ -286,8 +330,8 @@ underline reset, italic/reset and strikethrough/reset (0xDEADFED5)
- [Fix][pull3585]: `TagCmd.switch_options` was misnamed (erratic-pattern)
- [Fix][pull3580]: Fix typo that made `find/loc` show the wrong dbref in result (erratic-pattern)
- [Fix][pull3589]: Fix regex escaping in `utils.py` for future Python versions (hhsiao)
-- [Docs]: Add True-color description for Colors documentation (0xDEADFED5)
-- [Docs]: Doc fixes (Griatch, InspectorCaracal, 0xDEADFED5)
+- [Docs]: Add True-color description for Colors documentation (electroglyph)
+- [Docs]: Doc fixes (Griatch, InspectorCaracal, electroglyph)
[pull3585]: https://github.com/evennia/evennia/pull/3585
[pull3580]: https://github.com/evennia/evennia/pull/3580
diff --git a/docs/latest/_sources/Components/Prototypes.md.txt b/docs/latest/_sources/Components/Prototypes.md.txt
index c99e49061f..875dcdd18c 100644
--- a/docs/latest/_sources/Components/Prototypes.md.txt
+++ b/docs/latest/_sources/Components/Prototypes.md.txt
@@ -167,9 +167,8 @@ The default protfuncs available out of the box are defined in `evennia/prototype
| `$div(
, )` | Returns value2 / value1 |
| `$toint()` | Returns value converted to integer (or value if not possible) |
| `$eval()` | Returns result of [literal-eval](https://docs.python.org/2/library/ast.html#ast.literal_eval) of code string. Only simple python expressions. |
-| `$obj()` | Returns object #dbref searched globally by key, tag or #dbref. Error if more than one found. |
-| `$objlist()` | Like `$obj`, except always returns a list of zero, one or more results. |
-| `$dbref(dbref)` | Returns argument if it is formed as a #dbref (e.g. #1234), otherwise error. |
+| `$obj()` | Returns object searched globally by key, tag or #dbref. Requires `caller` kwarg in `spawner.spawn()` for access checks. See [searching callables](./FuncParser.md#evenniautilsfuncparsersearching_callables). ($dbref() is an alias and works the same) |
+| `$objlist()` | Like `$obj`, except always returns a list of zero, one or more results. Requires `caller` kwarg in `spawner.spawn()` for access checks. See [searching callables](./FuncParser.md#evenniautilsfuncparsersearching_callables). |
For developers with access to Python, using protfuncs in prototypes is generally not useful. Passing real Python functions is a lot more powerful and flexible. Their main use is to allow in-game builders to do limited coding/scripting for their prototypes without giving them direct access to raw Python.
diff --git a/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.md.txt b/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.md.txt
index e9ebe2826a..d0356ce1d5 100644
--- a/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.md.txt
+++ b/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.md.txt
@@ -1,47 +1,47 @@
# Player Characters
-In the [previous lesson about rules and dice rolling](./Beginner-Tutorial-Rules.md) we made some assumptions about the "Player Character" entity:
+In the [previous lesson about rules and dice rolling](./Beginner-Tutorial-Rules.md) we made some assumptions about the "Player Character" entity:
- It should store Abilities on itself as `character.strength`, `character.constitution` etc.
- It should have a `.heal(amount)` method.
-So we have some guidelines of how it should look! A Character is a database entity with values that should be able to be changed over time. It makes sense to base it off Evennia's
-[DefaultCharacter Typeclass](../../../Components/Typeclasses.md). The Character class is like a 'character sheet' in a tabletop
+So we have some guidelines of how it should look! A Character is a database entity with values that should be able to be changed over time. It makes sense to base it off Evennia's
+[DefaultCharacter Typeclass](../../../Components/Typeclasses.md). The Character class is like a 'character sheet' in a tabletop
RPG, it will hold everything relevant to that PC.
## Inheritance structure
-Player Characters (PCs) are not the only "living" things in our world. We also have _NPCs_
-(like shopkeepers and other friendlies) as well as _monsters_ (mobs) that can attack us.
+Player Characters (PCs) are not the only "living" things in our world. We also have _NPCs_
+(like shopkeepers and other friendlies) as well as _monsters_ (mobs) that can attack us.
-In code, there are a few ways we could structure this. If NPCs/monsters were just special cases of PCs, we could use a class inheritance like this:
+In code, there are a few ways we could structure this. If NPCs/monsters were just special cases of PCs, we could use a class inheritance like this:
```python
-from evennia import DefaultCharacter
+from evennia import DefaultCharacter
+
+class EvAdventureCharacter(DefaultCharacter):
+ # stuff
-class EvAdventureCharacter(DefaultCharacter):
- # stuff
-
class EvAdventureNPC(EvAdventureCharacter):
- # more stuff
-
-class EvAdventureMob(EvAdventureNPC):
- # more stuff
+ # more stuff
+
+class EvAdventureMob(EvAdventureNPC):
+ # more stuff
```
-All code we put on the `Character` class would now be inherited to `NPC` and `Mob` automatically.
+All code we put on the `Character` class would now be inherited to `NPC` and `Mob` automatically.
However, in _Knave_, NPCs and particularly monsters are _not_ using the same rules as PCs - they are simplified to use a Hit-Die (HD) concept. So while still character-like, NPCs should be separate from PCs like this:
```python
-from evennia import DefaultCharacter
+from evennia import DefaultCharacter
-class EvAdventureCharacter(DefaultCharacter):
- # stuff
+class EvAdventureCharacter(DefaultCharacter):
+ # stuff
class EvAdventureNPC(DefaultCharacter):
- # separate stuff
-
+ # separate stuff
+
class EvAdventureMob(EvadventureNPC):
# more separate stuff
```
@@ -57,18 +57,18 @@ Nevertheless, there are some things that _should_ be common for all 'living thin
We don't want to code this separately for every class but we no longer have a common parent class to put it on. So instead we'll use the concept of a _mixin_ class:
-```python
-from evennia import DefaultCharacter
+```python
+from evennia import DefaultCharacter
class LivingMixin:
# stuff common for all living things
-class EvAdventureCharacter(LivingMixin, DefaultCharacter):
- # stuff
+class EvAdventureCharacter(LivingMixin, DefaultCharacter):
+ # stuff
class EvAdventureNPC(LivingMixin, DefaultCharacter):
- # stuff
-
+ # stuff
+
class EvAdventureMob(LivingMixin, EvadventureNPC):
# more stuff
```
@@ -77,7 +77,8 @@ class EvAdventureMob(LivingMixin, EvadventureNPC):
In [evennia/contrib/tutorials/evadventure/characters.py](../../../api/evennia.contrib.tutorials.evadventure.characters.md)
is an example of a character class structure.
```
-Above, the `LivingMixin` class cannot work on its own - it just 'patches' the other classes with some extra functionality all living things should be able to do. This is an example of _multiple inheritance_. It's useful to know about, but one should not over-do multiple inheritance since it can also get confusing to follow the code.
+Above, the `LivingMixin` class cannot work on its own - it just 'patches' the other classes with some extra functionality all living things should be able to do. This is an example of _multiple inheritance_. The order of inheritance matters here - the `LivingMixin` must come _before_ `DefaultCharacter` (or EvAdventureNPC etc) so that its methods are found first when called.Multiple inheritance is a powerful tool in object-oriented programming, and useful to know about. Be careful to over-use it, however. If you have too many mixins it can get hard to follow which method comes from where.
+
## Living mixin class
@@ -85,15 +86,15 @@ Above, the `LivingMixin` class cannot work on its own - it just 'patches' the ot
Let's get some useful common methods all living things should have in our game.
-```python
-# in mygame/evadventure/characters.py
+```python
+# in mygame/evadventure/characters.py
-from .rules import dice
+from .rules import dice
class LivingMixin:
# makes it easy for mobs to know to attack PCs
- is_pc = False
+ is_pc = False
@property
def hurt_level(self):
@@ -118,58 +119,58 @@ class LivingMixin:
elif percent == 0:
return "|RCollapsed!|n"
- def heal(self, hp):
- """
+ def heal(self, hp):
+ """
Heal hp amount of health, not allowing to exceed our max hp
-
- """
- damage = self.hp_max - self.hp
- healed = min(damage, hp)
- self.hp += healed
-
- self.msg(f"You heal for {healed} HP.")
-
+
+ """
+ damage = self.hp_max - self.hp
+ healed = min(damage, hp)
+ self.hp += healed
+
+ self.msg(f"You heal for {healed} HP.")
+
def at_pay(self, amount):
"""When paying coins, make sure to never detract more than we have"""
amount = min(amount, self.coins)
self.coins -= amount
return amount
-
- def at_attacked(self, attacker, **kwargs):
+
+ def at_attacked(self, attacker, **kwargs):
"""Called when being attacked and combat starts."""
pass
-
+
def at_damage(self, damage, attacker=None):
"""Called when attacked and taking damage."""
- self.hp -= damage
-
- def at_defeat(self):
+ self.hp -= damage
+
+ def at_defeat(self):
"""Called when defeated. By default this means death."""
self.at_death()
-
+
def at_death(self):
"""Called when this thing dies."""
# this will mean different things for different living things
- pass
-
+ pass
+
def at_do_loot(self, looted):
- """Called when looting another entity"""
+ """Called when looting another entity"""
looted.at_looted(self)
-
+
def at_looted(self, looter):
- """Called when looted by another entity"""
-
- # default to stealing some coins
- max_steal = dice.roll("1d10")
+ """Called when looted by another entity"""
+
+ # default to stealing some coins
+ max_steal = dice.roll("1d10")
stolen = self.at_pay(max_steal)
looter.coins += stolen
```
Most of these are empty since they will behave differently for characters and npcs. But having them in the mixin means we can expect these methods to be available for all living things.
-Once we create more of our game, we will need to remember to actually call these hook methods so they serve a purpose. For example, once we implement combat, we must remember to call `at_attacked` as well as the other methods involving taking damage, getting defeated or dying.
+Once we create more of our game, we will need to remember to actually call these hook methods so they serve a purpose. For example, once we implement combat, we must remember to call `at_attacked` as well as the other methods involving taking damage, getting defeated or dying.
-## Character class
+## Character class
We will now start making the basic Character class, based on what we need from _Knave_.
@@ -177,28 +178,28 @@ We will now start making the basic Character class, based on what we need from _
# in mygame/evadventure/characters.py
from evennia import DefaultCharacter, AttributeProperty
-from .rules import dice
+from .rules import dice
class LivingMixin:
- # ...
+ # ...
class EvAdventureCharacter(LivingMixin, DefaultCharacter):
- """
- A character to use for EvAdventure.
"""
- is_pc = True
+ A character to use for EvAdventure.
+ """
+ is_pc = True
- strength = AttributeProperty(1)
+ strength = AttributeProperty(1)
dexterity = AttributeProperty(1)
constitution = AttributeProperty(1)
intelligence = AttributeProperty(1)
wisdom = AttributeProperty(1)
charisma = AttributeProperty(1)
-
- hp = AttributeProperty(8)
+
+ hp = AttributeProperty(8)
hp_max = AttributeProperty(8)
-
+
level = AttributeProperty(1)
xp = AttributeProperty(0)
coins = AttributeProperty(0)
@@ -213,18 +214,18 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter):
"$You() $conj(collapse) in a heap, alive but beaten.",
from_obj=self)
self.heal(self.hp_max)
-
+
def at_death(self):
"""We rolled 'dead' on the death table."""
self.location.msg_contents(
"$You() collapse in a heap, embraced by death.",
- from_obj=self)
- # TODO - go back into chargen to make a new character!
+ from_obj=self)
+ # TODO - go back into chargen to make a new character!
```
We make an assumption about our rooms here - that they have a property `.allow_death`. We need to make a note to actually add such a property to rooms later!
-In our `Character` class we implement all attributes we want to simulate from the _Knave_ ruleset. The `AttributeProperty` is one way to add an Attribute in a field-like way; these will be accessible on every character in several ways:
+In our `Character` class we implement all attributes we want to simulate from the _Knave_ ruleset. The `AttributeProperty` is one way to add an Attribute in a field-like way; these will be accessible on every character in several ways:
- As `character.strength`
- As `character.db.strength`
@@ -234,7 +235,7 @@ See [Attributes](../../../Components/Attributes.md) for seeing how Attributes wo
Unlike in base _Knave_, we store `coins` as a separate Attribute rather than as items in the inventory, this makes it easier to handle barter and trading later.
-We implement the Player Character versions of `at_defeat` and `at_death`. We also make use of `.heal()` from the `LivingMixin` class.
+We implement the Player Character versions of `at_defeat` and `at_death`. We also make use of `.heal()` from the `LivingMixin` class.
### Funcparser inlines
@@ -259,68 +260,68 @@ Note how `$conj()` chose `collapse/collapses` to make the sentences grammaticall
We make our first use of the `rules.dice` roller to roll on the death table! As you may recall, in the previous lesson, we didn't know just what to do when rolling 'dead' on this table. Now we know - we should be calling `at_death` on the character. So let's add that where we had TODOs before:
-```python
-# mygame/evadventure/rules.py
+```python
+# mygame/evadventure/rules.py
class EvAdventureRollEngine:
-
- # ...
- def roll_death(self, character):
+ # ...
+
+ def roll_death(self, character):
ability_name = self.roll_random_table("1d8", death_table)
if ability_name == "dead":
# kill the character!
character.at_death() # <------ TODO no more
- else:
- # ...
-
- if current_ability < -10:
+ else:
+ # ...
+
+ if current_ability < -10:
# kill the character!
character.at_death() # <------- TODO no more
else:
- # ...
+ # ...
```
-## Connecting the Character with Evennia
+## Connecting the Character with Evennia
-You can easily make yourself an `EvAdventureCharacter` in-game by using the
-`type` command:
+You can easily make yourself an `EvAdventureCharacter` in-game by using the
+`type` command:
type self = evadventure.characters.EvAdventureCharacter
-You can now do `examine self` to check your type updated.
+You can now do `examine self` to check your type updated.
-If you want _all_ new Characters to be of this type you need to tell Evennia about it. Evennia uses a global setting `BASE_CHARACTER_TYPECLASS` to know which typeclass to use when creating Characters (when logging in, for example). This defaults to `typeclasses.characters.Character` (that is, the `Character` class in `mygame/typeclasses/characters.py`).
+If you want _all_ new Characters to be of this type you need to tell Evennia about it. Evennia uses a global setting `BASE_CHARACTER_TYPECLASS` to know which typeclass to use when creating Characters (when logging in, for example). This defaults to `typeclasses.characters.Character` (that is, the `Character` class in `mygame/typeclasses/characters.py`).
-There are thus two ways to weave your new Character class into Evennia:
+There are thus two ways to weave your new Character class into Evennia:
1. Change `mygame/server/conf/settings.py` and add `BASE_CHARACTER_TYPECLASS = "evadventure.characters.EvAdventureCharacter"`.
-2. Or, change `typeclasses.characters.Character` to inherit from `EvAdventureCharacter`.
+2. Or, change `typeclasses.characters.Character` to inherit from `EvAdventureCharacter`.
You must always reload the server for changes like this to take effect.
```{important}
-In this tutorial we are making all changes in a folder `mygame/evadventure/`. This means we can isolate
-our code but means we need to do some extra steps to tie the character (and other objects) into Evennia.
-For your own game it would be just fine to start editing `mygame/typeclasses/characters.py` directly
+In this tutorial we are making all changes in a folder `mygame/evadventure/`. This means we can isolate
+our code but means we need to do some extra steps to tie the character (and other objects) into Evennia.
+For your own game it would be just fine to start editing `mygame/typeclasses/characters.py` directly
instead.
```
-## Unit Testing
+## Unit Testing
> Create a new module `mygame/evadventure/tests/test_characters.py`
-For testing, we just need to create a new EvAdventure character and check that calling the methods on it doesn't error out.
+For testing, we just need to create a new EvAdventure character and check that calling the methods on it doesn't error out.
```python
-# mygame/evadventure/tests/test_characters.py
+# mygame/evadventure/tests/test_characters.py
from evennia.utils import create
-from evennia.utils.test_resources import BaseEvenniaTest
+from evennia.utils.test_resources import BaseEvenniaTest
-from ..characters import EvAdventureCharacter
+from ..characters import EvAdventureCharacter
class TestCharacters(BaseEvenniaTest):
def setUp(self):
@@ -328,73 +329,73 @@ class TestCharacters(BaseEvenniaTest):
self.character = create.create_object(EvAdventureCharacter, key="testchar")
def test_heal(self):
- self.character.hp = 0
- self.character.hp_max = 8
-
+ self.character.hp = 0
+ self.character.hp_max = 8
+
self.character.heal(1)
self.assertEqual(self.character.hp, 1)
# make sure we can't heal more than max
self.character.heal(100)
self.assertEqual(self.character.hp, 8)
-
+
def test_at_pay(self):
- self.character.coins = 100
-
+ self.character.coins = 100
+
result = self.character.at_pay(60)
- self.assertEqual(result, 60)
+ self.assertEqual(result, 60)
self.assertEqual(self.character.coins, 40)
-
- # can't get more coins than we have
+
+ # can't get more coins than we have
result = self.character.at_pay(100)
self.assertEqual(result, 40)
self.assertEqual(self.character.coins, 0)
-
- # tests for other methods ...
+
+ # tests for other methods ...
```
-If you followed the previous lessons, these tests should look familiar. Consider adding tests for other methods as practice. Refer to previous lessons for details.
+If you followed the previous lessons, these tests should look familiar. Consider adding tests for other methods as practice. Refer to previous lessons for details.
-For running the tests you do:
+For running the tests you do:
evennia test --settings settings.py .evadventure.tests.test_characters
-## About races and classes
+## About races and classes
-_Knave_ doesn't have any D&D-style _classes_ (like Thief, Fighter etc). It also does not bother with _races_ (like dwarves, elves etc). This makes the tutorial shorter, but you may ask yourself how you'd add these functions.
+_Knave_ doesn't have any D&D-style _classes_ (like Thief, Fighter etc). It also does not bother with _races_ (like dwarves, elves etc). This makes the tutorial shorter, but you may ask yourself how you'd add these functions.
-In the framework we have sketched out for _Knave_, it would be simple - you'd add your race/class as an Attribute on your Character:
+In the framework we have sketched out for _Knave_, it would be simple - you'd add your race/class as an Attribute on your Character:
```python
# mygame/evadventure/characters.py
from evennia import DefaultCharacter, AttributeProperty
-# ...
+# ...
class EvAdventureCharacter(LivingMixin, DefaultCharacter):
-
- # ...
+
+ # ...
charclass = AttributeProperty("Fighter")
charrace = AttributeProperty("Human")
```
-We use `charclass` rather than `class` here, because `class` is a reserved Python keyword. Naming `race` as `charrace` thus matches in style.
+We use `charclass` rather than `class` here, because `class` is a reserved Python keyword. Naming `race` as `charrace` thus matches in style.
-We'd then need to expand our [rules module](./Beginner-Tutorial-Rules.md) (and later
+We'd then need to expand our [rules module](./Beginner-Tutorial-Rules.md) (and later
[character generation](./Beginner-Tutorial-Chargen.md) to check and include what these classes mean.
## Summary
-With the `EvAdventureCharacter` class in place, we have a better understanding of how our PCs will look like under _Knave_.
+With the `EvAdventureCharacter` class in place, we have a better understanding of how our PCs will look like under _Knave_.
-For now, we only have bits and pieces and haven't been testing this code in-game. But if you want you can swap yourself into `EvAdventureCharacter` right now. Log into your game and run the command
+For now, we only have bits and pieces and haven't been testing this code in-game. But if you want you can swap yourself into `EvAdventureCharacter` right now. Log into your game and run the command
- type self = evadventure.characters.EvAdventureCharacter
+ type self = evadventure.characters.EvAdventureCharacter
-If all went well, `ex self` will now show your typeclass as being `EvAdventureCharacter`. Check out your strength with
+If all went well, `ex self` will now show your typeclass as being `EvAdventureCharacter`. Check out your strength with
py self.strength = 3
diff --git a/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.md.txt b/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.md.txt
index 9932a5a5a1..d2587d1f1b 100644
--- a/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.md.txt
+++ b/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.md.txt
@@ -164,7 +164,7 @@ class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
def get_sides(self, combatant):
"""
Get a listing of the two 'sides' of this combat,
- m the perspective of the provided combatant.
+ from the perspective of the provided combatant.
"""
if self.obj.allow_pvp:
# in pvp, everyone else is an ememy
@@ -218,7 +218,7 @@ class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
We use the `advantage/disadvantage_matrix` Attributes to track who has advantage against whom.
```{sidebar} .pop()
-The Python `.pop()` method exists on lists and dicts as well as some other iterables. It 'pops' and returns an element from the container. For a list, it's either popped by index or by popping the last element. For a dict (like here), a specific key must be given to pop. If you don't provide a default value as a second element, an error will be raised if the key you try to pop is not found.
+The Python `.pop()` method removes an element from a list or dict and returns it. For a list, it removes by index (or the last element by default). For a dict (like here), you specify which key to remove. Providing a default value as a second argument prevents an error if the key doesn't exist.
```
In the `has dis/advantage` methods we `pop` the target from the matrix which will result either in the value `True` or `False` (the default value we give to `pop` if the target is not in the matrix). This means that the advantage, once gained, can only be used once.
@@ -478,7 +478,7 @@ class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler):
surviving_combatant = None
allies, enemies = (), ()
else:
- # grab a random survivor and check of they have any living enemies.
+ # grab a random survivor and check if they have any living enemies.
surviving_combatant = random.choice(list(self.combatants.keys()))
allies, enemies = self.get_sides(surviving_combatant)
@@ -722,7 +722,7 @@ Using this in an option will rerun the current node, but will preserve the `kwar
### Stepping through the wizard
-Our particualr menu is very symmetric - you select an option and then you will just select a series of option before you come back. So we will make another goto-function to help us easily do this. To understand, let's first show how we plan to use this:
+Our particular menu is very symmetric - you select an option and then you will just select a series of option before you come back. So we will make another goto-function to help us easily do this. To understand, let's first show how we plan to use this:
```python
# in the base combat-node function (just shown as an example)
@@ -816,7 +816,7 @@ We will make one final helper function, to quickly add the `back` (and `abort`)
# ...
-_get_default_wizard_options(caller, **kwargs):
+def _get_default_wizard_options(caller, **kwargs):
return [
{
"key": "back",
@@ -932,7 +932,6 @@ def node_choose_ability(caller, raw_string, **kwargs):
Ability.DEX,
Ability.CON,
Ability.INT,
- Ability.INT,
Ability.WIS,
Ability.CHA,
)
@@ -979,7 +978,7 @@ def node_choose_wield_item(caller, raw_string, **kwargs):
Our [equipment handler](./Beginner-Tutorial-Equipment.md) has the very useful help method `.get_usable_objects_from_backpack`. We just call this to get a list of all the items we want to choose. Otherwise this node should look pretty familiar by now.
-The `node_choose_wiqld_item` is very similar, except it uses `caller.equipment.get_wieldable_objects_from_backpack()` instead. We'll leave the implementation of this up to the reader.
+The `node_choose_wield_item` is very similar, except it uses `caller.equipment.get_wieldable_objects_from_backpack()` instead. We'll leave the implementation of this up to the reader.
### The main menu node
@@ -1084,11 +1083,11 @@ def node_combat(caller, raw_string, **kwargs):
This starts off the `_step_wizard` for each action choice. It also lays out the `action_dict` for every action, leaving `None` values for the fields that will be set by the following nodes.
-Note how we add the `"repeat"` key to some actions. Having them automatically repeat means the player don't have to insert the same action every time.
+Note how we add the `"repeat"` key to some actions. Having them automatically repeat means the player doesn't have to insert the same action every time.
## Attack Command
-We will only need one single Command to run the Turnbased combat system. This is the `attack` command. Once you use it once, you will be in the menu.
+We will only need one single command to run the turnbased combat system. This is the `attack` command. Once you use it once, you will be in the menu.
```python
@@ -1317,7 +1316,7 @@ But apart from the `# HEADER` and `# CODE` specials, this just a series of norma
Log into the game with a developer/superuser account and run
- > batchcmd evadventure.batchscripts.turnbased_combat_demo
+ > batchcode evadventure.batchscripts.turnbased_combat_demo
This should place you in the arena with the dummy (if not, check for errors in the output! Use `objects` and `delete` commands to list and delete objects if you need to start over.)
@@ -1325,6 +1324,6 @@ You can now try `attack dummy` and should be able to pound away at the dummy (lo
## Conclusions
-At this point we have coverered some ideas on how to implement both twitch- and turnbased combat systems. Along the way you have been exposed to many concepts such as classes, scripts and handlers, Commands, EvMenus and more.
+At this point we have covered some ideas on how to implement both twitch- and turnbased combat systems. Along the way you have been exposed to many concepts such as classes, scripts and handlers, Commands, EvMenus and more.
Before our combat system is actually usable, we need our enemies to actually fight back. We'll get to that next.
diff --git a/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.md.txt b/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.md.txt
index 90fc994457..fc2137a6da 100644
--- a/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.md.txt
+++ b/docs/latest/_sources/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.md.txt
@@ -259,7 +259,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatBaseHandler):
"""
if action_dict["key"] not in self.action_classes:
- self.obj.msg("This is an unkown action!")
+ self.obj.msg("This is an unknown action!")
return
# store action dict and schedule it to run in dt time
@@ -285,7 +285,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatBaseHandler):
- **Line 43**: We simply store the given action dict in the Attribute `action_dict` on the handler. Simple and effective!
- **Line 44**: When you enter e.g. `attack`, you expect in this type of combat to see the `attack` command repeat automatically even if you don't enter anything more. To this end we are looking for a new key in action dicts, indicating that this action should _repeat_ with a certain rate (`dt`, given in seconds). We make this compatible with all action dicts by simply assuming it's zero if not specified.
- [evennia.utils.utils.repeat](evennia.utils.utils.repeat) and [evennia.utils.utils.unrepeat](evennia.utils.utils.unrepeat) are convenient shortcuts to the [TickerHandler](../../../Components/TickerHandler.md). You tell `repeat` to call a given method/function at a certain rate. What you get back is a reference that you can then later use to 'un-repeat' (stop the repeating) later. We make sure to store this reference (we don't care exactly how it looks, just that we need to store it) in `the current_ticket_ref` Attribute (**Line 26**).
+ [evennia.utils.utils.repeat](evennia.utils.utils.repeat) and [evennia.utils.utils.unrepeat](evennia.utils.utils.unrepeat) are convenient shortcuts to the [TickerHandler](../../../Components/TickerHandler.md). You tell `repeat` to call a given method/function at a certain rate. What you get back is a reference that you can then later use to 'un-repeat' (stop the repeating) later. We make sure to store this reference (we don't care exactly how it looks, just that we need to store it) in the `current_ticker_ref` Attribute (**Line 26**).
- **Line 48**: Whenever we queue a new action (it may replace an existing one) we must make sure to kill (un-repeat) any old repeats that are ongoing. Otherwise we would get old actions firing over and over and new ones starting alongside them.
- **Line 49**: If `dt` is set, we call `repeat` to set up a new repeat action at the given rate. We store this new reference. After `dt` seconds, the `.execute_next_action` method will fire (we'll create that in the next section).
@@ -305,30 +305,30 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatBaseHandler):
# ...
def execute_next_action(self):
- """
- Triggered after a delay by the command
- """
- combatant = self.obj
- action_dict = self.action_dict
- action_class = self.action_classes[action_dict["key"]]
- action = action_class(self, combatant, action_dict)
-
- if action.can_use():
- action.execute()
- action.post_execute()
-
- if not action_dict.get("repeat", True):
- # not a repeating action, use the fallback (normally the original attack)
- self.action_dict = self.fallback_action_dict
- self.queue_action(self.fallback_action_dict)
-
- self.check_stop_combat()
+ """
+ Triggered after a delay by the command
+ """
+ combatant = self.obj
+ action_dict = self.action_dict
+ action_class = self.action_classes[action_dict["key"]]
+ action = action_class(self, combatant, action_dict)
+
+ if action.can_use():
+ action.execute()
+ action.post_execute()
+
+ if not action_dict.get("repeat", True):
+ # not a repeating action, use the fallback (normally the original attack)
+ self.action_dict = self.fallback_action_dict
+ self.queue_action(self.fallback_action_dict)
+
+ self.check_stop_combat()
```
This is the method called after `dt` seconds in `queue_action`.
- **Line 5**: We defined a 'fallback action'. This is used after a one-time action (one that should not repeat) has completed.
-- **Line 15**: We take the `'key'` from the `action-dict` and use the `action_classes` mapping to get an action class (e.g. `ACtionAttack` we defined [here](./Beginner-Tutorial-Combat-Base.md#attack-action)).
+- **Line 15**: We take the `'key'` from the `action_dict` and use the `action_classes` mapping to get an action class (e.g. `ActionAttack` we defined [here](./Beginner-Tutorial-Combat-Base.md#attack-action)).
- **Line 16**: Here we initialize the action class with the actual current data - the combatant and the `action_dict`. This calls the `__init__` method on the class and makes the action ready to use.
```{sidebar} New action-dict keys
To summarize, for twitch-combat use we have now introduced two new keys to action-dicts:
@@ -365,7 +365,7 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatBaseHandler):
enemies = [comb for comb in enemies if comb.hp > 0 and comb.location == location]
if not allies and not enemies:
- self.msg("The combat is over. Noone stands.", broadcast=False)
+ self.msg("The combat is over. No one stands.", broadcast=False)
self.stop_combat()
return
if not allies:
@@ -384,7 +384,7 @@ We must make sure to check if combat is over.
- **Line 12**: With our `.get_sides()` method we can easily get the two sides of the conflict.
- **Lines 18, 19**: We get everyone still alive _and still in the same room_. The latter condition is important in case we move away from the battle - you can't hit your enemy from another room.
-In the `stop_method` we'll need to do a bunch of cleanup. We'll hold off on implementing this until we have the Commands written out. Read on.
+In the `stop_combat` method we'll need to do a bunch of cleanup. We'll hold off on implementing this until we have the Commands written out. Read on.
## Commands
@@ -416,7 +416,7 @@ from evennia import InterruptCommand
class _BaseTwitchCombatCommand(Command):
"""
- Parent class for all twitch-combat commnads.
+ Parent class for all twitch-combat commands.
"""
@@ -641,7 +641,7 @@ class CmdStunt(_BaseTwitchCombatCommand):
# something like `boost str target`
target = recipient if advantage else "me"
recipient = "me" if advantage else recipient
- we still have None:s at this point, we can't continue
+ # if any values are still None at this point, we can't continue
if None in (stunt_type, recipient, target):
self.msg("Both ability, recipient and target of stunt must be given.")
raise InterruptCommand()
@@ -677,7 +677,7 @@ class CmdStunt(_BaseTwitchCombatCommand):
```
-This looks much longer, but that is only because the stunt command should understand many different input structures depending on if you are trying to create a advantage or disadvantage, and if an ally or enemy should receive the effect of the stunt.
+This looks much longer, but that is only because the stunt command should understand many different input structures depending on if you are trying to create an advantage or disadvantage, and if an ally or enemy should receive the effect of the stunt.
Note the `enums.ABILITY_REVERSE_MAP` (created in the [Utilities lesson](./Beginner-Tutorial-Utilities.md)) being useful to convert your input of 'str' into `Ability.STR` needed by the action dict.
@@ -754,8 +754,8 @@ To use an item, we need to make sure we are carrying it. Luckily our work in the
class CmdWield(_BaseTwitchCombatCommand):
"""
- Wield a weapon or spell-rune. You will the wield the item,
- swapping with any other item(s) you were wielded before.
+ Wield a weapon or spell-rune. You wield the item,
+ swapping with any other item(s) you were wielding before.
Usage:
wield
@@ -893,7 +893,7 @@ from .. import combat_twitch
# ...
-class TestEvAdventureTwitchCombat(EvenniaCommandTestMixin)
+class TestEvAdventureTwitchCombat(EvenniaCommandTestMixin):
def setUp(self):
self.combathandler = (
@@ -904,12 +904,12 @@ class TestEvAdventureTwitchCombat(EvenniaCommandTestMixin)
@patch("evadventure.combat_twitch.unrepeat", new=Mock())
@patch("evadventure.combat_twitch.repeat", new=Mock())
def test_hold_command(self):
- self.call(combat_twitch, CmdHold(), "", "You hold back, doing nothing")
+ self.call(combat_twitch, CmdHold(), "", "You hold back, doing nothing.")
self.assertEqual(self.combathandler.action_dict, {"key": "hold"})
```
-The `EvenniaCommandTestMixin` as a few default objects, including `self.char1`, which we make use of here.
+The `EvenniaCommandTestMixin` has a few default objects, including `self.char1`, which we make use of here.
The two `@patch` lines are Python [decorators](https://realpython.com/primer-on-python-decorators/) that 'patch' the `test_hold_command` method. What they do is basically saying "in the following method, whenever any code tries to access `evadventure.combat_twitch.un/repeat`, just return a Mocked object instead".
diff --git a/docs/latest/_sources/Setup/Installation-Troubleshooting.md.txt b/docs/latest/_sources/Setup/Installation-Troubleshooting.md.txt
index f862437e27..f89478da4b 100644
--- a/docs/latest/_sources/Setup/Installation-Troubleshooting.md.txt
+++ b/docs/latest/_sources/Setup/Installation-Troubleshooting.md.txt
@@ -12,18 +12,18 @@ Any system that supports Python3.10+ should work.
- Windows (Win7, Win8, Win10, Win11)
- Mac OSX (>10.5 recommended)
-- [Python](https://www.python.org) (3.10, 3.11 and 3.12 are tested. 3.12 is recommended)
-- [Twisted](https://twistedmatrix.com) (v23.10+)
+- [Python](https://www.python.org) (3.11, 3.12 and 3.13 are tested. 3.13 is recommended)
+- [Twisted](https://twistedmatrix.com) (v24.11+)
- [ZopeInterface](https://www.zope.org/Products/ZopeInterface) (v3.0+) - usually included in Twisted packages
- Linux/Mac users may need the `gcc` and `python-dev` packages or equivalent.
- Windows users need [MS Visual C++](https://aka.ms/vs/16/release/vs_buildtools.exe) and *maybe* [pypiwin32](https://pypi.python.org/pypi/pypiwin32).
-- [Django](https://www.djangoproject.com) (v4.2+), be warned that latest dev version is usually untested with Evennia.
-- [GIT](https://git-scm.com/) - version control software used if you want to install the sources (but also useful to track your own code)
+- [Django](https://www.djangoproject.com) (v5.2+), be warned that latest dev version is usually untested with Evennia.
+- [GIT](https://git-scm.com/) - version control software used if you want to install the sources (but also useful to track your own code)
- Mac users can use the [git-osx-installer](https://code.google.com/p/git-osx-installer/) or the [MacPorts version](https://git-scm.com/book/en/Getting-Started-Installing-Git#Installing-on-Mac).
## Confusion of location (GIT installation)
-When doing the [Git installation](./Installation-Git.md), some may be confused and install Evennia in the wrong location. After following the instructions (and using a virtualenv), the folder structure should look like this:
+When doing the [Git installation](./Installation-Git.md), some may be confused and install Evennia in the wrong location. After following the instructions (and using a virtualenv), the folder structure should look like this:
```
muddev/
@@ -34,14 +34,14 @@ muddev/
The evennia library code itself is found inside `evennia/evennia/` (so two levels down). You shouldn't change this; do all work in `mygame/`. Your settings file is `mygame/server/conf/settings.py` and the _parent_ setting file is `evennia/evennia/settings_default.py`.
-## Virtualenv setup fails
+## Virtualenv setup fails
When doing the `python3.x -m venv evenv` (where x is the python3 version) step, some users report getting an error; something like:
- Error: Command '['evenv', '-Im', 'ensurepip', '--upgrade', '--default-pip']'
+ Error: Command '['evenv', '-Im', 'ensurepip', '--upgrade', '--default-pip']'
returned non-zero exit status 1
-You can solve this by installing the `python3.11-venv` (or later) package (or equivalent for your OS). Alternatively you can bootstrap it in this way:
+You can solve this by installing the `python3.11-venv` (or later) package (or equivalent for your OS). Alternatively you can bootstrap it in this way:
python3.x -m --without-pip evenv
@@ -49,13 +49,13 @@ This should set up the virtualenv without `pip`. Activate the new virtualenv and
python -m ensurepip --upgrade
-If that fails, a worse alternative to try is
+If that fails, a worse alternative to try is
curl https://bootstrap.pypa.io/get-pip.py | python3.x (linux/unix/WSL only)
Either way, you should now be able to continue with the installation.
-## Localhost not found
+## Localhost not found
If `localhost` doesn't work when trying to connect to your local game, try `127.0.0.1`, which is the same thing.
@@ -63,7 +63,7 @@ If `localhost` doesn't work when trying to connect to your local game, try `127.
- If you get an error when installing Evennia (especially with lines mentioning
failing to include `Python.h`) then try `sudo apt-get install python3-setuptools python3-dev`. Once installed, run `pip install -e evennia` again.
-- When doing a [git install](./Installation-Git.md), some not-updated Linux distributions may give errors
+- When doing a [git install](./Installation-Git.md), some not-updated Linux distributions may give errors
about a too-old `setuptools` or missing `functools`. If so, update your environment
with `pip install --upgrade pip wheel setuptools`. Then try `pip install -e evennia` again.
- One user reported a rare issue on Ubuntu 16 is an install error on installing Twisted; `Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vnIFTg/twisted/` with errors like `distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('incremental>=16.10.1')`. This appears possible to solve by simply updating Ubuntu with `sudo apt-get update && sudo apt-get dist-upgrade`.
@@ -80,9 +80,9 @@ If `localhost` doesn't work when trying to connect to your local game, try `127.
## Windows Troubleshooting
- If you install with `pip install evennia` and find that the `evennia` command is not available, run `py -m evennia` once. This should add the evennia binary to your environment. If this fails, make sure you are using a [virtualenv](./Installation-Git.md#virtualenv). Worst case, you can keep using `py -m evennia` in the places where the `evennia` command is used.
-- - If you get a `command not found` when trying to run the `evennia` program directly after installation, try closing the Windows Console and starting it again (remember to re-activate the virtualenv if you use one!). Sometimes Windows is not updating its environment properly and `evennia` will be available only in the new console.
+- - If you get a `command not found` when trying to run the `evennia` program directly after installation, try closing the Windows Console and starting it again (remember to re-activate the virtualenv if you use one!). Sometimes Windows is not updating its environment properly and `evennia` will be available only in the new console.
- If you installed Python but the `python` command is not available (even in a new console), then you might have missed installing Python on the path. In the Windows Python installer you get a list of options for what to install. Most or all options are pre-checked except this one, and you may even have to scroll down to see it. Reinstall Python and make sure it's checked. Install Python [from the Python homepage](https://www.python.org/downloads/windows/). You will need to be a Windows Administrator to install packages.
- If your MUD client cannot connect to `localhost:4000`, try the equivalent `127.0.0.1:4000` instead. Some MUD clients on Windows does not appear to understand the alias `localhost`.
- Some Windows users get an error installing the Twisted 'wheel'. A wheel is a pre-compiled binary package for Python. A common reason for this error is that you are using a 32-bit version of Python, but Twisted has not yet uploaded the latest 32-bit wheel. Easiest way to fix this is to install a slightly older Twisted version. So if, say, version `22.1` failed, install `22.0` manually with `pip install twisted==22.0`. Alternatively you could check that you are using the 64-bit version of Python and uninstall any 32bit one. If so, you must then `deactivate` the virtualenv, delete the `evenv` folder and recreate it anew with your new Python.
- If you've done a git installation, and your server won't start with an error message like `AttributeError: module 'evennia' has no attribute '_init'`, it may be a python path issue. In a terminal, cd to `(your python directory)\site-packages` and run the command `echo "C:\absolute\path\to\evennia" > local-vendors.pth`. Open the created file in your favorite IDE and make sure it is saved with *UTF-8* encoding and not *UTF-8 with BOM*.
-- Some users have reported issues with Windows WSL and anti-virus software during Evennia development. Timeout errors and the inability to run `evennia connections` may be due to your anti-virus software interfering. Try disabling or changing your anti-virus software settings.
\ No newline at end of file
+- Some users have reported issues with Windows WSL and anti-virus software during Evennia development. Timeout errors and the inability to run `evennia connections` may be due to your anti-virus software interfering. Try disabling or changing your anti-virus software settings.
diff --git a/docs/latest/_sources/Setup/Installation.md.txt b/docs/latest/_sources/Setup/Installation.md.txt
index d952751ef1..080d548c54 100644
--- a/docs/latest/_sources/Setup/Installation.md.txt
+++ b/docs/latest/_sources/Setup/Installation.md.txt
@@ -1,9 +1,5 @@
# Installation
-```{important}
-If you are converting an existing game from a previous Evennia version, you will need to upgrade.
-```
-
The fastest way to install Evennia is to use the `pip` installer that comes with Python (read on). You can also [clone Evennia from github](./Installation-Git.md) or use [docker](./Installation-Docker.md). Some users have also experimented with [installing Evennia on Android](./Installation-Android.md).
If you are converting an existing game, please follow the [upgrade instructions](./Installation-Upgrade.md).
@@ -13,9 +9,9 @@ If you are converting an existing game, please follow the [upgrade instructions]
```{sidebar} Develop in isolation
Installing Evennia doesn't make anything visible online. Apart from installation and updating, you can develop your game without any internet connection if you want to.
```
-- Evennia requires [Python](https://www.python.org/downloads/) 3.10, 3.11 or 3.12 (recommended). Any OS that supports Python should work.
+- Evennia requires [Python](https://www.python.org/downloads/) 3.11, 3.12 or 3.13 (recommended). Any OS that supports Python should work.
- _Windows_: In the installer, make sure to select `add python to path`. If you have multiple versions of Python installed, use `py` command instead of `python` to have Windows automatically use the latest.
-- Don't install Evennia as administrator or superuser.
+- Don't install Evennia as administrator or superuser.
- If you run into trouble, see [installation troubleshooting](./Installation-Troubleshooting.md).
## Install with `pip`
@@ -34,11 +30,11 @@ Optional: If you use a [contrib](../Contribs/Contribs-Overview.md) that warns yo
pip install evennia[extra]
-To update Evennia later, do the following:
+To update Evennia later, do the following:
pip install --upgrade evennia
-```{note} **Windows users only -**
+```{note} **Windows users only -**
You now must run `python -m evennia` once. This should permanently make the `evennia` command available in your environment.
```
@@ -46,7 +42,7 @@ Once installed, make sure the `evennia` command works. Use `evennia -h` for usag
## Initialize a New Game
-We will create a new "game dir" in which to create your game. Here, and in the rest of the Evennia documentation, we refer to this game dir as `mygame`, but you should, of course, name your game whatever you like. To create the new `mygame` folder—or whatever you choose—in your current location:
+We will create a new "game dir" in which to create your game. Here, and in the rest of the Evennia documentation, we refer to this game dir as `mygame`, but you should, of course, name your game whatever you like. To create the new `mygame` folder—or whatever you choose—in your current location:
```{sidebar} Game Dir vs Game Name
The game dir you create doesn't have to match the name of your game. You can change the name of your game later by editing `mygame/server/conf/settings.py`.
@@ -102,21 +98,21 @@ or just:
evennia -l
-Press `Ctrl-C` (`Cmd-C` for Mac) to stop viewing the live log.
+Press `Ctrl-C` (`Cmd-C` for Mac) to stop viewing the live log.
You may also begin viewing the real-time log immediately by adding `-l/--log` to `evennia` commands, such as when starting the server:
evennia start -l
-## Server Configuration
+## Server Configuration
Your server's configuration file is `mygame/server/conf/settings.py`. It's empty by default. Copy and paste **only** the settings you want/need from the [default settings file](./Settings-Default.md) to your server's `settings.py`. See the [Settings](./Settings.md) documentation for more information before configuring your server at this time.
-
+
## Register with the Evennia Game Index (optional)
-To let the world know that you are working on a new Evennia-based game, you may register your server with the _Evennia game index_ by issuing:
+To let the world know that you are working on a new Evennia-based game, you may register your server with the _Evennia game index_ by issuing:
- evennia connections
+ evennia connections
Then, just follow the prompts. You don't have to be open for players to do this — simply mark your game as closed and "pre-alpha."
@@ -124,6 +120,6 @@ See [here](./Evennia-Game-Index.md) for more instructions and please [check out
## Next Steps
-You are good to go!
+You are good to go!
Next, why not head over to the [Starting Tutorial](../Howtos/Beginner-Tutorial/Beginner-Tutorial-Overview.md) to learn how to begin making your new game!
diff --git a/docs/latest/_sources/Setup/Settings-Default.md.txt b/docs/latest/_sources/Setup/Settings-Default.md.txt
index f7f0dc29c1..353ebcccd8 100644
--- a/docs/latest/_sources/Setup/Settings-Default.md.txt
+++ b/docs/latest/_sources/Setup/Settings-Default.md.txt
@@ -376,16 +376,16 @@ WEBCLIENT_OPTIONS = {
# The command parser module to use. See the default module for which
# functions it must implement
COMMAND_PARSER = "evennia.commands.cmdparser.cmdparser"
-# On a multi-match when search objects or commands, the user has the
+# On a multi-match when searching objects or commands, the user has the
# ability to search again with an index marker that differentiates
-# the results. If multiple "box" objects
-# are found, they can by default be separated as 1-box, 2-box. Below you
-# can change the regular expression used. The regex must have one
-# have two capturing groups (?P...) and (?P...) - the default
-# parser expects this. It should also involve a number starting from 1.
-# When changing this you must also update SEARCH_MULTIMATCH_TEMPLATE
+# the results. If multiple "box" objects are found, they can by default
+# be separated as box-1, box-2. Below you can change the regular expression
+# used. The regex must have two capturing groups (?P...) and
+# (?P...) - the default parser expects this. It may also have an
+# optional (?P...) group. It should also involve a number starting
+# from 1. When changing this you must also update SEARCH_MULTIMATCH_TEMPLATE
# to properly describe the syntax.
-SEARCH_MULTIMATCH_REGEX = r"(?P[^-]*)-(?P[0-9]+)(?P.*)"
+SEARCH_MULTIMATCH_REGEX = r"^(?P.*?)-(?P[0-9]+)(?P(?:\s.*)?)$"
# To display multimatch errors in various listings we must display
# the syntax in a way that matches what SEARCH_MULTIMATCH_REGEX understand.
# The template will be populated with data and expects the following markup:
diff --git a/docs/latest/_sources/index.md.txt b/docs/latest/_sources/index.md.txt
index fbbf17320a..66c14767a5 100644
--- a/docs/latest/_sources/index.md.txt
+++ b/docs/latest/_sources/index.md.txt
@@ -1,6 +1,6 @@
# Evennia Documentation
-This is the manual of [Evennia](https://www.evennia.com), the open source Python `MU*` creation system. Use the Search bar on the left to find or discover interesting articles. This manual was last updated outubro 26, 2024, see the [Evennia Changelog](Coding/Changelog.md). Latest released Evennia version is 5.0.1.
+This is the manual of [Evennia](https://www.evennia.com), the open source Python `MU*` creation system. Use the Search bar on the left to find or discover interesting articles. This manual was last updated January 12, 2026, see the [Evennia Changelog](Coding/Changelog.md). Latest released Evennia version is 5.0.1.
- [Introduction](./Evennia-Introduction.md) - what is this Evennia thing?
- [Evennia in Pictures](./Evennia-In-Pictures.md) - a visual overview of Evennia
diff --git a/docs/latest/api/evennia-api.html b/docs/latest/api/evennia-api.html
index c47026f185..302605a4e5 100644
--- a/docs/latest/api/evennia-api.html
+++ b/docs/latest/api/evennia-api.html
@@ -712,21 +712,6 @@
NoCmdSets
-WeakValueDictionary
-
chain
@@ -1898,6 +1883,7 @@
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -2270,6 +2256,7 @@
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -3243,6 +3230,10 @@
TestSystemCommands.test_multimatch()
+TestPreCmdOutputTestable
+
evennia.commands.default.unloggedin
NoCmdSets
-WeakValueDictionary
-
chain
diff --git a/docs/latest/api/evennia.commands.default.account.html b/docs/latest/api/evennia.commands.default.account.html
index 50128e9025..6e191d83a2 100644
--- a/docs/latest/api/evennia.commands.default.account.html
+++ b/docs/latest/api/evennia.commands.default.account.html
@@ -81,7 +81,7 @@ method. Otherwise all text will be returned to all connected sessions.
-
-aliases = ['l', 'ls']
+aliases = ['ls', 'l']
@@ -112,7 +112,7 @@ method. Otherwise all text will be returned to all connected sessions.
-
-search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\nlook while out-of-character\n\nUsage:\n look\n\nLook in the ooc state.\n'}
+search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\nlook while out-of-character\n\nUsage:\n look\n\nLook in the ooc state.\n'}
diff --git a/docs/latest/api/evennia.commands.default.batchprocess.html b/docs/latest/api/evennia.commands.default.batchprocess.html
index 86c366f922..61ffa25bbd 100644
--- a/docs/latest/api/evennia.commands.default.batchprocess.html
+++ b/docs/latest/api/evennia.commands.default.batchprocess.html
@@ -86,7 +86,7 @@ skipping, reloading etc.
-
-aliases = ['batchcmd', 'batchcommand']
+aliases = ['batchcommand', 'batchcmd']
@@ -117,7 +117,7 @@ skipping, reloading etc.
-
-search_index_entry = {'aliases': 'batchcmd batchcommand', 'category': 'building', 'key': 'batchcommands', 'no_prefix': ' batchcmd batchcommand', 'tags': '', 'text': '\nbuild from batch-command file\n\nUsage:\n batchcommands[/interactive] <python.path.to.file>\n\nSwitch:\n interactive - this mode will offer more control when\n executing the batch file, like stepping,\n skipping, reloading etc.\n\nRuns batches of commands from a batch-cmd text file (*.ev).\n\n'}
+search_index_entry = {'aliases': 'batchcommand batchcmd', 'category': 'building', 'key': 'batchcommands', 'no_prefix': ' batchcommand batchcmd', 'tags': '', 'text': '\nbuild from batch-command file\n\nUsage:\n batchcommands[/interactive] <python.path.to.file>\n\nSwitch:\n interactive - this mode will offer more control when\n executing the batch file, like stepping,\n skipping, reloading etc.\n\nRuns batches of commands from a batch-cmd text file (*.ev).\n\n'}
diff --git a/docs/latest/api/evennia.commands.default.building.html b/docs/latest/api/evennia.commands.default.building.html
index ee810e9571..32f97b854a 100644
--- a/docs/latest/api/evennia.commands.default.building.html
+++ b/docs/latest/api/evennia.commands.default.building.html
@@ -1353,7 +1353,7 @@ server settings.
-
-aliases = ['@update', '@typeclasses', '@swap', '@parent', '@type']
+aliases = ['@type', '@parent', '@update', '@swap', '@typeclasses']
@@ -1384,7 +1384,7 @@ server settings.
-
-search_index_entry = {'aliases': '@update @typeclasses @swap @parent @type', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass update typeclasses swap parent type', 'tags': '', 'text': "\nset or change an object's typeclass\n\nUsage:\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\nSwitch:\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\nExample:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\nIf the typeclass_path is not given, the current object's typeclass is\nassumed.\n\nView or set an object's typeclass. If setting, the creation hooks of the\nnew typeclass will be run on the object. If you have clashing properties on\nthe old class, use /reset. By default you are protected from changing to a\ntypeclass of the same name as the one you already have - use /force to\noverride this protection.\n\nThe given typeclass must be identified by its location using python\ndot-notation pointing to the correct module and class. If no typeclass is\ngiven (or a wrong typeclass is given). Errors in the path or new typeclass\nwill lead to the old typeclass being kept. The location of the typeclass\nmodule is searched from the default typeclass directory, as defined in the\nserver settings.\n\n"}
+search_index_entry = {'aliases': '@type @parent @update @swap @typeclasses', 'category': 'building', 'key': '@typeclass', 'no_prefix': 'typeclass type parent update swap typeclasses', 'tags': '', 'text': "\nset or change an object's typeclass\n\nUsage:\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\nSwitch:\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\nExample:\n type button = examples.red_button.RedButton\n type/prototype button=a red button\n\nIf the typeclass_path is not given, the current object's typeclass is\nassumed.\n\nView or set an object's typeclass. If setting, the creation hooks of the\nnew typeclass will be run on the object. If you have clashing properties on\nthe old class, use /reset. By default you are protected from changing to a\ntypeclass of the same name as the one you already have - use /force to\noverride this protection.\n\nThe given typeclass must be identified by its location using python\ndot-notation pointing to the correct module and class. If no typeclass is\ngiven (or a wrong typeclass is given). Errors in the path or new typeclass\nwill lead to the old typeclass being kept. The location of the typeclass\nmodule is searched from the default typeclass directory, as defined in the\nserver settings.\n\n"}
@@ -1539,7 +1539,7 @@ If object is not specified, the current location is examined.
-
-aliases = ['@ex', '@exam']
+aliases = ['@exam', '@ex']
@@ -1812,7 +1812,7 @@ the cases, see the module doc.
-
-search_index_entry = {'aliases': '@ex @exam', 'category': 'building', 'key': '@examine', 'no_prefix': 'examine ex exam', 'tags': '', 'text': '\nget detailed information about an object\n\nUsage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\nSwitch:\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\nThe examine command shows detailed game info about an\nobject and optionally a specific attribute on it.\nIf object is not specified, the current location is examined.\n\nAppend 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': '\nget detailed information about an object\n\nUsage:\n examine [<object>[/attrname]]\n examine [*<account>[/attrname]]\n\nSwitch:\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\nThe examine command shows detailed game info about an\nobject and optionally a specific attribute on it.\nIf object is not specified, the current location is examined.\n\nAppend a * before the search string to examine an account.\n\n'}
@@ -3550,6 +3550,11 @@ matches against the expressions.
conditional = True
+
+-
+connectors = (None, 'AND', 'OR', 'XOR')
+
+
-
deconstruct()[source]
@@ -4750,6 +4755,7 @@ are replaced by the default argument.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.commands.default.comms.html b/docs/latest/api/evennia.commands.default.comms.html
index 97306e5473..a6dd806bcc 100644
--- a/docs/latest/api/evennia.commands.default.comms.html
+++ b/docs/latest/api/evennia.commands.default.comms.html
@@ -3126,6 +3126,11 @@ matches against the expressions.
conditional = True
+
+-
+connectors = (None, 'AND', 'OR', 'XOR')
+
+
-
deconstruct()[source]
@@ -3586,6 +3591,7 @@ all if the file is shorter than nlines.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.commands.default.general.html b/docs/latest/api/evennia.commands.default.general.html
index 23fa58f3a1..76a82f412d 100644
--- a/docs/latest/api/evennia.commands.default.general.html
+++ b/docs/latest/api/evennia.commands.default.general.html
@@ -123,7 +123,7 @@ look *<account&g
-
-aliases = ['l', 'ls']
+aliases = ['ls', 'l']
@@ -154,7 +154,7 @@ look *<account&g
-
-search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\nlook at location or object\n\nUsage:\n look\n look <obj>\n look *<account>\n\nObserves your location or objects in your vicinity.\n'}
+search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\nlook at location or object\n\nUsage:\n look\n look <obj>\n look *<account>\n\nObserves your location or objects in your vicinity.\n'}
@@ -216,7 +216,7 @@ for everyone to use, you need build privileges and the alias command.
-
-aliases = ['nickname', 'nicks']
+aliases = ['nicks', 'nickname']
@@ -248,7 +248,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': '\ndefine a personal alias/nick by defining a string to\nmatch and replace it with another on the fly\n\nUsage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\nSwitches:\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\nExamples:\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\nA \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\nPut the last $-marker without an ending space to catch all remaining text. You\ncan 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\nNote that no objects are actually renamed or changed by this command - your nicks\nare only available to you. If you want to permanently add keywords to an object\nfor 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': '\ndefine a personal alias/nick by defining a string to\nmatch and replace it with another on the fly\n\nUsage:\n nick[/switches] <string> [= [replacement_string]]\n nick[/switches] <template> = <replacement_template>\n nick/delete <string> or number\n nicks\n\nSwitches:\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\nExamples:\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\nA \'nick\' is a personal string replacement. Use $1, $2, ... to catch arguments.\nPut the last $-marker without an ending space to catch all remaining text. You\ncan 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\nNote that no objects are actually renamed or changed by this command - your nicks\nare only available to you. If you want to permanently add keywords to an object\nfor everyone to use, you need build privileges and the alias command.\n\n'}
@@ -545,7 +545,7 @@ placing it in their inventory.
-
-aliases = ['"', "'"]
+aliases = ["'", '"']
@@ -576,7 +576,7 @@ placing it in their inventory.
-
-search_index_entry = {'aliases': '" \'', 'category': 'general', 'key': 'say', 'no_prefix': ' " \'', 'tags': '', 'text': '\nspeak as your character\n\nUsage:\n say <message>\n\nTalk to those in your current location.\n'}
+search_index_entry = {'aliases': '\' "', 'category': 'general', 'key': 'say', 'no_prefix': ' \' "', 'tags': '', 'text': '\nspeak as your character\n\nUsage:\n say <message>\n\nTalk to those in your current location.\n'}
@@ -656,7 +656,7 @@ automatically begin with your name.
-
-aliases = ['emote', ':']
+aliases = [':', 'emote']
@@ -697,7 +697,7 @@ space.
-
-search_index_entry = {'aliases': 'emote :', 'category': 'general', 'key': 'pose', 'no_prefix': ' emote :', 'tags': '', 'text': "\nstrike a pose\n\nUsage:\n pose <pose text>\n pose's <pose text>\n\nExample:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\nDescribe an action being taken. The pose text will\nautomatically begin with your name.\n"}
+search_index_entry = {'aliases': ': emote', 'category': 'general', 'key': 'pose', 'no_prefix': ' : emote', 'tags': '', 'text': "\nstrike a pose\n\nUsage:\n pose <pose text>\n pose's <pose text>\n\nExample:\n pose is standing by the wall, smiling.\n -> others will see:\n Tom is standing by the wall, smiling.\n\nDescribe an action being taken. The pose text will\nautomatically begin with your name.\n"}
@@ -720,7 +720,7 @@ which permission groups you are a member of.
-
-aliases = ['groups', 'hierarchy']
+aliases = ['hierarchy', 'groups']
@@ -751,7 +751,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': '\nshow your current game access\n\nUsage:\n access\n\nThis command shows you the permission hierarchy and\nwhich permission groups you are a member of.\n'}
+search_index_entry = {'aliases': 'hierarchy groups', 'category': 'general', 'key': 'access', 'no_prefix': ' hierarchy groups', 'tags': '', 'text': '\nshow your current game access\n\nUsage:\n access\n\nThis command shows you the permission hierarchy and\nwhich permission groups you are a member of.\n'}
diff --git a/docs/latest/api/evennia.commands.default.html b/docs/latest/api/evennia.commands.default.html
index d8ac1c1e44..1b2360e6c2 100644
--- a/docs/latest/api/evennia.commands.default.html
+++ b/docs/latest/api/evennia.commands.default.html
@@ -1048,6 +1048,7 @@
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -1420,6 +1421,7 @@
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -2393,6 +2395,10 @@
TestSystemCommands.test_multimatch()
+TestPreCmdOutputTestable
+
evennia.commands.default.unloggedin
diff --git a/docs/latest/api/evennia.commands.default.system.html b/docs/latest/api/evennia.commands.default.system.html
index 02a5aff6bd..85dbe9bc21 100644
--- a/docs/latest/api/evennia.commands.default.system.html
+++ b/docs/latest/api/evennia.commands.default.system.html
@@ -631,7 +631,7 @@ See |luhttps://ww
-
-aliases = ['@task', '@delays']
+aliases = ['@delays', '@task']
@@ -679,7 +679,7 @@ to all the variables defined therein.
-
-search_index_entry = {'aliases': '@task @delays', 'category': 'system', 'key': '@tasks', 'no_prefix': 'tasks task delays', 'tags': '', 'text': "\nDisplay or terminate active tasks (delays).\n\nUsage:\n tasks[/switch] [task_id or function_name]\n\nSwitches:\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\nNotes:\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\nExamples:\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': '@delays @task', 'category': 'system', 'key': '@tasks', 'no_prefix': 'tasks delays task', 'tags': '', 'text': "\nDisplay or terminate active tasks (delays).\n\nUsage:\n tasks[/switch] [task_id or function_name]\n\nSwitches:\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\nNotes:\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\nExamples:\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/latest/api/evennia.commands.default.tests.html b/docs/latest/api/evennia.commands.default.tests.html
index 1d33e82277..7ca4f7e835 100644
--- a/docs/latest/api/evennia.commands.default.tests.html
+++ b/docs/latest/api/evennia.commands.default.tests.html
@@ -1087,6 +1087,17 @@ set in self.parse())
+
+-
+class evennia.commands.default.tests.TestPreCmdOutputTestable(methodName='runTest')[source]
+Bases: BaseEvenniaCommandTest
+
+-
+test_pre_cmd()[source]
+
+
+
+
@@ -1324,6 +1335,10 @@ set in self.parse())
TestSystemCommands.test_multimatch()
+TestPreCmdOutputTestable
+
diff --git a/docs/latest/api/evennia.commands.default.unloggedin.html b/docs/latest/api/evennia.commands.default.unloggedin.html
index 87a85d248d..385205f391 100644
--- a/docs/latest/api/evennia.commands.default.unloggedin.html
+++ b/docs/latest/api/evennia.commands.default.unloggedin.html
@@ -70,7 +70,7 @@ connect “account name” “pass word”
-
-aliases = ['conn', 'co', 'con']
+aliases = ['con', 'co', 'conn']
@@ -105,7 +105,7 @@ there is no object yet before the account has logged in)
-
-search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'no_prefix': ' conn co con', 'tags': '', 'text': '\nconnect to the game\n\nUsage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\nUse the create command to first create an account before logging in.\n\nIf you have spaces in your name, enclose it in double quotes.\n'}
+search_index_entry = {'aliases': 'con co conn', 'category': 'general', 'key': 'connect', 'no_prefix': ' con co conn', 'tags': '', 'text': '\nconnect to the game\n\nUsage (at login screen):\n connect accountname password\n connect "account name" "pass word"\n\nUse the create command to first create an account before logging in.\n\nIf you have spaces in your name, enclose it in double quotes.\n'}
@@ -129,7 +129,7 @@ create “account name” “pass word”
-
-aliases = ['cr', 'cre']
+aliases = ['cre', 'cr']
@@ -166,7 +166,7 @@ create “account name” “pass word”
-
-search_index_entry = {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'no_prefix': ' cr cre', 'tags': '', 'text': '\ncreate a new account account\n\nUsage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\nThis creates a new account account.\n\nIf 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': '\ncreate a new account account\n\nUsage (at login screen):\n create <accountname> <password>\n create "account name" "pass word"\n\nThis creates a new account account.\n\nIf you have spaces in your name, enclose it in double quotes.\n'}
@@ -190,7 +190,7 @@ version is a bit more complicated.
-
-aliases = ['q', 'qu']
+aliases = ['qu', 'q']
@@ -216,7 +216,7 @@ version is a bit more complicated.
-
-search_index_entry = {'aliases': 'q qu', 'category': 'general', 'key': 'quit', 'no_prefix': ' q qu', 'tags': '', 'text': '\nquit when in unlogged-in state\n\nUsage:\n quit\n\nWe maintain a different version of the quit command\nhere for unconnected accounts for the sake of simplicity. The logged in\nversion is a bit more complicated.\n'}
+search_index_entry = {'aliases': 'qu q', 'category': 'general', 'key': 'quit', 'no_prefix': ' qu q', 'tags': '', 'text': '\nquit when in unlogged-in state\n\nUsage:\n quit\n\nWe maintain a different version of the quit command\nhere for unconnected accounts for the sake of simplicity. The logged in\nversion is a bit more complicated.\n'}
@@ -289,7 +289,7 @@ for simplicity. It shows a pane of info.
-
-aliases = ['?', 'h']
+aliases = ['h', '?']
@@ -315,7 +315,7 @@ for simplicity. It shows a pane of info.
-
-search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'no_prefix': ' ? h', 'tags': '', 'text': '\nget help when in unconnected-in state\n\nUsage:\n help\n\nThis is an unconnected version of the help command,\nfor simplicity. It shows a pane of info.\n'}
+search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'no_prefix': ' h ?', 'tags': '', 'text': '\nget help when in unconnected-in state\n\nUsage:\n help\n\nThis is an unconnected version of the help command,\nfor simplicity. It shows a pane of info.\n'}
diff --git a/docs/latest/api/evennia.commands.html b/docs/latest/api/evennia.commands.html
index 7251802f92..c5a65e95a9 100644
--- a/docs/latest/api/evennia.commands.html
+++ b/docs/latest/api/evennia.commands.html
@@ -91,21 +91,6 @@ Evennia.
NoCmdSets
-WeakValueDictionary
-
chain
@@ -1284,6 +1269,7 @@ Evennia.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -1656,6 +1642,7 @@ Evennia.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -2629,6 +2616,10 @@ Evennia.
TestSystemCommands.test_multimatch()
+TestPreCmdOutputTestable
+
evennia.commands.default.unloggedin
diff --git a/docs/latest/api/evennia.contrib.base_systems.email_login.email_login.html b/docs/latest/api/evennia.contrib.base_systems.email_login.email_login.html
index 51c4d63600..245b1f4a0e 100644
--- a/docs/latest/api/evennia.contrib.base_systems.email_login.email_login.html
+++ b/docs/latest/api/evennia.contrib.base_systems.email_login.email_login.html
@@ -87,7 +87,7 @@ the module given by settings.CONNECTION_SCREEN_MODULE.
-
-aliases = ['conn', 'co', 'con']
+aliases = ['con', 'co', 'conn']
@@ -117,7 +117,7 @@ there is no object yet before the account has logged in)
-
-search_index_entry = {'aliases': 'conn co con', 'category': 'general', 'key': 'connect', 'no_prefix': ' conn co con', 'tags': '', 'text': '\nConnect to the game.\n\nUsage (at login screen):\n connect <email> <password>\n\nUse the create command to first create an account before logging in.\n'}
+search_index_entry = {'aliases': 'con co conn', 'category': 'general', 'key': 'connect', 'no_prefix': ' con co conn', 'tags': '', 'text': '\nConnect to the game.\n\nUsage (at login screen):\n connect <email> <password>\n\nUse the create command to first create an account before logging in.\n'}
@@ -139,7 +139,7 @@ there is no object yet before the account has logged in)
-
-aliases = ['cr', 'cre']
+aliases = ['cre', 'cr']
@@ -181,7 +181,7 @@ name enclosed in quotes:
-
-search_index_entry = {'aliases': 'cr cre', 'category': 'general', 'key': 'create', 'no_prefix': ' cr cre', 'tags': '', 'text': '\nCreate a new account.\n\nUsage (at login screen):\n create "accountname" <email> <password>\n\nThis creates a new account account.\n\n'}
+search_index_entry = {'aliases': 'cre cr', 'category': 'general', 'key': 'create', 'no_prefix': ' cre cr', 'tags': '', 'text': '\nCreate a new account.\n\nUsage (at login screen):\n create "accountname" <email> <password>\n\nThis creates a new account account.\n\n'}
@@ -200,7 +200,7 @@ version is a bit more complicated.
-
-aliases = ['q', 'qu']
+aliases = ['qu', 'q']
@@ -226,7 +226,7 @@ version is a bit more complicated.
-
-search_index_entry = {'aliases': 'q qu', 'category': 'general', 'key': 'quit', 'no_prefix': ' q qu', 'tags': '', 'text': '\nWe maintain a different version of the `quit` command\nhere for unconnected accounts for the sake of simplicity. The logged in\nversion is a bit more complicated.\n'}
+search_index_entry = {'aliases': 'qu q', 'category': 'general', 'key': 'quit', 'no_prefix': ' qu q', 'tags': '', 'text': '\nWe maintain a different version of the `quit` command\nhere for unconnected accounts for the sake of simplicity. The logged in\nversion is a bit more complicated.\n'}
@@ -289,7 +289,7 @@ for simplicity. It shows a pane of info.
-
-aliases = ['?', 'h']
+aliases = ['h', '?']
@@ -315,7 +315,7 @@ for simplicity. It shows a pane of info.
-
-search_index_entry = {'aliases': '? h', 'category': 'general', 'key': 'help', 'no_prefix': ' ? h', 'tags': '', 'text': '\nThis is an unconnected version of the help command,\nfor simplicity. It shows a pane of info.\n'}
+search_index_entry = {'aliases': 'h ?', 'category': 'general', 'key': 'help', 'no_prefix': ' h ?', 'tags': '', 'text': '\nThis is an unconnected version of the help command,\nfor simplicity. It shows a pane of info.\n'}
diff --git a/docs/latest/api/evennia.contrib.base_systems.ingame_python.commands.html b/docs/latest/api/evennia.contrib.base_systems.ingame_python.commands.html
index f6d13c6952..c43ab8f47f 100644
--- a/docs/latest/api/evennia.contrib.base_systems.ingame_python.commands.html
+++ b/docs/latest/api/evennia.contrib.base_systems.ingame_python.commands.html
@@ -64,7 +64,7 @@
-
-aliases = ['@callback', '@callbacks', '@calls']
+aliases = ['@calls', '@callbacks', '@callback']
@@ -145,7 +145,7 @@ on user permission.
-
-search_index_entry = {'aliases': '@callback @callbacks @calls', 'category': 'building', 'key': '@call', 'no_prefix': 'call callback callbacks calls', 'tags': '', 'text': '\nCommand to edit callbacks.\n'}
+search_index_entry = {'aliases': '@calls @callbacks @callback', 'category': 'building', 'key': '@call', 'no_prefix': 'call calls callbacks callback', 'tags': '', 'text': '\nCommand to edit callbacks.\n'}
diff --git a/docs/latest/api/evennia.contrib.base_systems.ingame_reports.reports.html b/docs/latest/api/evennia.contrib.base_systems.ingame_reports.reports.html
index d7c2d604ef..8b0289a7d5 100644
--- a/docs/latest/api/evennia.contrib.base_systems.ingame_reports.reports.html
+++ b/docs/latest/api/evennia.contrib.base_systems.ingame_reports.reports.html
@@ -100,7 +100,7 @@ players
-
-aliases = ['manage players', 'manage ideas', 'manage bugs']
+aliases = ['manage bugs', 'manage players', 'manage ideas']
@@ -136,7 +136,7 @@ to all the variables defined therein.
-
-search_index_entry = {'aliases': 'manage players manage ideas manage bugs', 'category': 'general', 'key': 'manage reports', 'no_prefix': ' manage players manage ideas manage bugs', 'tags': '', 'text': '\nmanage the various reports\n\nUsage:\n manage [report type]\n\nAvailable report types:\n bugs\n ideas\n players\n\nInitializes a menu for reviewing and changing the status of current reports.\n'}
+search_index_entry = {'aliases': 'manage bugs manage players manage ideas', 'category': 'general', 'key': 'manage reports', 'no_prefix': ' manage bugs manage players manage ideas', 'tags': '', 'text': '\nmanage the various reports\n\nUsage:\n manage [report type]\n\nAvailable report types:\n bugs\n ideas\n players\n\nInitializes a menu for reviewing and changing the status of current reports.\n'}
diff --git a/docs/latest/api/evennia.contrib.base_systems.mux_comms_cmds.mux_comms_cmds.html b/docs/latest/api/evennia.contrib.base_systems.mux_comms_cmds.mux_comms_cmds.html
index 0645ccca65..30c948271d 100644
--- a/docs/latest/api/evennia.contrib.base_systems.mux_comms_cmds.mux_comms_cmds.html
+++ b/docs/latest/api/evennia.contrib.base_systems.mux_comms_cmds.mux_comms_cmds.html
@@ -165,7 +165,7 @@ for that channel.
-
-aliases = ['delaliaschan', 'delchanalias']
+aliases = ['delchanalias', 'delaliaschan']
@@ -196,7 +196,7 @@ for that channel.
-
-search_index_entry = {'aliases': 'delaliaschan delchanalias', 'category': 'comms', 'key': 'delcom', 'no_prefix': ' delaliaschan delchanalias', 'tags': '', 'text': "\nremove a channel alias and/or unsubscribe from channel\n\nUsage:\n delcom <alias or channel>\n delcom/all <channel>\n\nIf the full channel name is given, unsubscribe from the\nchannel. If an alias is given, remove the alias but don't\nunsubscribe. If the 'all' switch is used, remove all aliases\nfor that channel.\n"}
+search_index_entry = {'aliases': 'delchanalias delaliaschan', 'category': 'comms', 'key': 'delcom', 'no_prefix': ' delchanalias delaliaschan', 'tags': '', 'text': "\nremove a channel alias and/or unsubscribe from channel\n\nUsage:\n delcom <alias or channel>\n delcom/all <channel>\n\nIf the full channel name is given, unsubscribe from the\nchannel. If an alias is given, remove the alias but don't\nunsubscribe. If the 'all' switch is used, remove all aliases\nfor that channel.\n"}
diff --git a/docs/latest/api/evennia.contrib.full_systems.evscaperoom.commands.html b/docs/latest/api/evennia.contrib.full_systems.evscaperoom.commands.html
index 5775c6597b..bb9005e9e9 100644
--- a/docs/latest/api/evennia.contrib.full_systems.evscaperoom.commands.html
+++ b/docs/latest/api/evennia.contrib.full_systems.evscaperoom.commands.html
@@ -159,7 +159,7 @@ the operation will be general or on the room.
-
-aliases = ['chicken out', 'abort', 'q', 'quit']
+aliases = ['abort', 'chicken out', 'quit', 'q']
@@ -183,7 +183,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'chicken out abort q quit', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' chicken out abort q quit', 'tags': '', 'text': '\nGive up\n\nUsage:\n give up\n\nAbandons your attempts at escaping and of ever winning the pie-eating contest.\n\n'}
+search_index_entry = {'aliases': 'abort chicken out quit q', 'category': 'evscaperoom', 'key': 'give up', 'no_prefix': ' abort chicken out quit q', 'tags': '', 'text': '\nGive up\n\nUsage:\n give up\n\nAbandons your attempts at escaping and of ever winning the pie-eating contest.\n\n'}
@@ -204,7 +204,7 @@ set in self.parse())
-
-aliases = ['l', 'ls']
+aliases = ['ls', 'l']
@@ -238,7 +238,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'l ls', 'category': 'evscaperoom', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\nLook at the room, an object or the currently focused object\n\nUsage:\n look [obj]\n\n'}
+search_index_entry = {'aliases': 'ls l', 'category': 'evscaperoom', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\nLook at the room, an object or the currently focused object\n\nUsage:\n look [obj]\n\n'}
@@ -319,7 +319,7 @@ shout
-
-aliases = ['whisper', ';', 'shout']
+aliases = ['shout', 'whisper', ';']
@@ -348,7 +348,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'whisper ; shout', 'category': 'general', 'key': 'say', 'no_prefix': ' whisper ; shout', 'tags': '', 'text': '\nPerform an communication action.\n\nUsage:\n say <text>\n whisper\n shout\n\n'}
+search_index_entry = {'aliases': 'shout whisper ;', 'category': 'general', 'key': 'say', 'no_prefix': ' shout whisper ;', 'tags': '', 'text': '\nPerform an communication action.\n\nUsage:\n say <text>\n whisper\n shout\n\n'}
@@ -376,7 +376,7 @@ emote /me points to /box and /lever.
-
-aliases = ['pose', ':']
+aliases = [':', 'pose']
@@ -415,7 +415,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'pose :', 'category': 'general', 'key': 'emote', 'no_prefix': ' pose :', 'tags': '', 'text': '\nPerform a free-form emote. Use /me to\ninclude yourself in the emote and /name\nto include other objects or characters.\nUse "..." to enact speech.\n\nUsage:\n emote <emote>\n :<emote\n\nExample:\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': '\nPerform a free-form emote. Use /me to\ninclude yourself in the emote and /name\nto include other objects or characters.\nUse "..." to enact speech.\n\nUsage:\n emote <emote>\n :<emote\n\nExample:\n emote /me smiles at /peter\n emote /me points to /box and /lever.\n\n'}
@@ -438,7 +438,7 @@ looks and what actions is available.
-
-aliases = ['examine', 'e', 'ex', 'unfocus']
+aliases = ['examine', 'unfocus', 'ex', 'e']
@@ -467,7 +467,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'examine e ex unfocus', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' examine e ex unfocus', 'tags': '', 'text': '\nFocus your attention on a target.\n\nUsage:\n focus <obj>\n\nOnce focusing on an object, use look to get more information about how it\nlooks and what actions is available.\n\n'}
+search_index_entry = {'aliases': 'examine unfocus ex e', 'category': 'evscaperoom', 'key': 'focus', 'no_prefix': ' examine unfocus ex e', 'tags': '', 'text': '\nFocus your attention on a target.\n\nUsage:\n focus <obj>\n\nOnce focusing on an object, use look to get more information about how it\nlooks and what actions is available.\n\n'}
@@ -529,7 +529,7 @@ set in self.parse())
-
-aliases = ['inv', 'give', 'i', 'inventory']
+aliases = ['inventory', 'give', 'i', 'inv']
@@ -553,7 +553,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'inv give i inventory', 'category': 'evscaperoom', 'key': 'get', 'no_prefix': ' inv give i inventory', 'tags': '', 'text': '\nUse focus / examine instead.\n\n'}
+search_index_entry = {'aliases': 'inventory give i inv', 'category': 'evscaperoom', 'key': 'get', 'no_prefix': ' inventory give i inv', 'tags': '', 'text': '\nUse focus / examine instead.\n\n'}
@@ -574,7 +574,7 @@ set in self.parse())
-
-aliases = ['@dig', '@open']
+aliases = ['@open', '@dig']
@@ -599,7 +599,7 @@ to all the variables defined therein.
-
-search_index_entry = {'aliases': '@dig @open', 'category': 'general', 'key': 'open', 'no_prefix': ' dig open', 'tags': '', 'text': '\nInteract with an object in focus.\n\nUsage:\n <action> [arg]\n\n'}
+search_index_entry = {'aliases': '@open @dig', 'category': 'general', 'key': 'open', 'no_prefix': ' open dig', 'tags': '', 'text': '\nInteract with an object in focus.\n\nUsage:\n <action> [arg]\n\n'}
diff --git a/docs/latest/api/evennia.contrib.game_systems.achievements.achievements.html b/docs/latest/api/evennia.contrib.game_systems.achievements.achievements.html
index 506a913771..8dcc4b526e 100644
--- a/docs/latest/api/evennia.contrib.game_systems.achievements.achievements.html
+++ b/docs/latest/api/evennia.contrib.game_systems.achievements.achievements.html
@@ -223,7 +223,7 @@ achievements/progress rats
-
-aliases = ['achieve', 'achieves', 'achievement']
+aliases = ['achievement', 'achieves', 'achieve']
@@ -273,7 +273,7 @@ to all the variables defined therein.
-
-search_index_entry = {'aliases': 'achieve achieves achievement', 'category': 'general', 'key': 'achievements', 'no_prefix': ' achieve achieves achievement', 'tags': '', 'text': '\nview achievements\n\nUsage:\n achievements[/switches] [args]\n\nSwitches:\n all View all achievements, including locked ones.\n completed View achievements you\'ve completed.\n progress View achievements you have partially completed\n\nCheck your achievement statuses or browse the list. Providing a command argument\nwill search all your currently unlocked achievements for matches, and the switches\nwill filter the list to something other than "all unlocked". Combining a command\nargument with a switch will search only in that list.\n\nExamples:\n achievements apples\n achievements/all\n achievements/progress rats\n'}
+search_index_entry = {'aliases': 'achievement achieves achieve', 'category': 'general', 'key': 'achievements', 'no_prefix': ' achievement achieves achieve', 'tags': '', 'text': '\nview achievements\n\nUsage:\n achievements[/switches] [args]\n\nSwitches:\n all View all achievements, including locked ones.\n completed View achievements you\'ve completed.\n progress View achievements you have partially completed\n\nCheck your achievement statuses or browse the list. Providing a command argument\nwill search all your currently unlocked achievements for matches, and the switches\nwill filter the list to something other than "all unlocked". Combining a command\nargument with a switch will search only in that list.\n\nExamples:\n achievements apples\n achievements/all\n achievements/progress rats\n'}
diff --git a/docs/latest/api/evennia.contrib.grid.extended_room.extended_room.html b/docs/latest/api/evennia.contrib.grid.extended_room.extended_room.html
index 13f80c266f..c676230cee 100644
--- a/docs/latest/api/evennia.contrib.grid.extended_room.extended_room.html
+++ b/docs/latest/api/evennia.contrib.grid.extended_room.extended_room.html
@@ -591,7 +591,7 @@ look *<account&g
-
-aliases = ['l', 'ls']
+aliases = ['ls', 'l']
@@ -611,7 +611,7 @@ look *<account&g
-
-search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\nlook\n\nUsage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\nObserves your location, details at your location or objects in your vicinity.\n'}
+search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\nlook\n\nUsage:\n look\n look <obj>\n look <room detail>\n look *<account>\n\nObserves your location, details at your location or objects in your vicinity.\n'}
diff --git a/docs/latest/api/evennia.contrib.grid.xyzgrid.commands.html b/docs/latest/api/evennia.contrib.grid.xyzgrid.commands.html
index e8ac9be00b..939df047bb 100644
--- a/docs/latest/api/evennia.contrib.grid.xyzgrid.commands.html
+++ b/docs/latest/api/evennia.contrib.grid.xyzgrid.commands.html
@@ -370,7 +370,7 @@ there is no room above/below you, your movement will fail.
-
-aliases = ['dive', 'fly']
+aliases = ['fly', 'dive']
@@ -395,7 +395,7 @@ to all the variables defined therein.
-
-search_index_entry = {'aliases': 'dive fly', 'category': 'general', 'key': 'fly or dive', 'no_prefix': ' dive fly', 'tags': '', 'text': '\nFly or Dive up and down.\n\nUsage:\n fly\n dive\n\nWill fly up one room or dive down one room at your current position. If\nthere is no room above/below you, your movement will fail.\n\n'}
+search_index_entry = {'aliases': 'fly dive', 'category': 'general', 'key': 'fly or dive', 'no_prefix': ' fly dive', 'tags': '', 'text': '\nFly or Dive up and down.\n\nUsage:\n fly\n dive\n\nWill fly up one room or dive down one room at your current position. If\nthere is no room above/below you, your movement will fail.\n\n'}
diff --git a/docs/latest/api/evennia.contrib.rpg.rpsystem.rpsystem.html b/docs/latest/api/evennia.contrib.rpg.rpsystem.rpsystem.html
index 194c45cd52..d2c08aca49 100644
--- a/docs/latest/api/evennia.contrib.rpg.rpsystem.rpsystem.html
+++ b/docs/latest/api/evennia.contrib.rpg.rpsystem.rpsystem.html
@@ -670,7 +670,7 @@ commands the caller can use.
-
-aliases = ['"', "'"]
+aliases = ["'", '"']
@@ -701,7 +701,7 @@ commands the caller can use.
-
-search_index_entry = {'aliases': '" \'', 'category': 'general', 'key': 'say', 'no_prefix': ' " \'', 'tags': '', 'text': '\nspeak as your character\n\nUsage:\n say <message>\n\nTalk to those in your current location.\n'}
+search_index_entry = {'aliases': '\' "', 'category': 'general', 'key': 'say', 'no_prefix': ' \' "', 'tags': '', 'text': '\nspeak as your character\n\nUsage:\n say <message>\n\nTalk to those in your current location.\n'}
diff --git a/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_turnbased.html b/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_turnbased.html
index 0837acef5a..797590cde2 100644
--- a/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_turnbased.html
+++ b/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_turnbased.html
@@ -414,7 +414,7 @@ turn of combat, performing everyone’s actions in random order.
-
-aliases = ['hit', 'turnbased combat']
+aliases = ['turnbased combat', 'hit']
@@ -460,7 +460,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'hit turnbased combat', 'category': 'general', 'key': 'attack', 'no_prefix': ' hit turnbased combat', 'tags': '', 'text': '\nStart or join combat.\n\nUsage:\n attack [<target>]\n\n'}
+search_index_entry = {'aliases': 'turnbased combat hit', 'category': 'general', 'key': 'attack', 'no_prefix': ' turnbased combat hit', 'tags': '', 'text': '\nStart or join combat.\n\nUsage:\n attack [<target>]\n\n'}
diff --git a/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_twitch.html b/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_twitch.html
index ce16380a8e..96d3858755 100644
--- a/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_twitch.html
+++ b/docs/latest/api/evennia.contrib.tutorials.evadventure.combat_twitch.html
@@ -329,7 +329,7 @@ look *<account&g
-
-aliases = ['l', 'ls']
+aliases = ['ls', 'l']
@@ -349,7 +349,7 @@ look *<account&g
-
-search_index_entry = {'aliases': 'l ls', 'category': 'general', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\nlook at location or object\n\nUsage:\n look\n look <obj>\n look *<account>\n\nObserves your location or objects in your vicinity.\n'}
+search_index_entry = {'aliases': 'ls l', 'category': 'general', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\nlook at location or object\n\nUsage:\n look\n look <obj>\n look *<account>\n\nObserves your location or objects in your vicinity.\n'}
@@ -425,7 +425,7 @@ boost INT Wizard Goblin
-
-aliases = ['boost', 'foil']
+aliases = ['foil', 'boost']
@@ -459,7 +459,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'boost foil', 'category': 'combat', 'key': 'stunt', 'no_prefix': ' boost foil', 'tags': '', 'text': '\nPerform a combat stunt, that boosts an ally against a target, or\nfoils an enemy, giving them disadvantage against an ally.\n\nUsage:\n boost [ability] <recipient> <target>\n foil [ability] <recipient> <target>\n boost [ability] <target> (same as boost me <target>)\n foil [ability] <target> (same as foil <target> me)\n\nExample:\n boost STR me Goblin\n boost DEX Goblin\n foil STR Goblin me\n foil INT Goblin\n boost INT Wizard Goblin\n\n'}
+search_index_entry = {'aliases': 'foil boost', 'category': 'combat', 'key': 'stunt', 'no_prefix': ' foil boost', 'tags': '', 'text': '\nPerform a combat stunt, that boosts an ally against a target, or\nfoils an enemy, giving them disadvantage against an ally.\n\nUsage:\n boost [ability] <recipient> <target>\n foil [ability] <recipient> <target>\n boost [ability] <target> (same as boost me <target>)\n foil [ability] <target> (same as foil <target> me)\n\nExample:\n boost STR me Goblin\n boost DEX Goblin\n foil STR Goblin me\n foil INT Goblin\n boost INT Wizard Goblin\n\n'}
diff --git a/docs/latest/api/evennia.contrib.tutorials.evadventure.commands.html b/docs/latest/api/evennia.contrib.tutorials.evadventure.commands.html
index f32086942c..28a2cd87a2 100644
--- a/docs/latest/api/evennia.contrib.tutorials.evadventure.commands.html
+++ b/docs/latest/api/evennia.contrib.tutorials.evadventure.commands.html
@@ -241,7 +241,7 @@ unwear <item>
-
-aliases = ['unwield', 'unwear']
+aliases = ['unwear', 'unwield']
@@ -265,7 +265,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'unwield unwear', 'category': 'general', 'key': 'remove', 'no_prefix': ' unwield unwear', 'tags': '', 'text': '\nRemove a remove a weapon/shield, armor or helmet.\n\nUsage:\n remove <item>\n unwield <item>\n unwear <item>\n\nTo remove an item from the backpack, use |wdrop|n instead.\n\n'}
+search_index_entry = {'aliases': 'unwear unwield', 'category': 'general', 'key': 'remove', 'no_prefix': ' unwear unwield', 'tags': '', 'text': '\nRemove a remove a weapon/shield, armor or helmet.\n\nUsage:\n remove <item>\n unwield <item>\n unwear <item>\n\nTo remove an item from the backpack, use |wdrop|n instead.\n\n'}
diff --git a/docs/latest/api/evennia.contrib.tutorials.red_button.red_button.html b/docs/latest/api/evennia.contrib.tutorials.red_button.red_button.html
index e0e1cf4737..22215ea757 100644
--- a/docs/latest/api/evennia.contrib.tutorials.red_button.red_button.html
+++ b/docs/latest/api/evennia.contrib.tutorials.red_button.red_button.html
@@ -94,7 +94,7 @@ such as when closing the lid and un-blinding a character.
-
-aliases = ['press', 'press button', 'push']
+aliases = ['press button', 'press', 'push']
@@ -123,7 +123,7 @@ check if the lid is open or closed.
-
-search_index_entry = {'aliases': 'press press button push', 'category': 'general', 'key': 'push button', 'no_prefix': ' press press button push', 'tags': '', 'text': '\nPush the red button (lid closed)\n\nUsage:\n push button\n\n'}
+search_index_entry = {'aliases': 'press button press push', 'category': 'general', 'key': 'push button', 'no_prefix': ' press button press push', 'tags': '', 'text': '\nPush the red button (lid closed)\n\nUsage:\n push button\n\n'}
@@ -193,7 +193,7 @@ check if the lid is open or closed.
-
-aliases = ['smash', 'smash lid', 'break lid']
+aliases = ['smash lid', 'smash', 'break lid']
@@ -220,7 +220,7 @@ break.
-
-search_index_entry = {'aliases': 'smash smash lid break lid', 'category': 'general', 'key': 'smash glass', 'no_prefix': ' smash smash lid break lid', 'tags': '', 'text': '\nSmash the protective glass.\n\nUsage:\n smash glass\n\nTry to smash the glass of the button.\n\n'}
+search_index_entry = {'aliases': 'smash lid smash break lid', 'category': 'general', 'key': 'smash glass', 'no_prefix': ' smash lid smash break lid', 'tags': '', 'text': '\nSmash the protective glass.\n\nUsage:\n smash glass\n\nTry to smash the glass of the button.\n\n'}
@@ -320,7 +320,7 @@ be mutually exclusive.
-
-aliases = ['press', 'press button', 'push']
+aliases = ['press button', 'press', 'push']
@@ -349,7 +349,7 @@ set in self.parse())
-
-search_index_entry = {'aliases': 'press press button push', 'category': 'general', 'key': 'push button', 'no_prefix': ' press press button push', 'tags': '', 'text': '\nPush the red button\n\nUsage:\n push button\n\n'}
+search_index_entry = {'aliases': 'press button press push', 'category': 'general', 'key': 'push button', 'no_prefix': ' press button press push', 'tags': '', 'text': '\nPush the red button\n\nUsage:\n push button\n\n'}
@@ -447,7 +447,7 @@ be mutually exclusive.
-
-aliases = ['listen', 'ex', 'l', 'examine', 'feel', 'get']
+aliases = ['get', 'listen', 'examine', 'l', 'ex', 'feel']
@@ -473,7 +473,7 @@ be mutually exclusive.
-
-search_index_entry = {'aliases': 'listen ex l examine feel get', 'category': 'general', 'key': 'look', 'no_prefix': ' listen ex l examine feel get', 'tags': '', 'text': "\nLooking around in darkness\n\nUsage:\n look <obj>\n\n... not that there's much to see in the dark.\n\n"}
+search_index_entry = {'aliases': 'get listen examine l ex feel', 'category': 'general', 'key': 'look', 'no_prefix': ' get listen examine l ex feel', 'tags': '', 'text': "\nLooking around in darkness\n\nUsage:\n look <obj>\n\n... not that there's much to see in the dark.\n\n"}
diff --git a/docs/latest/api/evennia.contrib.tutorials.tutorial_world.objects.html b/docs/latest/api/evennia.contrib.tutorials.tutorial_world.objects.html
index 9aefab8ec6..4d39e48372 100644
--- a/docs/latest/api/evennia.contrib.tutorials.tutorial_world.objects.html
+++ b/docs/latest/api/evennia.contrib.tutorials.tutorial_world.objects.html
@@ -504,7 +504,7 @@ shift green root up/down
-
-aliases = ['shiftroot', 'push', 'pull', 'move']
+aliases = ['pull', 'move', 'push', 'shiftroot']
@@ -540,7 +540,7 @@ yellow/green - horizontal roots
-
-search_index_entry = {'aliases': 'shiftroot push pull move', 'category': 'tutorialworld', 'key': 'shift', 'no_prefix': ' shiftroot push pull move', 'tags': '', 'text': '\nShifts roots around.\n\nUsage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n'}
+search_index_entry = {'aliases': 'pull move push shiftroot', 'category': 'tutorialworld', 'key': 'shift', 'no_prefix': ' pull move push shiftroot', 'tags': '', 'text': '\nShifts roots around.\n\nUsage:\n shift blue root left/right\n shift red root left/right\n shift yellow root up/down\n shift green root up/down\n\n'}
@@ -557,7 +557,7 @@ yellow/green - horizontal roots
-
-aliases = ['push button', 'button', 'press button']
+aliases = ['push button', 'press button', 'button']
@@ -583,7 +583,7 @@ yellow/green - horizontal roots
-
-search_index_entry = {'aliases': 'push button button press button', 'category': 'tutorialworld', 'key': 'press', 'no_prefix': ' push button button press button', 'tags': '', 'text': '\nPresses a button.\n'}
+search_index_entry = {'aliases': 'push button press button button', 'category': 'tutorialworld', 'key': 'press', 'no_prefix': ' push button press button button', 'tags': '', 'text': '\nPresses a button.\n'}
@@ -727,7 +727,7 @@ parry - forgoes your attack but will make you harder to hit on next
-
-aliases = ['parry', 'defend', 'pierce', 'chop', 'slash', 'stab', 'bash', 'hit', 'kill', 'fight', 'thrust']
+aliases = ['stab', 'parry', 'bash', 'thrust', 'hit', 'slash', 'kill', 'fight', 'chop', 'pierce', 'defend']
@@ -753,7 +753,7 @@ parry - forgoes your attack but will make you harder to hit on next
-
-search_index_entry = {'aliases': 'parry defend pierce chop slash stab bash hit kill fight thrust', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' parry defend pierce chop slash stab bash hit kill fight thrust', 'tags': '', 'text': '\nAttack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\nstab - (thrust) makes a lot of damage but is harder to hit with.\nslash - is easier to land, but does not make as much damage.\nparry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n'}
+search_index_entry = {'aliases': 'stab parry bash thrust hit slash kill fight chop pierce defend', 'category': 'tutorialworld', 'key': 'attack', 'no_prefix': ' stab parry bash thrust hit slash kill fight chop pierce defend', 'tags': '', 'text': '\nAttack the enemy. Commands:\n\n stab <enemy>\n slash <enemy>\n parry\n\nstab - (thrust) makes a lot of damage but is harder to hit with.\nslash - is easier to land, but does not make as much damage.\nparry - forgoes your attack but will make you harder to hit on next\n enemy attack.\n\n'}
diff --git a/docs/latest/api/evennia.contrib.tutorials.tutorial_world.rooms.html b/docs/latest/api/evennia.contrib.tutorials.tutorial_world.rooms.html
index ee61d0e7bf..914215dc02 100644
--- a/docs/latest/api/evennia.contrib.tutorials.tutorial_world.rooms.html
+++ b/docs/latest/api/evennia.contrib.tutorials.tutorial_world.rooms.html
@@ -196,7 +196,7 @@ code except for adding in the details.
-
-aliases = ['l', 'ls']
+aliases = ['ls', 'l']
@@ -211,7 +211,7 @@ code except for adding in the details.
-
-search_index_entry = {'aliases': 'l ls', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' l ls', 'tags': '', 'text': '\nlooks at the room and on details\n\nUsage:\n look <obj>\n look <room detail>\n look *<account>\n\nObserves your location, details at your location or objects\nin your vicinity.\n\nTutorial: This is a child of the default Look command, that also\nallows us to look at "details" in the room. These details are\nthings to examine and offers some extra description without\nactually having to be actual database objects. It uses the\nreturn_detail() hook on TutorialRooms for this.\n'}
+search_index_entry = {'aliases': 'ls l', 'category': 'tutorialworld', 'key': 'look', 'no_prefix': ' ls l', 'tags': '', 'text': '\nlooks at the room and on details\n\nUsage:\n look <obj>\n look <room detail>\n look *<account>\n\nObserves your location, details at your location or objects\nin your vicinity.\n\nTutorial: This is a child of the default Look command, that also\nallows us to look at "details" in the room. These details are\nthings to examine and offers some extra description without\nactually having to be actual database objects. It uses the\nreturn_detail() hook on TutorialRooms for this.\n'}
@@ -766,7 +766,7 @@ if they fall off the bridge.
-
-aliases = ['?', 'h']
+aliases = ['h', '?']
@@ -792,7 +792,7 @@ if they fall off the bridge.
-
-search_index_entry = {'aliases': '? h', 'category': 'tutorial world', 'key': 'help', 'no_prefix': ' ? h', 'tags': '', 'text': '\nOverwritten help command while on the bridge.\n'}
+search_index_entry = {'aliases': 'h ?', 'category': 'tutorial world', 'key': 'help', 'no_prefix': ' h ?', 'tags': '', 'text': '\nOverwritten help command while on the bridge.\n'}
diff --git a/docs/latest/api/evennia.html b/docs/latest/api/evennia.html
index 8970186b5b..e03619c2c8 100644
--- a/docs/latest/api/evennia.html
+++ b/docs/latest/api/evennia.html
@@ -823,21 +823,6 @@ with ‘q’, remove the break line and restart server when finished.
NoCmdSets
-WeakValueDictionary
-
chain
@@ -2012,6 +1997,7 @@ with ‘q’, remove the break line and restart server when finished.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -2384,6 +2370,7 @@ with ‘q’, remove the break line and restart server when finished.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -3357,6 +3344,10 @@ with ‘q’, remove the break line and restart server when finished.
TestSystemCommands.test_multimatch()
+TestPreCmdOutputTestable
+
evennia.commands.default.unloggedin
@@ -11211,6 +11202,7 @@ with ‘q’, remove the break line and restart server when finished.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -11615,6 +11607,7 @@ with ‘q’, remove the break line and restart server when finished.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
@@ -13412,6 +13405,7 @@ with ‘q’, remove the break line and restart server when finished.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.objects.html b/docs/latest/api/evennia.objects.html
index 326c1c75a7..d5708746c2 100644
--- a/docs/latest/api/evennia.objects.html
+++ b/docs/latest/api/evennia.objects.html
@@ -78,6 +78,7 @@ objects inherit from classes in this package.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.objects.manager.html b/docs/latest/api/evennia.objects.manager.html
index 6d623f0396..829a2828b1 100644
--- a/docs/latest/api/evennia.objects.manager.html
+++ b/docs/latest/api/evennia.objects.manager.html
@@ -501,6 +501,11 @@ matches against the expressions.
conditional = True
+
+-
+connectors = (None, 'AND', 'OR', 'XOR')
+
+
-
deconstruct()[source]
@@ -1362,6 +1367,7 @@ array) instead of strings.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.prototypes.spawner.html b/docs/latest/api/evennia.prototypes.spawner.html
index e974708ac1..af82e2330f 100644
--- a/docs/latest/api/evennia.prototypes.spawner.html
+++ b/docs/latest/api/evennia.prototypes.spawner.html
@@ -378,7 +378,8 @@ expected - for example, one usually do not want to remove the object’s locatio
if it’s not set in the prototype. With exact=True, all un-specified properties of the
objects will be removed if they exist. This will lead to a more accurate 1:1 correlation
between the object and the prototype but is usually impractical.
-caller (Object or Account, optional) – This may be used by protfuncs to do permission checks.
+caller (Object or Account, optional) – The object requesting the update. Required when using
+protofuncs that perform searches. For example ($obj, $objlist, $dbref, $search).
protfunc_raise_errors (bool) – Have protfuncs raise explicit errors if malformed/not found.
This is highly recommended.
@@ -460,7 +461,8 @@ dictionary. These will be batched-spawned as one object each.
Keyword Arguments:
-caller (Object or Account, optional) – This may be used by protfuncs to do access checks.
+caller (Object or Account, optional) – The object requesting the update. Required when using
+protofuncs that perform searches. For example ($obj, $objlist, $dbref, $search).
prototype_modules (str or list) – A python-path to a prototype
module, or a list of such paths. These will be used to build
the global protparents dictionary accessible by the input
diff --git a/docs/latest/api/evennia.scripts.html b/docs/latest/api/evennia.scripts.html
index 79f3b4f252..fcb7a86752 100644
--- a/docs/latest/api/evennia.scripts.html
+++ b/docs/latest/api/evennia.scripts.html
@@ -74,6 +74,7 @@ timed effects.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.scripts.manager.html b/docs/latest/api/evennia.scripts.manager.html
index e431fc556e..0d3b860f9d 100644
--- a/docs/latest/api/evennia.scripts.manager.html
+++ b/docs/latest/api/evennia.scripts.manager.html
@@ -285,6 +285,11 @@ matches against the expressions.
conditional = True
+
+-
+connectors = (None, 'AND', 'OR', 'XOR')
+
+
-
deconstruct()[source]
@@ -1097,6 +1102,7 @@ object.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.typeclasses.html b/docs/latest/api/evennia.typeclasses.html
index 58dc67a8ad..93484ca91c 100644
--- a/docs/latest/api/evennia.typeclasses.html
+++ b/docs/latest/api/evennia.typeclasses.html
@@ -307,6 +307,7 @@ Attribute and Tag models are defined along with their handlers.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.typeclasses.managers.html b/docs/latest/api/evennia.typeclasses.managers.html
index b949d4c57a..89aae8b6b7 100644
--- a/docs/latest/api/evennia.typeclasses.managers.html
+++ b/docs/latest/api/evennia.typeclasses.managers.html
@@ -1039,6 +1039,11 @@ matches against the expressions.
conditional = True
+
+-
+connectors = (None, 'AND', 'OR', 'XOR')
+
+
-
deconstruct()[source]
@@ -1704,6 +1709,7 @@ are replaced by the default argument.
Q.__init__()
Q.check()
Q.conditional
+Q.connectors
Q.deconstruct()
Q.default
Q.flatten()
diff --git a/docs/latest/api/evennia.utils.eveditor.html b/docs/latest/api/evennia.utils.eveditor.html
index f3c8866156..dd17ea852d 100644
--- a/docs/latest/api/evennia.utils.eveditor.html
+++ b/docs/latest/api/evennia.utils.eveditor.html
@@ -290,7 +290,7 @@ indentation.
-
-aliases = [':u', ':h', ':fi', ':q!', ':x', ':dd', ':I', ':A', ':uu', ':S', ':>', ':DD', ':::', ':p', ':<', ':=', ':q', ':UU', ':s', ':i', ':y', ':w', '::', ':dw', ':!', ':f', ':', ':j', ':r', ':fd', ':echo', ':wq']
+aliases = [':y', ':u', ':::', ':fd', ':s', ':dw', ':dd', ':A', ':!', ':f', ':uu', ':h', ':r', ':echo', ':q', ':>', ':DD', ':<', ':i', ':S', ':I', ':UU', ':fi', '::', ':j', ':q!', ':p', ':=', ':x', ':', ':wq', ':w']
@@ -318,7 +318,7 @@ efficient presentation.
-
-search_index_entry = {'aliases': ':u :h :fi :q! :x :dd :I :A :uu :S :> :DD ::: :p :< := :q :UU :s :i :y :w :: :dw :! :f : :j :r :fd :echo :wq', 'category': 'general', 'key': ':editor_command_group', 'no_prefix': ' :u :h :fi :q! :x :dd :I :A :uu :S :> :DD ::: :p :< := :q :UU :s :i :y :w :: :dw :! :f : :j :r :fd :echo :wq', 'tags': '', 'text': '\nCommands for the editor\n'}
+search_index_entry = {'aliases': ':y :u ::: :fd :s :dw :dd :A :! :f :uu :h :r :echo :q :> :DD :< :i :S :I :UU :fi :: :j :q! :p := :x : :wq :w', 'category': 'general', 'key': ':editor_command_group', 'no_prefix': ' :y :u ::: :fd :s :dw :dd :A :! :f :uu :h :r :echo :q :> :DD :< :i :S :I :UU :fi :: :j :q! :p := :x : :wq :w', 'tags': '', 'text': '\nCommands for the editor\n'}
diff --git a/docs/latest/api/evennia.utils.evmenu.html b/docs/latest/api/evennia.utils.evmenu.html
index a9ef1ed948..9762745a01 100644
--- a/docs/latest/api/evennia.utils.evmenu.html
+++ b/docs/latest/api/evennia.utils.evmenu.html
@@ -882,7 +882,7 @@ single question.
+aliases = ['__nomatch_command', 'a', 'yes', 'abort', 'no', 'y', 'n']
@@ -908,7 +908,7 @@ single question.
+search_index_entry = {'aliases': '__nomatch_command a yes abort no y n', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' __nomatch_command a yes abort no y n', 'tags': '', 'text': '\nHandle a prompt for yes or no. Press [return] for the default choice.\n\n'}
diff --git a/docs/latest/api/evennia.utils.evmore.html b/docs/latest/api/evennia.utils.evmore.html
index 210dbd6094..16fbf3a28c 100644
--- a/docs/latest/api/evennia.utils.evmore.html
+++ b/docs/latest/api/evennia.utils.evmore.html
@@ -85,7 +85,7 @@ the caller.msg() construct every time the page is updated.
-
-aliases = ['quit', 'previous', 'p', 'top', 'q', 'a', 'end', 'next', 'abort', 'n', 'e', 't']
+aliases = ['top', 'a', 'quit', 'previous', 'abort', 'p', 'e', 'next', 't', 'end', 'n', 'q']
@@ -111,7 +111,7 @@ the caller.msg() construct every time the page is updated.
-
-search_index_entry = {'aliases': 'quit previous p top q a end next abort n e t', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' quit previous p top q a end next abort n e t', 'tags': '', 'text': '\nManipulate the text paging. Catch no-input with aliases.\n'}
+search_index_entry = {'aliases': 'top a quit previous abort p e next t end n q', 'category': 'general', 'key': '__noinput_command', 'no_prefix': ' top a quit previous abort p e next t end n q', 'tags': '', 'text': '\nManipulate the text paging. Catch no-input with aliases.\n'}
diff --git a/docs/latest/genindex.html b/docs/latest/genindex.html
index 22894f68ee..b3ced68a26 100644
--- a/docs/latest/genindex.html
+++ b/docs/latest/genindex.html
@@ -100,8 +100,6 @@
(evennia.commands.cmdhandler.ErrorReported method)
(evennia.commands.cmdhandler.ExecSystemCommand method)
-
- (evennia.commands.cmdhandler.WeakValueDictionary method)
(evennia.commands.cmdset.CmdSet method)
@@ -5140,12 +5138,12 @@
CmdOpenCloseDoor (class in evennia.contrib.grid.simpledoor.simpledoor)
-
- |
+
ConnectionWizard (class in evennia.server.connection_wizard)
+ connectors (evennia.commands.default.building.Q attribute)
+
+
constitution (evennia.contrib.tutorials.evadventure.characters.EvAdventureCharacter attribute)
|
iter_to_string() (in module evennia.utils.utils)
-
- itervaluerefs() (evennia.commands.cmdhandler.WeakValueDictionary method)
|
@@ -17009,11 +17011,9 @@
keys (evennia.contrib.base_systems.building_menu.building_menu.Choice property)
- keys() (evennia.commands.cmdhandler.WeakValueDictionary method)
+ keys() (evennia.commands.cmdset.WeakKeyDictionary method)
@@ -22268,11 +22268,9 @@
pong() (evennia.server.portal.irc.IRCBot method)
- pop() (evennia.commands.cmdhandler.WeakValueDictionary method)
+ pop() (evennia.commands.cmdset.WeakKeyDictionary method)
- popitem() (evennia.commands.cmdhandler.WeakValueDictionary method)
+ popitem() (evennia.commands.cmdset.WeakKeyDictionary method)
- sendmessage() (in module evennia.contrib.utils.fieldfill.fieldfill)
-
|
- |
+
- update_error_dict() (evennia.accounts.accounts.ValidationError method)
- update_flags() (evennia.server.serversession.ServerSession method)
@@ -30455,14 +30451,10 @@
- value_to_obj_or_any() (in module evennia.prototypes.prototypes)
- value_to_string() (evennia.utils.picklefield.PickledObjectField method)
-
- - valuerefs() (evennia.commands.cmdhandler.WeakValueDictionary method)
- values() (evennia.accounts.manager.TypeclassManager method)
- - (evennia.commands.cmdhandler.WeakValueDictionary method)
-
- (evennia.commands.cmdset.WeakKeyDictionary method)
- (evennia.objects.manager.TypeclassManager method)
@@ -30621,8 +30613,6 @@
- WeakSharedMemoryModel.Meta (class in evennia.utils.idmapper.models)
- WeakSharedMemoryModelBase (class in evennia.utils.idmapper.models)
-
- - WeakValueDictionary (class in evennia.commands.cmdhandler)
- weapon (evennia.contrib.tutorials.evadventure.characters.EvAdventureCharacter property)
diff --git a/docs/latest/index.html b/docs/latest/index.html
index af15ece3da..738fc88170 100644
--- a/docs/latest/index.html
+++ b/docs/latest/index.html
@@ -41,7 +41,7 @@
Evennia Documentation
-This is the manual of Evennia, the open source Python MU* creation system. Use the Search bar on the left to find or discover interesting articles. This manual was last updated outubro 26, 2024, see the Evennia Changelog. Latest released Evennia version is 5.0.1.
+This is the manual of Evennia, the open source Python MU* creation system. Use the Search bar on the left to find or discover interesting articles. This manual was last updated January 12, 2026, see the Evennia Changelog. Latest released Evennia version is 5.0.1.
|