diff --git a/docs/Makefile b/docs/Makefile index 6e532b949c..10db7143e3 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -13,9 +13,10 @@ SPHINXAPIDOC ?= sphinx-apidoc SPHINXAPIDOCOPTS = --tocfile evennia-api --module-first --force -d 6 --separate --templatedir=$(SOURCEDIR)/_templates/ SPHINXAPIDOCOPTSQUICK = --tocfile evennia-api --module-first -d 6 --separate --templatedir=$(SOURCEDIR)/_templates/ SPHINXAPIDOCENV = members,undoc-members,show-inheritance -SPHINXAPIDOCEXCLUDE = ../*/migrations/* ../evennia/game_template/* ../evennia/*/tests.py ../evennia/accounts/tests/* ../evennia/commands/tests/* ../evennia/comms/tests/* ../evennia/help/tests/* ../evennia/locks/tests/* ../evennia/objects/tests/* ../evennia/prototypes/tests/* ../evennia/scripts/tests/* ../evennia/server/tests/* ../evennia/typeclasses/tests/* ../evennia/utils/tests/* ../evennia/web/tests/* # don't exclude contrib tests for tutorial purposes +SPHINXAPIDOCEXCLUDE = ../*/migrations/* ../evennia/game_template/* ../evennia/*/tests/* ../evennia/*/tests.py + +# ../evennia/*/tests.py ../evennia/accounts/tests/* ../evennia/commands/tests/* ../evennia/comms/tests/* ../evennia/help/tests/* ../evennia/locks/tests/* ../evennia/objects/tests/* ../evennia/prototypes/tests/* ../evennia/scripts/tests/* ../evennia/server/tests/* ../evennia/typeclasses/tests/* ../evennia/utils/tests/* ../evennia/web/tests/* # don't exclude contrib tests for tutorial purposes -# ../evennia/*/tests/* ../evennia/*/tests.py EVDIR ?= $(realpath ../evennia) EVGAMEDIR ?= $(realpath ../../gamedir) diff --git a/docs/source/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.md b/docs/source/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.md index d6c6b51821..622be9127b 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.md @@ -13,7 +13,6 @@ import evennia roses = evennia.search_object(key="rose") accts = evennia.search_account(key="MyAccountName", email="foo@bar.com") ``` -``` ```{sidebar} Querysets @@ -73,7 +72,7 @@ class CmdQuickFind(Command): result = self.caller.search(query) if not result return - self.caller.msg(f"Found match for {query}: {foo}") + self.caller.msg(f"Found match for {query}: {foo}") ``` Remember, `self.caller` is the one calling the command. This is usually a Character, which diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-AI.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-AI.md new file mode 100644 index 0000000000..53911ed9b8 --- /dev/null +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-AI.md @@ -0,0 +1,5 @@ +# NPC and monster AI + +```{warning} +This part of the Beginner tutorial is still being developed. +``` \ No newline at end of file diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.md index 1e8b765ed5..92d37996c6 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Turnbased.md @@ -350,7 +350,7 @@ We use a Python `set()` to track who has queued an action this turn. If all comb ```{code-block} python :linenos: -:emphasize-lines: 13,17,31 +:emphasize-lines: 13,16,17,22,43,49 # in evadventure/combat_turnbased.py @@ -372,6 +372,18 @@ class EvadventureTurnbasedCombatHandler(EvAdventureCombatBaseHandler): action.execute() action.post_execute() + + if action_dict.get("repeat", False): + # queue the action again *without updating the + # *.ndb.did_action list* (otherwise + # we'd always auto-end the turn if everyone used + # repeating actions and there'd be + # no time to change it before the next round) + self.combatants[combatant] = action_dict + else: + # if not a repeat, set the fallback action + self.combatants[combatant] = self.fallback_action_dict + def at_repeat(self): """ @@ -399,14 +411,15 @@ Our action-execution consists of two parts - the `execute_next_action` (which wa For `execute_next_action` : -- **Line 11**: We get the `action_dict` from the `combatants` Attribute. We return the `fallback_action_dict` if nothing was queued (this defaults to `hold`). -- **Line 14**: We use the `key` of the `action_dict` (which would be something like "attack", "use", "wield" etc) to get the class of the matching Action from the `action_classes` dictionary. -- **Line 15**: Here the action class is instantiated with the combatant and action dict, making it ready to execute. This is then on the following lines. +- **Line 13**: We get the `action_dict` from the `combatants` Attribute. We return the `fallback_action_dict` if nothing was queued (this defaults to `hold`). +- **Line 16**: We use the `key` of the `action_dict` (which would be something like "attack", "use", "wield" etc) to get the class of the matching Action from the `action_classes` dictionary. +- **Line 17**: Here the action class is instantiated with the combatant and action dict, making it ready to execute. This is then executed on the following lines. +- **Line 22**: We introduce a new optional `action-dict` here, the boolean `repeat` key. This allows us to re-queue the action. If not the fallback action will be used. The `at_repeat` is called repeatedly every `interval` seconds that the Script fires. This is what we use to track when each round ends. -- **Lines 28-33**: In this example, we have no internal order between actions. So we simply randomize in which order they fire. -- **Line 35**: We set this `set` in the `queue_action` to know when everyone submitted a new action. We must make sure to unset it here before the next round. +- **Lines 43**: In this example, we have no internal order between actions. So we simply randomize in which order they fire. +- **Line 49**: This `set` was assigned to in the `queue_action` method to know when everyone submitted a new action. We must make sure to unset it here before the next round. ### Check and stop combat @@ -414,8 +427,6 @@ The `at_repeat` is called repeatedly every `interval` seconds that the Script fi :linenos: :emphasize-lines: 28,41,49,60 -from evennia.utils.utils import list_to_string - # in evadventure/combat_turnbased.py import random @@ -527,9 +538,9 @@ The `start(**kwargs)` method is a method on the Script, and will make it start t ## Using EvMenu for the combat menu -The [EvMenu](../../../Components/EvMenu.md) is used to create in-game menues in Evennia. We used a simple EvMenu already in the [Character Generation Lesson](./Beginner-Tutorial-Chargen.md). This time we'll need to be a bit more advanced. While EvMenu is described in detail on its own page, we will give a quick overview of how it works here. +The _EvMenu_ used to create in-game menues in Evennia. We used a simple EvMenu already in the [Character Generation Lesson](./Beginner-Tutorial-Chargen.md). This time we'll need to be a bit more advanced. While [The EvMenu documentation](../../../Components/EvMenu.md) describe its functionality in more detail, we will give a quick overview of how it works here. -An EvMenu is made up of _nodes_, which are regular functions on this form: +An EvMenu is made up of _nodes_, which are regular functions on this form (somewhat simplified here, there are more options): ```python def node_somenodename(caller, raw_string, **kwargs): @@ -545,7 +556,6 @@ def node_somenodename(caller, raw_string, **kwargs): ] return text, options ``` -> There are more possibilities, described in the main [EvMenu docs](../../../Components/EvMenu.md), So basically each node takes the arguments of `caller` (the one using the menu), `raw_string` (the empty string or what the user input on the _previous node_) and `**kwargs` which can be used to pass data from node to node. It returns `text` and `options`. @@ -575,13 +585,14 @@ def _goto_when_choosing_option1(caller, raw_string, **kwargs): return nodename # also nodename, dict works ``` +```{sidebar} Separating node-functions from goto callables +To make node-functions clearly separate from goto-callables, Evennia docs always prefix node-functions with `node_` and menu goto-functions with an underscore `_` (which is also making goto-functions 'private' in Python lingo). +``` Here, `caller` is still the one using the menu and `raw_string` is the actual string you entered to choose this option. `**kwargs` is the keywords you added to the `(callable, {keywords})` tuple. The goto-callable must return the name of the next node. Optionally, you can return both `nodename, {kwargs}`. If you do the next node will get those kwargs as ingoing `**kwargs`. This way you can pass information from one node to the next. A special feature is that if `nodename` is returned as `None`, then the _current_ node will be _rerun_ again. -> To make node functions clearly separate from goto-callables, Evennia docs prefix the first with `node_...` and the latter with `_`. - -Here's an example of how the goto-callable and node-function hang together: +Here's a (somewhat contrived) example of how the goto-callable and node-function hang together: ``` # goto-callable @@ -611,26 +622,26 @@ def node_somenodename(caller, raw_string, **kwargs): ## Menu for Turnbased combat -Our combat menu will be pretty simple. We will have one central menu node with options indicating all the different actions of combat. When choosing an option, the player should be asked a series of question, each specifying one piece of information needed for that action. The last step will be the build this information into an `action-dict` we can queue with the combathandler. +Our combat menu will be pretty simple. We will have one central menu node with options indicating all the different actions of combat. When choosing an action in the menu, the player should be asked a series of question, each specifying one piece of information needed for that action. The last step will be the build this information into an `action-dict` we can queue with the combathandler. -To understand the process, here's how the action selection will work: +To understand the process, here's how the action selection will work (read left to right): -| start node | step 1 | step 2 | step 3 | step 4 | +| In base node | step 1 | step 2 | step 3 | step 4 | | --- | --- | --- | --- | --- | | select `attack` | select `target` | queue action-dict | - | - | -| select `stunt - give advantage` | select `Ability to boost`| select `allied recipient` | select `enemy target` | queue action-dict | -| select `stunt - give disadvantage` | select `Ability to foil` | select `enemy recipient` | select `allied target` | queue action-dict | -| select `use item on yourself or ally` | select `item` to use from inventory | select `allied target` | queue action-dict | - | -| select `use item on enemy` | select `item` to use from inventory | select `enemy target` | queue action-dict | - | -| select `wield/swap item from inventory` | select `item from inventory` | queue action-dict | - | - | +| select `stunt - give advantage` | select `Ability`| select `allied recipient` | select `enemy target` | queue action-dict | +| select `stunt - give disadvantage` | select `Ability` | select `enemy recipient` | select `allied target` | queue action-dict | +| select `use item on yourself or ally` | select `item` from inventory | select `allied target` | queue action-dict | - | +| select `use item on enemy` | select `item` from inventory | select `enemy target` | queue action-dict | - | +| select `wield/swap item from inventory` | select `item` from inventory` | queue action-dict | - | - | | select `flee` | queue action-dict | - | - | - | | select `hold, doing nothing` | queue action-dict | - | - | - | -Looking at the above table we can see that we have a lot of re-use. The selection of allied/enemy/target/recipient/item are represent nodes that are reused between different actions. +Looking at the above table we can see that we have _a lot_ of re-use. The selection of allied/enemy/target/recipient/item represent nodes that can be shared by different actions. Each of these actions also follow a linear sequence, like the step-by step 'wizard' you see in some software. We want to be able to step back and forth in each sequence, and also abort the action if you change your mind along the way. -After queueing the action, we should always go back to the start node where we will wait until the round ends and all actions are executed. +After queueing the action, we should always go back to the base node where we will wait until the round ends and all actions are executed. We will create a few helpers to make our particular menu easy to work with. @@ -677,7 +688,7 @@ We only add this to not have to write as much when calling this later. We pass ` ### Queue an action -This is our first "goto function", the function that when execute when we select the last option in the 'wizard' of each Action. This should queue the action-dict and return us to the `combat_node`. +This is our first "goto function". This will be called to actually queue our finished action-dict with the combat handler. After doing that, it should return us to the base `node_combat`. ```python # in evadventure/combat_turnbased.py @@ -690,7 +701,7 @@ def _queue_action(caller, raw_string, **kwargs): return "node_combat" ``` -We make one assumption here - that `kwargs` contains the `action-dict` key with the action-dict built up by previous steps. We get the combathandler and queues it. +We make one assumption here - that `kwargs` contains the `action_dict` key with the action-dict ready to go. Since this is a goto-callable, we must return the next node to go to. Since this is the last step, we will always go back to the `node_combat` base node, so that's what we return. @@ -729,7 +740,9 @@ options = [ ] ``` -When the user chooses to use an item on an enemy, will call `_step_wizard` with two keywords `steps` and `action_dict`. The first is the names of the menu nodes we will step through for this action. The latter is the `action_dict` that will eventually end up in the [`_queue_action`](#queue-an-action) goto function we defined earlier. +When the user chooses to use an item on an enemy, we will call `_step_wizard` with two keywords `steps` and `action_dict`. The first is the _sequence_ of menu nodes we need to guide the player through in order to build up our action-dict. + +The latter is the `action_dict` itself. Each node will gradually fill in the `None` places in this dict until we have a complete dict and can send it to the [`_queue_action`](#queue-an-action) goto function we defined earlier. Furthermore, we want the ability to go "back" to the previous node like this: @@ -752,7 +765,7 @@ def some_node(caller, raw_string, **kwargs): # ... ``` -Note the use of `**` here - `{**dict1, **dict2}` is a powerful one-liner way to combine two dicts into one. This preserves (and passes on) the incoming `kwargs` and just adds a new key "step" to it. +Note the use of `**` here. `{**dict1, **dict2}` is a powerful one-liner syntax to combine two dicts into one. This preserves (and passes on) the incoming `kwargs` and just adds a new key "step" to it. The end effect is similar to if we had done `kwargs["step"] = "back"` on a separate line (except we end up with a _new_ `dict` when using the `**`-approach). So let's implement a `_step_wizard` goto-function to handle this! @@ -990,7 +1003,7 @@ def node_combat(caller, raw_string, **kwargs): _step_wizard, { "steps": ["node_choose_enemy_target"], - "action_dict": {"key": "attack", "target": None}, + "action_dict": {"key": "attack", "target": None, "repeat": True}, }, ), }, @@ -1054,7 +1067,7 @@ def node_combat(caller, raw_string, **kwargs): }, { "desc": "flee!", - "goto": (_queue_action, {"action_dict": {"key": "flee"}}), + "goto": (_queue_action, {"action_dict": {"key": "flee", "repeat": True}}), }, { "desc": "hold, doing nothing", @@ -1071,6 +1084,8 @@ 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. + ## 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. diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.md index 5314c7d0c3..515bedaa27 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Combat-Twitch.md @@ -68,7 +68,7 @@ Here is the general design of the Twitch-based combat handler: - The twitch-version of the CombatHandler will be stored on each combatant whenever combat starts. When combat is over, or they leave the room with combat, the handler will be deleted. - The handler will queue each action independently, starting a timer until they fire. -- All input are handled via Commands. +- All input are handled via Evennia [Commands](../../../Components/Commands.md). ## Twitch combat handler @@ -281,10 +281,12 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatBaseHandler): ``` -- The `queue_action` (**Line 30**) method takes an "Action dict" representing an action the combatant wants to perform next. It must be one of the keyed Actions added to the handler in the `action_classes` property (**Line 17**). We make no use of the `combatant` keyword argument since we already know that the combatant is `self.obj`. +- **Line 30**: The `queue_action` method takes an "Action dict" representing an action the combatant wants to perform next. It must be one of the keyed Actions added to the handler in the `action_classes` property (**Line 17**). We make no use of the `combatant` keyword argument since we already know that the combatant is `self.obj`. - **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 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**). + - **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). @@ -328,6 +330,11 @@ 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 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: +- `dt`: How long to wait (in seconds) from queueing the action until it fires. +- `repeat`: Boolean determining if action should automatically be queued again after it fires. +``` - **Line 18**: Here we run through the usage methods of the action - where we perform the action. We let the action itself handle all the logics. - **Line 22**: We check for another optional flag on the action-dict: `repeat`. Unless it's set, we use the fallback-action defined on **Line 5**. Many actions should not repeat - for example, it would not make sense to do `wield` for the same weapon over and over. - **Line 27**: It's important that we know how to stop combat. We will write this method next. @@ -375,10 +382,10 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatBaseHandler): 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. +- **Line 12**: With our `.get_sides()` method we can easily get the two sides of the conflict. Note that `combatant` is not included among the allies, so we need to add it back in on the following line. - **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 we'll need to wrap up the Twitch combat. Read on. +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. ## Commands @@ -397,7 +404,7 @@ We'll override the first two for our parent. ```{code-block} python :linenos: -:emphasize-lines: 22,48 +:emphasize-lines: 23,49 # in evadventure/combat_twitch.py @@ -787,8 +794,7 @@ class CmdWield(_BaseTwitchCombatCommand): ``` -Wield follows the same pattern. - +The Wield command follows the same pattern as other commands. ## Grouping Commands for use @@ -827,24 +833,7 @@ class TwitchLookCmdSet(CmdSet): ``` -The first cmdset, `TwitchCombatCmdSet` is intended to be added to the Character. We can do so permanently by adding the cmdset to the default character cmdset (as outlined in the [Beginner Command lesson](../Part1/Beginner-Tutorial-Adding-Commands.md)). We can also add it more explicitly to our Character class over in `characters.py`: - -```python -# in evadventure/characters.py - -# ... - -class EvAdventureCharacter(LivingMixin, DefaultCharacter): - - # ... - - def at_object_creation - from .combat_twitch import TwitchCombatCmdSet - self.cmdset.add(TwitchCombatCmdSet, persistent=True) - -``` - -For quick testing we will also explore another option in the next section. +The first cmdset, `TwitchCombatCmdSet` is intended to be added to the Character. We can do so permanently by adding the cmdset to the default character cmdset (as outlined in the [Beginner Command lesson](../Part1/Beginner-Tutorial-Adding-Commands.md)). In the testing section below, we'll do this in another way. What about that `TwitchLookCmdSet`? We can't add it to our character permanently, because we only want this particular version of `look` to operate while we are in combat. @@ -931,6 +920,9 @@ Inside the test, we use the `self.call()` method to explicitly fire the Command ## A small combat test +```{sidebar} +You can find an example batch-command script in [evennia/contrib/tutorials/evadventure/batchscripts/twitch_combat_demo.ev](github:evennia/contrib/tutorials/evadventure/batchscripts/turnbased_combat_demo.ev) +``` Showing that the individual pieces of code works (unit testing) is not enough to be sure that your combat system is actually working. We need to test all the pieces _together_. This is often called _functional testing_. While functional testing can also be automated, wouldn't it be fun to be able to actually see our code in action? This is what we need for a minimal test: @@ -940,9 +932,6 @@ This is what we need for a minimal test: - A weapon we can `wield` - An item (like a potion) we can `use`. -```{sidebar} -You can find an example batch-command script in [evennia/contrib/tutorials/evadventure/batchscripts/twitch_combat_demo.ev](github:evennia/contrib/tutorials/evadventure/batchscripts/turnbased_combat_demo.ev) -``` While you can create these manually in-game, it can be convenient to create a [batch-command script](../../../Components/Batch-Command-Processor.md) to set up your testing environment. > create a new subfolder `evadventure/batchscripts/` (if it doesn't already exist) diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Overview.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Overview.md index c50a7067ab..6552521e23 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Overview.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Overview.md @@ -56,6 +56,7 @@ Beginner-Tutorial-NPCs Beginner-Tutorial-Combat-Base Beginner-Tutorial-Combat-Twitch Beginner-Tutorial-Combat-Turnbased +Beginner-Tutorial-AI Beginner-Tutorial-Dungeon Beginner-Tutorial-Monsters Beginner-Tutorial-Quests