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 d08e91e17b..20d6ce8899 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 @@ -1,9 +1,7 @@ # Part 3: How we get there (example game) ```{warning} -The tutorial game is under development and is not yet complete, nor tested. Use the existing -lessons as inspiration and to help get you going, but don't expect out-of-the-box perfection -from it at this time. +The tutorial game is under development and is not yet complete, nor tested. Use the existing lessons as inspiration and to help get you going, but don't expect out-of-the-box perfection from it at this time. ``` ```{sidebar} Beginner Tutorial Parts @@ -22,19 +20,15 @@ from it at this time. ``` In part three of the Evennia Beginner tutorial we will go through the actual creation of -our tutorial game _EvAdventure_, based on the [Knave](https://www.drivethrurpg.com/product/250888/Knave) -RPG ruleset. - -This is a big part. You'll be seeing a lot of code and there are plenty of lessons to go through. -Take your time! - -If you followed the previous parts of this tutorial you will have some notions about Python and where to -find and make use of things in Evennia. We also have a good idea of the type of game we will -create. +our tutorial game _EvAdventure_, based on the [Knave](https://www.drivethrurpg.com/product/250888/Knave) RPG ruleset. Even if this is not the game-style you are interested in, following along will give you a lot of experience using Evennia and be really helpful for doing your own thing later! +This is a big part. You'll be seeing a lot of code and there are plenty of lessons to go through. Take your time! + +If you followed the previous parts of this tutorial series you will have some notions about Python and where to find and make use of things in Evennia. We also have a good idea of the type of game we will create. + Fully coded examples of all code we make in this part can be found in the [evennia/contrib/tutorials/evadventure](../../../api/evennia.contrib.tutorials.evadventure.md) package. @@ -53,7 +47,7 @@ Beginner-Tutorial-Equipment Beginner-Tutorial-Chargen Beginner-Tutorial-Rooms Beginner-Tutorial-NPCs -Beginner-Tutorial-Turnbased-Combat +Beginner-Tutorial-Combat Beginner-Tutorial-Quests Beginner-Tutorial-Shops Beginner-Tutorial-Dungeon diff --git a/evennia/contrib/tutorials/evadventure/combat_turnbased.py b/evennia/contrib/tutorials/evadventure/combat_turnbased.py index 3d6cbf8ba5..efdc99acc4 100644 --- a/evennia/contrib/tutorials/evadventure/combat_turnbased.py +++ b/evennia/contrib/tutorials/evadventure/combat_turnbased.py @@ -437,6 +437,7 @@ class EvAdventureCombatHandler(DefaultScript): flee_timeout = 1 # persistent storage + turn = AttributeProperty(0) # who is involved in combat, and their action queue, @@ -569,6 +570,13 @@ class EvAdventureCombatHandler(DefaultScript): """ self.combatants[combatant].append(action_dict) + # track who inserted actions this turn (non-persistent) + did_action = set(self.nbd.did_action or ()) + did_action.add(combatant) + if len(did_action) >= len(self.combatants): + # everyone has inserted an action. Start next turn without waiting! + self.force_repeat() + def execute_next_action(self, combatant): """ Perform a combatant's next queued action. Note that there is _always_ an action queued, @@ -656,6 +664,22 @@ class EvAdventureCombatHandler(DefaultScript): self.msg(txt) self.stop_combat() + def get_combat_summary(self, combatant): + """ + Get a 'battle report' - an overview of the current state of combat. + + Goblin shaman + Ally (hurt) Goblin brawler + Bob vs Goblin grunt 1 (hurt) + Goblin grunt 2 + Goblin grunt 3 + + """ + allies, enemies = self.get_sides(combatant) + nallies, nenemies = len(allies), len(enemies) + + # make a table with three columns + def get_or_create_combathandler(combatant, combathandler_name="combathandler", combat_tick=5): """ @@ -711,7 +735,6 @@ Examples of commands: - |yuse on |n - use an item on an enemy or ally - |yflee|n - start to flee or disengage from combat - - |yhinder|n - hinder an enemy from fleeing Use |yhelp |n for more info.""" @@ -762,7 +785,6 @@ class CombatCmdSet(CmdSet): self.add(CmdUseItem()) self.add(CmdWield()) self.add(CmdUseFlee()) - self.add(CmdUseHinder()) class CmdAttack(_CmdCombatBase): @@ -965,44 +987,6 @@ class CmdFlee(_CmdCombatBase): self.msg("You prepare to flee!") -class CmdHinder(_CmdCombatBase): - """ - Hinder an enemy from fleeing combat. This is a DEX challenge. If you - don't specify a target, you'll try to block one that flees (or a random - one if there are multiple fleeing enemies). - - Usage: - hinder [target] - block [target] - - """ - - key = "hinder" - aliases = ["block"] - - def func(self): - - # see if anyone is trying to run away - combathandler = self.combathandler - allies, enemies = combathander.get_sides(self.caller) - fleeing_enemies = list( - set(enemies).intersection(set(combathandler.fleeing_combatants.values())) - ) - - if not fleeing_enemies: - self.caller.msg("No enemies are fleeing!") - return - - if self.args: - target = self.caller.search(self.args, candidates=fleeing_enemies) - if not target: - return - else: - target = random.choice(fleeing_enemies) - - combathandler.queue_action(self.caller, {"key": "hinder", "target": target}) - - # ----------------------------------------------------------------------------------- # # Turn-based combat (Final Fantasy style), using a menu @@ -1010,10 +994,51 @@ class CmdHinder(_CmdCombatBase): # ----------------------------------------------------------------------------------- -def _get_menu_options(caller): - location = caller.location +def _get_combathandler(caller): + evmenu = caller.ndb._evmenu + if not hasattr(evmenu, "combathandler"): + evmenu.combathandler = get_or_create_combathandler(caller) + return evmenu.combathandler - combathandler = location.scripts.get("combathandler") + +def _select_target(caller, raw_string, **kwargs): + """Helper to set a selection""" + action_dict = kwargs["action_dict"] + action_dict["target"] = kwargs["target"] + + _get_combathandler(caller).queue_action(caller, action_dict) + + +def node_choose_target(caller, raw_string, **kwargs): + """ + Choose target! + + """ + text = kwargs.get("text", "Choose your target!") + target_type = kwargs.get("target_type", "enemies") + + combathandler = _get_combathandler(caller) + allies, enemies = combathandler.get_sides(caller) + + if target_type == "enemies": + targets = enemies + else: + targets = allies + + options = [ + { + "desc": target.get_display_name(caller), + "goto": (_select_target, {"target": target, **kwargs}), + } + for target in targets + ] + + return text, options + + +def node_combat(caller, raw_string, **kwargs): + """Base combat menu""" + text = "" ## ----------------------------------------------------------------------------------- diff --git a/evennia/scripts/scripts.py b/evennia/scripts/scripts.py index 203abfbe5f..7abd589c34 100644 --- a/evennia/scripts/scripts.py +++ b/evennia/scripts/scripts.py @@ -6,13 +6,12 @@ ability to run timers. """ from django.utils.translation import gettext as _ -from twisted.internet.defer import Deferred, maybeDeferred -from twisted.internet.task import LoopingCall - from evennia.scripts.manager import ScriptManager from evennia.scripts.models import ScriptDB from evennia.typeclasses.models import TypeclassBase from evennia.utils import create, logger +from twisted.internet.defer import Deferred, maybeDeferred +from twisted.internet.task import LoopingCall __all__ = ["DefaultScript", "DoNothing", "Store"]