From b1c765b50fa54a177ae71c8b6eb9dbadabc43138 Mon Sep 17 00:00:00 2001 From: Griatch Date: Mon, 10 Apr 2023 13:12:41 +0200 Subject: [PATCH] Fixing tests for turnbased combat --- .../tutorials/evadventure/combat_base.py | 39 +- .../tutorials/evadventure/combat_turnbased.py | 16 +- .../tutorials/evadventure/combat_twitch.py | 16 +- .../evadventure/tests/test_combat.py | 378 ++++++++++++------ 4 files changed, 290 insertions(+), 159 deletions(-) diff --git a/evennia/contrib/tutorials/evadventure/combat_base.py b/evennia/contrib/tutorials/evadventure/combat_base.py index 4253800249..68bf6b4b97 100644 --- a/evennia/contrib/tutorials/evadventure/combat_base.py +++ b/evennia/contrib/tutorials/evadventure/combat_base.py @@ -12,7 +12,7 @@ This establishes the basic building blocks for combat: """ -from evennia import create_script +from evennia import Command, create_script from evennia.scripts.scripts import DefaultScript from evennia.typeclasses.attributes import AttributeProperty from evennia.utils import evtable @@ -27,9 +27,6 @@ class CombatFailure(RuntimeError): """ -# Combaw action classes - - class CombatAction: """ Parent class for all actions. @@ -130,7 +127,9 @@ class CombatActionAttack(CombatAction): target = self.target if weapon.at_pre_use(attacker, target): - weapon.use(attacker, target, advantage=self.has_advantage(attacker, target)) + weapon.use( + attacker, target, advantage=self.combathandler.has_advantage(attacker, target) + ) weapon.at_post_use(attacker, target) @@ -204,7 +203,7 @@ class CombatActionStunt(CombatAction): "|yHaving succeeded, you hold back to plan your next move.|n [hold]", broadcast=False, ) - combathandler.queue_action(attacker, combathandler.default_action_dict) + combathandler.queue_action(attacker, combathandler.fallback_action_dict) else: self.msg(f"$You({defender.key}) $conj(resist)! $You() $conj(fail) the stunt.") @@ -235,8 +234,8 @@ class CombatActionUseItem(CombatAction): item.use( user, target, - advantage=self.has_advantage(user, target), - disadvantage=self.has_disadvantage(user, target), + advantage=self.combathandler.has_advantage(user, target), + disadvantage=self.combathandler.has_disadvantage(user, target), ) item.at_post_use(user, target) # to back to idle after this @@ -365,11 +364,11 @@ class EvAdventureCombatHandlerBase(DefaultScript): Example: :: - Goblin shaman (Perfect)[attack] - Gregor (Hurt)[attack] Goblin brawler(Hurt)[attack] - Bob (Perfect)[stunt] vs Goblin grunt 1 (Hurt)[attack] - Goblin grunt 2 (Perfect)[hold] - Goblin grunt 3 (Wounded)[flee] + Goblin shaman (Perfect) + Gregor (Hurt) Goblin brawler(Hurt) + Bob (Perfect) vs Goblin grunt 1 (Hurt) + Goblin grunt 2 (Perfect) + Goblin grunt 3 (Wounded) """ allies, enemies = self.get_sides(combatant) @@ -378,14 +377,8 @@ class EvAdventureCombatHandlerBase(DefaultScript): nallies, nenemies = len(allies), len(enemies) # prepare colors and hurt-levels - allies = [ - f"{ally} ({ally.hurt_level})[{self.get_next_action_dict(ally)['key']}]" - for ally in allies - ] - enemies = [ - f"{enemy} ({enemy.hurt_level})[{self.get_next_action_dict(enemy)['key']}]" - for enemy in enemies - ] + allies = [f"{ally} ({ally.hurt_level})" for ally in allies] + enemies = [f"{enemy} ({enemy.hurt_level})" for enemy in enemies] # the center column with the 'vs' vs_column = ["" for _ in range(max(nallies, nenemies))] @@ -523,13 +516,11 @@ class EvAdventureCombatHandlerBase(DefaultScript): Check if this combat should be aborted, whatever this means for the particular the particular combat type. - Stop the combat immediately. This should also do all needed cleanup. - Keyword Args: kwargs: Any extra keyword args used. Returns: - bool: If `True`, the `stop_combat` method sho + bool: If `True`, the `stop_combat` method should be called. """ raise NotImplementedError diff --git a/evennia/contrib/tutorials/evadventure/combat_turnbased.py b/evennia/contrib/tutorials/evadventure/combat_turnbased.py index 62d35b9d45..20c888a1de 100644 --- a/evennia/contrib/tutorials/evadventure/combat_turnbased.py +++ b/evennia/contrib/tutorials/evadventure/combat_turnbased.py @@ -87,13 +87,10 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandlerBase): } # how many turns you must be fleeing before escaping - flee_timeout = AttributeProperty(3, autocreate=False) - - # how many turns you must be fleeing before escaping - flee_timeout = AttributeProperty(3, autocreate=False) + flee_timeout = AttributeProperty(1, autocreate=False) # fallback action if not selecting anything - fallback_action_dict = AttributeProperty({"key": "attack"}, autocreate=False) + fallback_action_dict = AttributeProperty({"key": "hold"}, autocreate=False) # persistent storage @@ -137,7 +134,6 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandlerBase): """ self.disadvantage_matrix[combatant][target] = True - self.combathandler.advantage_matrix[combatant][target] = False def has_advantage(self, combatant, target, **kwargs): """ @@ -148,8 +144,8 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandlerBase): target (Character or NPC): The target to check advantage against. """ - return bool(self.combathandler.advantage_matrix[combatant].pop(target, False)) or ( - target in self.combathandler.fleeing_combatants + return bool(self.advantage_matrix[combatant].pop(target, False)) or ( + target in self.fleeing_combatants ) def has_disadvantage(self, combatant, target): @@ -161,8 +157,8 @@ class EvAdventureTurnbasedCombatHandler(EvAdventureCombatHandlerBase): target (Character or NPC): The target to check disadvantage against. """ - return bool(self.combathandler.disadvantage_matrix[combatant].pop(target, False)) or ( - combatant in self.combathandler.fleeing_combatants + return bool(self.disadvantage_matrix[combatant].pop(target, False)) or ( + combatant in self.fleeing_combatants ) def add_combatant(self, combatant): diff --git a/evennia/contrib/tutorials/evadventure/combat_twitch.py b/evennia/contrib/tutorials/evadventure/combat_twitch.py index 2d8927ab30..cddfc2ef0d 100644 --- a/evennia/contrib/tutorials/evadventure/combat_twitch.py +++ b/evennia/contrib/tutorials/evadventure/combat_twitch.py @@ -156,11 +156,11 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase): unrepeat(self.current_ticker_ref) if dt <= 0: # no repeat - self.tickerhandler_ref = None + self.current_ticker_ref = None else: # always schedule the task to be repeating, cancel later otherwise. We store # the tickerhandler's ref to make sure we can remove it later - self.tickerhandler_ref = repeat(dt, self.execute_next_action, id_string="combat") + self.current_ticker_ref = repeat(dt, self.execute_next_action, id_string="combat") def execute_next_action(self): """ @@ -178,15 +178,21 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase): 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.get("dt", 0)) + self.queue_action(self.fallback_action_dict) def check_stop_combat(self): # check if one side won the battle. + allies, enemies = self.get_sides() + allies.append(self.obj) + + # remove all dead combatants + allies = [comb for comb in allies if comb.hp > 0] + enemies = [comb for comb in enemies if comb.hp > 0] if not allies and not enemies: - txt = "Noone stands after the dust settles." - self.msg(txt) + self.msg("Noone stands after the dust settles.") + self.stop_combat() return if not allies or not enemies: diff --git a/evennia/contrib/tutorials/evadventure/tests/test_combat.py b/evennia/contrib/tutorials/evadventure/tests/test_combat.py index d39168fbdd..44c49e5297 100644 --- a/evennia/contrib/tutorials/evadventure/tests/test_combat.py +++ b/evennia/contrib/tutorials/evadventure/tests/test_combat.py @@ -3,7 +3,6 @@ Test EvAdventure combat. """ -from collections import deque from unittest.mock import Mock, call, patch from evennia.utils import create @@ -82,25 +81,207 @@ class TestEvAdventureCombatHandlerBase(_CombatTestBase): def test_get_combat_summary(self): """Test combat summary""" - self.combathandler.get_sides = Mock(return_value=(self.combatant, self.target)) + self.combathandler.get_sides = Mock(return_value=([], [self.target])) # as seen from one side result = str(self.combathandler.get_combat_summary(self.combatant)) self.assertEqual( strip_ansi(result), - " testchar (Perfect) vs testmonster (Perfect) ", + " testchar (Perfect) vs testmonster (Perfect) ", ) # as seen from other side + self.combathandler.get_sides = Mock(return_value=([], [self.combatant])) result = str(self.combathandler.get_combat_summary(self.target)) self.assertEqual( strip_ansi(result), - " testmonster (Perfect) vs testchar (Perfect) ", + " testmonster (Perfect) vs testchar (Perfect) ", ) +class TestCombatActionsBase(_CombatTestBase): + """ + A class for testing all subclasses of CombatAction in combat_base.py + + """ + + def setUp(self): + super().setUp() + self.combathandler = combat_base.EvAdventureCombatHandlerBase.get_or_create_combathandler( + self.location, key="combathandler" + ) + # we need to mock all NotImplemented methods + self.combathandler.get_sides = Mock(return_value=([], [self.target])) + self.combathandler.give_advantage = Mock() + self.combathandler.give_disadvantage = Mock() + self.combathandler.remove_advantage = Mock() + self.combathandler.remove_disadvantage = Mock() + self.combathandler.get_advantage = Mock() + self.combathandler.get_disadvantage = Mock() + self.combathandler.has_advantage = Mock() + self.combathandler.has_disadvantage = Mock() + self.combathandler.queue_action = Mock() + + def test_base_action(self): + action = combat_base.CombatAction( + self.combathandler, self.combatant, {"key": "hold", "foo": "bar"} + ) + self.assertEqual(action.key, "hold") + self.assertEqual(action.foo, "bar") + self.assertEqual(action.combathandler, self.combathandler) + self.assertEqual(action.combatant, self.combatant) + + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") + def test_attack__miss(self, mock_randint): + actiondict = {"key": "attack", "target": self.target} + + mock_randint.return_value = 8 # target has default armor 11, so 8+1 str will miss + action = combat_base.CombatActionAttack(self.combathandler, self.combatant, actiondict) + action.execute() + self.assertEqual(self.target.hp, 4) + + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") + def test_attack__success(self, mock_randint): + actiondict = {"key": "attack", "target": self.target} + + mock_randint.return_value = 11 # 11 + 1 str will hit beat armor 11 + self.target.hp = 20 + action = combat_base.CombatActionAttack(self.combathandler, self.combatant, actiondict) + action.execute() + self.assertEqual(self.target.hp, 9) + + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") + def test_stunt_fail(self, mock_randint): + action_dict = { + "key": "stunt", + "recipient": self.combatant, + "target": self.target, + "advantage": True, + "stunt_type": Ability.STR, + "defense_type": Ability.DEX, + } + mock_randint.return_value = 8 # fails 8+1 dex vs DEX 11 defence + action = combat_base.CombatActionStunt(self.combathandler, self.combatant, action_dict) + action.execute() + self.combathandler.give_advantage.assert_not_called() + + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") + def test_stunt_advantage__success(self, mock_randint): + action_dict = { + "key": "stunt", + "recipient": self.combatant, + "target": self.target, + "advantage": True, + "stunt_type": Ability.STR, + "defense_type": Ability.DEX, + } + mock_randint.return_value = 11 # 11+1 dex vs DEX 11 defence is success + action = combat_base.CombatActionStunt(self.combathandler, self.combatant, action_dict) + action.execute() + self.combathandler.give_advantage.assert_called_with(self.combatant, self.target) + + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") + def test_stunt_disadvantage__success(self, mock_randint): + action_dict = { + "key": "stunt", + "recipient": self.target, + "target": self.combatant, + "advantage": False, + "stunt_type": Ability.STR, + "defense_type": Ability.DEX, + } + mock_randint.return_value = 11 # 11+1 dex vs DEX 11 defence is success + action = combat_base.CombatActionStunt(self.combathandler, self.combatant, action_dict) + action.execute() + self.combathandler.give_disadvantage.assert_called_with(self.target, self.combatant) + + def test_use_item(self): + """ + Use up a potion during combat. + + """ + item = create.create_object( + EvAdventureConsumable, key="Healing potion", attributes=[("uses", 2)] + ) + + item.use = Mock() + + action_dict = { + "key": "use", + "item": item, + "target": self.target, + } + + self.assertEqual(item.uses, 2) + action = combat_base.CombatActionUseItem(self.combathandler, self.combatant, action_dict) + action.execute() + self.assertEqual(item.uses, 1) + action.execute() + self.assertEqual(item.pk, None) # deleted, it was used up + + def test_swap_wielded_weapon_or_spell(self): + """ + First draw a weapon (from empty fists), then swap that out to another weapon, then + swap to a spell rune. + + """ + sword = create.create_object(EvAdventureWeapon, key="sword") + zweihander = create.create_object( + EvAdventureWeapon, + key="zweihander", + attributes=(("inventory_use_slot", WieldLocation.TWO_HANDS),), + ) + runestone = create.create_object(EvAdventureRunestone, key="ice rune") + + # check hands are empty + self.assertEqual(self.combatant.weapon.key, "Empty Fists") + self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], None) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], None) + + # swap to sword + + actiondict = {"key": "wield", "item": sword} + + action = combat_base.CombatActionWield(self.combathandler, self.combatant, actiondict) + action.execute() + + self.assertEqual(self.combatant.weapon, sword) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], sword) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], None) + + # swap to zweihander (two-handed sword) + actiondict["item"] = zweihander + + action = combat_base.CombatActionWield(self.combathandler, self.combatant, actiondict) + action.execute() + + self.assertEqual(self.combatant.weapon, zweihander) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], None) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], zweihander) + + # swap to runestone (also using two hands) + actiondict["item"] = runestone + + action = combat_base.CombatActionWield(self.combathandler, self.combatant, actiondict) + action.execute() + + self.assertEqual(self.combatant.weapon, runestone) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], None) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], runestone) + + # swap back to normal one-handed sword + actiondict["item"] = sword + + action = combat_base.CombatActionWield(self.combathandler, self.combatant, actiondict) + action.execute() + + self.assertEqual(self.combatant.weapon, sword) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], sword) + self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], None) + + class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): """ Test methods on the turn-based combat handler and actions @@ -111,18 +292,17 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): # make sure to mock away all time-keeping elements @patch( - "evennia.contrib.tutorials.evadventure.combat_turnbased.EvAdventureTurnbasedCombatHandler.interval", # noqa + ( + "evennia.contrib.tutorials.evadventure." + "combat_turnbased.EvAdventureTurnbasedCombatHandler.interval" + ), new=-1, ) - @patch( - "evennia.contrib.tutorials.evadventure.combat_turnbased.delay", - new=Mock(return_value=None), - ) def setUp(self): super().setUp() # add target to combat self.combathandler = ( - combat_turnbased.EvAdventureTurnebasedCombatHandler.get_or_create_combathandler( + combat_turnbased.EvAdventureTurnbasedCombatHandler.get_or_create_combathandler( self.location, key="combathandler" ) ) @@ -141,7 +321,7 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): """ self.combathandler.queue_action(self.combatant, action_dict) self.combathandler.queue_action(self.target, action_dict2) - self.combathandler.execute_full_turn() + self.combathandler.at_repeat() if combatant_msg is not None: # this works because we mock combatant.msg in SetUp self.combatant.msg.assert_called_with(combatant_msg) @@ -153,7 +333,10 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): """Testing all is set up correctly in the combathandler""" chandler = self.combathandler - self.assertEqual(dict(chandler.combatants), {self.combatant: {}, self.target: {}}) + self.assertEqual( + dict(chandler.combatants), + {self.combatant: {"key": "hold"}, self.target: {"key": "hold"}}, + ) self.assertEqual( dict(chandler.action_classes), { @@ -175,8 +358,7 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): """Remove a combatant.""" self.combathandler.remove_combatant(self.target) - - self.assertEqual(dict(self.combathandler.combatants), {self.combatant: deque()}) + self.assertEqual(dict(self.combathandler.combatants), {self.combatant: {"key": "hold"}}) def test_stop_combat(self): """Stopping combat, making sure combathandler is deleted.""" @@ -215,7 +397,7 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): self.combathandler.queue_action(self.combatant, hold) self.assertEqual( dict(self.combathandler.combatants), - {self.combatant: deque([hold]), self.target: deque()}, + {self.combatant: {"key": "hold"}, self.target: {"key": "hold"}}, ) mock_action = Mock() @@ -238,14 +420,14 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): self.combathandler.execute_next_action = Mock() - self.combathandler.execute_full_turn() + self.combathandler.at_repeat() self.combathandler.execute_next_action.assert_has_calls( [call(self.combatant), call(self.target)], any_order=True ) - def test_action__hold(self): - """Hold, doing nothing""" + def test_action__action_ticks_turn(self): + """Test that action execution ticks turns""" actiondict = {"key": "hold"} self._run_actions(actiondict, actiondict) @@ -253,26 +435,9 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): self.combatant.msg.assert_not_called() - @patch("evennia.contrib.tutorials.evadventure.combat.rules.randint") - def test_attack__miss(self, mock_randint): - actiondict = {"key": "attack", "target": self.target} - - mock_randint.return_value = 8 # target has default armor 11, so 8+1 str will miss - self._run_actions(actiondict) - self.assertEqual(self.target.hp, 4) - - @patch("evennia.contrib.tutorials.evadventure.combat.rules.randint") - def test_attack__success__still_alive(self, mock_randint): - actiondict = {"key": "attack", "target": self.target} - - mock_randint.return_value = 11 # 11 + 1 str will hit beat armor 11 - # make sure target survives - self.target.hp = 20 - self._run_actions(actiondict) - self.assertEqual(self.target.hp, 9) - - @patch("evennia.contrib.tutorials.evadventure.combat.rules.randint") + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") def test_attack__success__kill(self, mock_randint): + """Test that the combathandler is deleted once there are no more enemies""" actiondict = {"key": "attack", "target": self.target} mock_randint.return_value = 11 # 11 + 1 str will hit beat armor 11 @@ -281,7 +446,7 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): # after this the combat is over self.assertIsNone(self.combathandler.pk) - @patch("evennia.contrib.tutorials.evadventure.combat.rules.randint") + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") def test_stunt_fail(self, mock_randint): action_dict = { "key": "stunt", @@ -296,8 +461,9 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): self.assertEqual(self.combathandler.advantage_matrix[self.combatant], {}) self.assertEqual(self.combathandler.disadvantage_matrix[self.combatant], {}) - @patch("evennia.contrib.tutorials.evadventure.combat.rules.randint") + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") def test_stunt_advantage__success(self, mock_randint): + """Test so the advantage matrix is updated correctly""" action_dict = { "key": "stunt", "recipient": self.combatant, @@ -312,8 +478,9 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): bool(self.combathandler.advantage_matrix[self.combatant][self.target]), True ) - @patch("evennia.contrib.tutorials.evadventure.combat.rules.randint") + @patch("evennia.contrib.tutorials.evadventure.combat_base.rules.randint") def test_stunt_disadvantage__success(self, mock_randint): + """Test so the disadvantage matrix is updated correctly""" action_dict = { "key": "stunt", "recipient": self.target, @@ -328,81 +495,6 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): bool(self.combathandler.disadvantage_matrix[self.target][self.combatant]), True ) - def test_use_item(self): - """ - Use up a potion during combat. - - """ - item = create.create_object( - EvAdventureConsumable, key="Healing potion", attributes=[("uses", 2)] - ) - - item.use = Mock() - - action_dict = { - "key": "use", - "item": item, - "target": self.target, - } - - self.assertEqual(item.uses, 2) - self._run_actions(action_dict) - self.assertEqual(item.uses, 1) - self._run_actions(action_dict) - self.assertEqual(item.pk, None) # deleted, it was used up - - def test_swap_wielded_weapon_or_spell(self): - """ - First draw a weapon (from empty fists), then swap that out to another weapon, then - swap to a spell rune. - - """ - sword = create.create_object(EvAdventureWeapon, key="sword") - zweihander = create.create_object( - EvAdventureWeapon, - key="zweihander", - attributes=(("inventory_use_slot", WieldLocation.TWO_HANDS),), - ) - runestone = create.create_object(EvAdventureRunestone, key="ice rune") - - # check hands are empty - self.assertEqual(self.combatant.weapon.key, "Empty Fists") - self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], None) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], None) - - # swap to sword - - actiondict = {"key": "wield", "item": sword} - - self._run_actions(actiondict) - self.assertEqual(self.combatant.weapon, sword) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], sword) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], None) - - # swap to zweihander (two-handed sword) - actiondict["item"] = zweihander - - self._run_actions(actiondict) - self.assertEqual(self.combatant.weapon, zweihander) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], None) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], zweihander) - - # swap to runestone (also using two hands) - actiondict["item"] = runestone - - self._run_actions(actiondict) - self.assertEqual(self.combatant.weapon, runestone) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], None) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], runestone) - - # swap back to normal one-handed sword - actiondict["item"] = sword - - self._run_actions(actiondict) - self.assertEqual(self.combatant.weapon, sword) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.WEAPON_HAND], sword) - self.assertEqual(self.combatant.equipment.slots[WieldLocation.TWO_HANDS], None) - def test_flee__success(self): """ Test fleeing twice, leading to leaving combat. @@ -414,19 +506,20 @@ class EvAdventureTurnbasedCombatHandlerTest(_CombatTestBase): # first flee records the fleeing state self._run_actions(action_dict) + self.combathandler.flee_timeout = 1 # to make sure self.assertEqual(self.combathandler.turn, 1) self.assertEqual(self.combathandler.fleeing_combatants[self.combatant], 1) self.combatant.msg.assert_called_with( text=( - "You retreat, leaving yourself exposed while doing so (will escape in 1 turn).", + "You retreat, being exposed to attack while doing so (will escape in 1 turn).", {}, ), from_obj=self.combatant, ) # Check that enemies have advantage against you now action = combat_turnbased.CombatAction(self.combathandler, self.target, {"key": "hold"}) - self.assertTrue(action.has_advantage(self.target, self.combatant)) + self.assertTrue(action.combathandler.has_advantage(self.target, self.combatant)) # second flee should remove combatant self._run_actions(action_dict) @@ -450,7 +543,52 @@ class TestEvAdventureTwitchCombatHandler(EvenniaCommandTestMixin, _CombatTestBas ) def test_get_sides(self): - """ """ + sides = self.combatant_combathandler.get_sides(self.combatant) + self.assertEqual(sides, ([], [self.target])) - def test_queue_and_execute_action(self): - """ """ + @patch("evennia.contrib.tutorials.evadventure.combat_twitch.unrepeat", new=Mock()) + @patch("evennia.contrib.tutorials.evadventure.combat_twitch.repeat", new=Mock(return_value=999)) + def test_queue_action(self): + """Test so the queue action cleans up tickerhandler correctly""" + + actiondict = {"key": "hold"} + self.combatant_combathandler.queue_action(actiondict) + + self.assertIsNone(self.combatant_combathandler.current_ticker_ref) + + actiondict = {"key": "hold", "dt": 5} + self.combatant_combathandler.queue_action(actiondict) + self.assertEqual(self.combatant_combathandler.current_ticker_ref, 999) + + @patch("evennia.contrib.tutorials.evadventure.combat_twitch.unrepeat", new=Mock()) + @patch("evennia.contrib.tutorials.evadventure.combat_twitch.repeat", new=Mock()) + def test_execute_next_action(self): + self.combatant_combathandler.action_dict = { + "key": "hold", + "dummy": "foo", + "repeat": False, + } # to separate from fallback + + self.combatant_combathandler.execute_next_action() + # should now be back to fallback + self.assertEqual( + self.combatant_combathandler.action_dict, + self.combatant_combathandler.fallback_action_dict, + ) + + @patch("evennia.contrib.tutorials.evadventure.combat_twitch.unrepeat", new=Mock()) + def test_check_stop_combat(self): + """Test if combat should stop""" + + # noone remains + self.combatant_combathandler.get_sides = Mock(return_value=([], [])) + self.combatant_combathandler.stop_combat = Mock() + + self.combatant_combathandler.check_stop_combat() + self.combatant.msg.assert_called_with() + self.combatant_combathandler.stop_combat.assert_called() + + # one side wiped out + self.combatant_combathandler.get_sides = Mock(return_value=([], [])) + self.combatant_combathandler.check_stop_combat() + self.combatant.msg.assert_called_with()