evennia/evennia/contrib/tutorials/evadventure/tests/test_combat.py
2023-03-25 16:56:57 +01:00

426 lines
15 KiB
Python

"""
Test EvAdventure combat.
"""
from collections import deque
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 BaseEvenniaTest
from .. import combat
from ..characters import EvAdventureCharacter
from ..enums import Ability, WieldLocation
from ..npcs import EvAdventureMob
from ..objects import EvAdventureConsumable, EvAdventureRunestone, EvAdventureWeapon
from ..rooms import EvAdventureRoom
from .mixins import EvAdventureMixin
class EvAdventureCombatHandlerTest(BaseEvenniaTest):
"""
Test methods on the turn-based combat handler
"""
maxDiff = None
# make sure to mock away all time-keeping elements
@patch(
"evennia.contrib.tutorials.evadventure.combat.EvAdventureCombatHandler.interval",
new=-1,
)
@patch(
"evennia.contrib.tutorials.evadventure.combat.delay",
new=Mock(return_value=None),
)
def setUp(self):
super().setUp()
self.location = create.create_object(EvAdventureRoom, key="testroom")
self.combatant = create.create_object(
EvAdventureCharacter, key="testchar", location=self.location
)
self.location.allow_combat = True
self.location.allow_death = True
self.target = create.create_object(
EvAdventureMob,
key="testmonster",
location=self.location,
attributes=(("is_idle", True),),
)
# mock the msg so we can check what they were sent later
self.combatant.msg = Mock()
self.target.msg = Mock()
self.combathandler = combat.get_or_create_combathandler(self.combatant)
# add target to combat
self.combathandler.add_combatant(self.target)
def _get_action(self, action_dict={"key": "hold"}):
action_class = self.combathandler.action_classes[action_dict["key"]]
return action_class(self.combathandler, self.combatant, action_dict)
def _run_actions(
self, action_dict, action_dict2={"key": "hold"}, combatant_msg=None, target_msg=None
):
"""
Helper method to run an action and check so combatant saw the expected message.
"""
self.combathandler.queue_action(self.combatant, action_dict)
self.combathandler.queue_action(self.target, action_dict2)
self.combathandler.execute_full_turn()
if combatant_msg is not None:
# this works because we mock combatant.msg in SetUp
self.combatant.msg.assert_called_with(combatant_msg)
if target_msg is not None:
# this works because we mock target.msg in SetUp
self.combatant.msg.assert_called_with(target_msg)
def test_combatanthandler_setup(self):
"""Testing all is set up correctly in the combathandler"""
chandler = self.combathandler
self.assertEqual(dict(chandler.combatants), {self.combatant: deque(), self.target: deque()})
self.assertEqual(
dict(chandler.action_classes),
{
"hold": combat.CombatActionHold,
"attack": combat.CombatActionAttack,
"stunt": combat.CombatActionStunt,
"use": combat.CombatActionUseItem,
"wield": combat.CombatActionWield,
"flee": combat.CombatActionFlee,
},
)
self.assertEqual(chandler.flee_timeout, 1)
self.assertEqual(dict(chandler.advantage_matrix), {})
self.assertEqual(dict(chandler.disadvantage_matrix), {})
self.assertEqual(dict(chandler.fleeing_combatants), {})
self.assertEqual(dict(chandler.defeated_combatants), {})
def test_combathandler_msg(self):
"""Test sending messages to all in handler"""
self.location.msg_contents = Mock()
self.combathandler.msg("test_message")
self.location.msg_contents.assert_called_with(
"test_message",
exclude=[],
from_obj=None,
mapping={"testchar": self.combatant, "testmonster": self.target},
)
def test_remove_combatant(self):
"""Remove a combatant."""
self.combathandler.remove_combatant(self.target)
self.assertEqual(dict(self.combathandler.combatants), {self.combatant: deque()})
def test_stop_combat(self):
"""Stopping combat, making sure combathandler is deleted."""
self.combathandler.stop_combat()
self.assertIsNone(self.combathandler.pk)
def test_get_sides(self):
"""Getting the sides of combat"""
combatant2 = create.create_object(
EvAdventureCharacter, key="testchar2", location=self.location
)
target2 = create.create_object(
EvAdventureMob,
key="testmonster2",
location=self.location,
attributes=(("is_idle", True),),
)
self.combathandler.add_combatant(combatant2)
self.combathandler.add_combatant(target2)
# allies to combatant
allies, enemies = self.combathandler.get_sides(self.combatant)
self.assertEqual((allies, enemies), ([combatant2], [self.target, target2]))
# allies to monster
allies, enemies = self.combathandler.get_sides(self.target)
self.assertEqual((allies, enemies), ([target2], [self.combatant, combatant2]))
def test_get_combat_summary(self):
"""Test combat summary"""
# as seen from one side
result = str(self.combathandler.get_combat_summary(self.combatant))
self.assertEqual(
strip_ansi(result),
" testchar (Perfect) vs testmonster (Perfect) ",
)
# as seen from other side
result = str(self.combathandler.get_combat_summary(self.target))
self.assertEqual(
strip_ansi(result),
" testmonster (Perfect) vs testchar (Perfect) ",
)
def test_queue_and_execute_action(self):
"""Queue actions and execute"""
hold = {"key": "hold"}
self.combathandler.queue_action(self.combatant, hold)
self.assertEqual(
dict(self.combathandler.combatants),
{self.combatant: deque([hold]), self.target: deque()},
)
mock_action = Mock()
self.combathandler.action_classes["hold"] = Mock(return_value=mock_action)
self.combathandler.execute_next_action(self.combatant)
self.combathandler.action_classes["hold"].assert_called_with(
self.combathandler, self.combatant, hold
)
mock_action.execute.assert_called_once()
def test_execute_full_turn(self):
"""Run a full (passive) turn"""
hold = {"key": "hold"}
self.combathandler.queue_action(self.combatant, hold)
self.combathandler.queue_action(self.target, hold)
self.combathandler.execute_next_action = Mock()
self.combathandler.execute_full_turn()
self.combathandler.execute_next_action.assert_has_calls(
[call(self.combatant), call(self.target)], any_order=True
)
def test_combat_action(self):
"""General tests of action functionality"""
combatant = self.combatant
target = self.target
action = self._get_action({"key": "hold"})
self.assertTrue(action.can_use())
action.give_advantage(combatant, target)
action.give_disadvantage(combatant, target)
self.assertTrue(action.has_advantage(combatant, target))
self.assertTrue(action.has_disadvantage(combatant, target))
action.lose_advantage(combatant, target)
action.lose_disadvantage(combatant, target)
self.assertFalse(action.has_advantage(combatant, target))
self.assertFalse(action.has_disadvantage(combatant, target))
action.msg(f"$You() attack $You({target.key}).")
combatant.msg.assert_called_with(text=("You attack testmonster.", {}), from_obj=combatant)
def test_action__hold(self):
"""Hold, doing nothing"""
actiondict = {"key": "hold"}
self._run_actions(actiondict, actiondict)
self.assertEqual(self.combathandler.turn, 1)
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")
def test_attack__success__kill(self, mock_randint):
actiondict = {"key": "attack", "target": self.target}
mock_randint.return_value = 11 # 11 + 1 str will hit beat armor 11
self._run_actions(actiondict)
self.assertEqual(self.target.hp, -7)
# after this the combat is over
self.assertIsNone(self.combathandler.pk)
@patch("evennia.contrib.tutorials.evadventure.combat.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
self._run_actions(action_dict)
self.assertEqual(self.combathandler.advantage_matrix[self.combatant], {})
self.assertEqual(self.combathandler.disadvantage_matrix[self.combatant], {})
@patch("evennia.contrib.tutorials.evadventure.combat.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
self._run_actions(action_dict)
self.assertEqual(
bool(self.combathandler.advantage_matrix[self.combatant][self.target]), True
)
@patch("evennia.contrib.tutorials.evadventure.combat.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
self._run_actions(action_dict)
self.assertEqual(
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.
"""
self.assertEqual(self.combathandler.turn, 0)
action_dict = {"key": "flee"}
# first flee records the fleeing state
self._run_actions(action_dict)
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).",
{},
),
from_obj=self.combatant,
)
# Check that enemies have advantage against you now
action = combat.CombatAction(self.combathandler, self.target, {"key": "hold"})
self.assertTrue(action.has_advantage(self.target, self.combatant))
# second flee should remove combatant
self._run_actions(action_dict)
# this ends combat, so combathandler should be gone
self.assertIsNone(self.combathandler.pk)