diff --git a/evennia/contrib/tutorials/evadventure/combat_base.py b/evennia/contrib/tutorials/evadventure/combat_base.py index 68bf6b4b97..17ef4d8413 100644 --- a/evennia/contrib/tutorials/evadventure/combat_base.py +++ b/evennia/contrib/tutorials/evadventure/combat_base.py @@ -3,13 +3,14 @@ EvAdventure Base combat utilities. This establishes the basic building blocks for combat: - - `CombatAction` - classes encompassing all the working around an action. They are initialized - from 'action-dicts` - dictionaries with all the relevant data for the particular invocation + - `CombatFailure` - exception for combat-specific errors. + - `CombatAction` (and subclasses) - classes encompassing all the working around an action. + They are initialized from 'action-dicts` - dictionaries with all the relevant data for the + particular invocation - `CombatHandler` - base class for running a combat. Exactly how this is used depends on the type of combat intended (twitch- or turn-based) so many details of this will be implemented in child classes. - """ from evennia import Command, create_script @@ -318,7 +319,7 @@ class EvAdventureCombatHandlerBase(DefaultScript): obj.ndb.combathandler = combathandler return combathandler - def msg(self, message, combatant=None, broadcast=True): + def msg(self, message, combatant=None, broadcast=True, location=None): """ Central place for sending messages to combatants. This allows for adding any combat-specific text-decoration in one place. @@ -329,6 +330,9 @@ class EvAdventureCombatHandlerBase(DefaultScript): broadcast (bool): If `False`, `combatant` must be included and will be the only one to see the message. If `True`, send to everyone in the location. + location (Object, optional): If given, use this as the location to + send broadcast messages to. If not, use `self.obj` as that + location. Notes: If `combatant` is given, use `$You/you()` markup to create @@ -336,7 +340,9 @@ class EvAdventureCombatHandlerBase(DefaultScript): `$You(combatant_key)` to refer to other combatants. """ - location = self.obj + if not location: + location = self.obj + location_objs = location.contents exclude = [] diff --git a/evennia/contrib/tutorials/evadventure/combat_twitch.py b/evennia/contrib/tutorials/evadventure/combat_twitch.py index cddfc2ef0d..f73dd18a89 100644 --- a/evennia/contrib/tutorials/evadventure/combat_twitch.py +++ b/evennia/contrib/tutorials/evadventure/combat_twitch.py @@ -48,6 +48,28 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase): # stores the current ticker reference, so we can manipulate it later current_ticker_ref = AttributeProperty(None) + def msg(self, message, broadcast=True): + """ + Central place for sending messages to combatants. This allows + for adding any combat-specific text-decoration in one place. + + Args: + message (str): The message to send. + combatant (Object): The 'You' in the message, if any. + broadcast (bool): If `False`, `combatant` must be included and + will be the only one to see the message. If `True`, send to + everyone in the location. + location (Object, optional): If given, use this as the location to + send broadcast messages to. If not, use `self.obj` as that + location. + + Notes: + If `combatant` is given, use `$You/you()` markup to create + a message that looks different depending on who sees it. Use + `$You(combatant_key)` to refer to other combatants. + """ + super().msg(message, broadcast=broadcast, location=self.obj.location) + def get_sides(self, combatant): """ Get a listing of the two 'sides' of this combat, from the perspective of the provided @@ -181,7 +203,9 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase): self.queue_action(self.fallback_action_dict) def check_stop_combat(self): - # check if one side won the battle. + """ + Check if the combat is over. + """ allies, enemies = self.get_sides() allies.append(self.obj) @@ -191,15 +215,19 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase): enemies = [comb for comb in enemies if comb.hp > 0] if not allies and not enemies: - self.msg("Noone stands after the dust settles.") + self.msg("Noone stands after the dust settles.", broadcast=False) self.stop_combat() return if not allies or not enemies: - still_standing = list_to_string( - f"$You({comb.key})" for comb in allies + enemies if comb.hp > 0 + still_standing = list_to_string(f"$You({comb.key})" for comb in allies + enemies) + self.msg( + ( + f"The combat is over. {still_standing} $pluralize(is, {len(allies + enemies)})" + " are) still standing." + ), + broadcast=False, ) - self.msg(f"The combat is over. {still_standing} are still standing.") self.stop_combat() def stop_combat(self): @@ -268,7 +296,7 @@ class CmdAttack(_BaseTwitchCombatCommand): help_category = "combat" def func(self): - target = self.search(self.lhs) + target = self.caller.search(self.lhs) if not target: return diff --git a/evennia/contrib/tutorials/evadventure/tests/test_combat.py b/evennia/contrib/tutorials/evadventure/tests/test_combat.py index 44c49e5297..2cf18cb02e 100644 --- a/evennia/contrib/tutorials/evadventure/tests/test_combat.py +++ b/evennia/contrib/tutorials/evadventure/tests/test_combat.py @@ -7,7 +7,7 @@ from unittest.mock import Mock, call, patch from evennia.utils import create from evennia.utils.ansi import strip_ansi -from evennia.utils.test_resources import EvenniaCommandTestMixin, EvenniaTestCase +from evennia.utils.test_resources import BaseEvenniaTest, EvenniaCommandTestMixin, EvenniaTestCase from .. import combat_base, combat_turnbased, combat_twitch from ..characters import EvAdventureCharacter @@ -531,6 +531,10 @@ class TestEvAdventureTwitchCombatHandler(EvenniaCommandTestMixin, _CombatTestBas def setUp(self): super().setUp() + # in order to use the EvenniaCommandTestMixin we need these variables defined + self.char1 = self.combatant + self.account = None + self.combatant_combathandler = ( combat_twitch.EvAdventureCombatTwitchHandler.get_or_create_combathandler( self.combatant, key="combathandler" @@ -578,17 +582,31 @@ class TestEvAdventureTwitchCombatHandler(EvenniaCommandTestMixin, _CombatTestBas @patch("evennia.contrib.tutorials.evadventure.combat_twitch.unrepeat", new=Mock()) def test_check_stop_combat(self): - """Test if combat should stop""" + """Test combat-stop functionality""" # noone remains self.combatant_combathandler.get_sides = Mock(return_value=([], [])) self.combatant_combathandler.stop_combat = Mock() + self.combatant.hp = -1 + self.target.hp = -1 + self.combatant_combathandler.check_stop_combat() self.combatant.msg.assert_called_with() self.combatant_combathandler.stop_combat.assert_called() - # one side wiped out + # only one side wiped out + self.combatant = 10 self.combatant_combathandler.get_sides = Mock(return_value=([], [])) self.combatant_combathandler.check_stop_combat() self.combatant.msg.assert_called_with() + + @patch("evennia.contrib.tutorials.evadventure.combat_twitch.unrepeat", new=Mock()) + @patch("evennia.contrib.tutorials.evadventure.combat_twitch.repeat", new=Mock()) + def test_attack(self): + """Test attack action in the twitch combathandler""" + self.call(combat_twitch.CmdAttack(), f"{self.target.key}", "") + self.assertEqual( + self.combatant_combathandler.action_dict, + {"key": "attack", "target": self.target, "dt": 3}, + )