More tutorial combat tests

This commit is contained in:
Griatch 2022-07-16 18:01:09 +02:00
parent e6ac8d347e
commit 2daadca999
6 changed files with 431 additions and 124 deletions

View file

@ -119,13 +119,17 @@ class EquipmentHandler:
method.
Returns:
int: Armor from equipment.
int: Armor from equipment. Note that this is the +bonus of Armor, not the
'defense' (to get that one adds 10).
"""
slots = self.slots
return sum(
(
getattr(slots[WieldLocation.BODY], "armor", 0),
# armor is listed using its defense, so we remove 10 from it
# (11 is base no-armor value in Knave)
getattr(slots[WieldLocation.BODY], "armor", 11) - 10,
# shields and helmets are listed by their bonus to armor
getattr(slots[WieldLocation.SHIELD_HAND], "armor", 0),
getattr(slots[WieldLocation.HEAD], "armor", 0),
)
@ -333,7 +337,8 @@ class EquipmentHandler:
list: A list of objects that are usable.
"""
return [obj for obj in slots[WieldLocation.BACKPACK] if obj.uses > 0]
character = self.obj
return [obj for obj in slots[WieldLocation.BACKPACK] if obj.at_pre_use(character)]
class LivingMixin:
@ -386,6 +391,21 @@ class LivingMixin:
"""
pass
def at_defeat(self):
"""
Called when this living thing reaches HP 0.
"""
# by default, defeat means death
self.at_death()
def at_death(self):
"""
Called when this living thing dies.
"""
pass
class EvAdventureCharacter(LivingMixin, DefaultCharacter):
"""
@ -417,6 +437,14 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter):
"""Allows to access equipment like char.equipment.worn"""
return EquipmentHandler(self)
@property
def weapon(self):
return self.equipment.weapon
@property
def armor(self):
return self.equipment.armor
def at_pre_object_receive(self, moved_object, source_location, **kwargs):
"""
Hook called by Evennia before moving an object here. Return False to abort move.
@ -475,20 +503,14 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter):
rules.dice.roll_death(self)
if hp <= 0:
# this means we rolled death on the table
self.handle_death()
self.at_death()
else:
# still alive, but lost in some stats
self.location.msg_contents(
f"|y$You() $conj(stagger) back, weakened but still alive.|n", from_obj=self
)
def defeat_message(self, attacker, dmg):
"""
Sent out to everyone in the location by the combathandler.
"""
def handle_death(self):
def at_death(self):
"""
Called when character dies.

View file

@ -326,8 +326,7 @@ class CombatActionStunt(CombatAction):
"actions. The effect needs to be used up within 5 turns."
)
give_advantage = True
give_disadvantage = False
give_advantage = True # if False, give_disadvantage
max_uses = 1
priority = -1
attack_type = Ability.DEX
@ -353,10 +352,19 @@ class CombatActionStunt(CombatAction):
)
self.msg(f"$You() $conj(attempt) stunt on $You(defender.key). {txt}")
if is_success:
if advantage:
stunt_duration = self.combathandler.stunt_duration
if self.give_advantage:
self.combathandler.gain_advantage(attacker, defender)
self.msg(
f"%You() $conj(gain) advantage against $You(defender.key! "
f"You must use it within {stunt_duration} turns."
)
else:
self.combathandler.gain_disadvantage(defender, attacker)
self.msg(
f"%You(defender.key) $conj(suffer) disadvantage against $You(). "
f"Lasts next attack, or until 3 turns passed."
)
# only spend a use after being successful
self.uses += 1
@ -386,19 +394,53 @@ class CombatActionUseItem(CombatAction):
help_text = "Use an item from your inventory."
def get_help(self, item, *args):
return item.combat_get_help(*args)
def can_use(self, item, *args, **kwargs):
return item.combat_can_use(self.combatant, self.combathandler, *args, **kwargs)
return item.get_help(*args)
def pre_use(self, item, *args, **kwargs):
item.combat_pre_use(self.combatant, *args, **kwargs)
"""
We tie into the `item.at_pre_use` hook here, which returns False if
the item is not usable (that is, has .uses > 0).
"""
if item.at_pre_use(self.combatant, *args, **kwargs):
item.at_use(self.combatant, *args, **kwargs)
def use(self, item, target, *args, **kwargs):
item.combat_use(self.combatant, target, *args, **kwargs)
item.at_use(self.combatant, target, *args, **kwargs)
def post_use(self, item, *args, **kwargs):
item.combat_post_use(self.combatant, *args, **kwargs)
item.at_post_use(self.combatant, *args, **kwargs)
self.msg("$You() $conj(use) an item.")
class CombatActionSwapWieldedWeaponOrSpell(CombatAction):
"""
Swap Wielded weapon or spell.
"""
key = "Swap weapon/rune/shield"
desc = "Swap currently wielded weapon, shield or spell-rune."
aliases = (
"s",
"swap",
"draw",
"swap weapon",
"draw weapon",
"swap rune",
"draw rune",
"swap spell",
"draw spell",
)
help_text = (
"Draw a new weapon or spell-rune from your inventory, replacing your current loadout"
)
next_menu_node = "node_select_wield_from_inventory"
def use(self, _, item, *args, **kwargs):
# this will make use of the item
self.combatant.equipment.use(item)
class CombatActionFlee(CombatAction):
@ -451,13 +493,17 @@ class CombatActionBlock(CombatAction):
attack_type = Ability.DEX
defense_type = Ability.DEX
def use(self, combatant, fleeing_target, *args, **kwargs):
def use(self, fleeing_target, *args, **kwargs):
advantage = bool(self.advantage_matrix[combatant].pop(fleeing_target, False))
disadvantage = bool(self.disadvantage_matrix[combatant].pop(fleeing_target, False))
advantage = bool(
self.combathandler.advantage_matrix[self.combatant].pop(fleeing_target, False)
)
disadvantage = bool(
self.combathandler.disadvantage_matrix[self.combatant].pop(fleeing_target, False)
)
is_success, _, txt = rules.dice.opposed_saving_throw(
combatant,
self.combatant,
fleeing_target,
attack_type=self.attack_type,
defense_type=self.defense_type,
@ -468,60 +514,12 @@ class CombatActionBlock(CombatAction):
if is_success:
# managed to stop the target from fleeing/disengaging
self.combatant.unflee(fleeing_target)
self.combathandler.unflee(fleeing_target)
self.msg("$You() blocks the retreat of $You({fleeing_target.key})")
else:
self.msg("$You({fleeing_target.key}) dodges away from you $You()!")
class CombatActionSwapWieldedWeaponOrSpell(CombatAction):
"""
Swap Wielded weapon or spell.
"""
key = "Swap weapon/rune/shield"
desc = "Swap currently wielded weapon, shield or spell-rune."
aliases = (
"s",
"swap",
"draw",
"swap weapon",
"draw weapon",
"swap rune",
"draw rune",
"swap spell",
"draw spell",
)
help_text = (
"Draw a new weapon or spell-rune from your inventory, replacing your current loadout"
)
next_menu_node = "node_select_wield_from_inventory"
def use(self, combatant, item, *args, **kwargs):
# this will make use of the item
combatant.inventory.use(item)
class CombatActionUseItem(CombatAction):
"""
Use an item from inventory.
"""
key = "Use an item from backpack"
desc = "Use an item from your inventory."
aliases = ("u", "use", "use item")
help_text = "Choose an item from your inventory to use."
next_menu_node = "node_select_use_item_from_inventory"
def use(self, combatant, item, *args, **kwargs):
item.use(combatant, *args, **kwargs)
self.msg("$You() $conj(use) an item.")
class CombatActionDoNothing(CombatAction):
"""
Do nothing this turn.
@ -635,19 +633,6 @@ class EvAdventureCombatHandler(DefaultScript):
combathandler=self, # makes this available as combatant.ndb._evmenu.combathandler
)
def _reset_menu(self):
"""
Move menu to the action-selection node.
"""
def _update_turn_stats(self, combatant, message):
"""
Store combat messages to display at the end of turn.
"""
self.turn_stats[combatant].append(message)
def _warn_time(self, time_remaining):
"""
Send a warning message when time is about to run out.
@ -693,6 +678,9 @@ class EvAdventureCombatHandler(DefaultScript):
f"|y__________________ turn resolution (turn {self.turn}) ____________________|n\n"
)
# store those in the process of fleeing
already_fleeing = self.fleeing_combatants[:]
# do all actions
for combatant in self.combatants:
# read the current action type selected by the player
@ -710,39 +698,26 @@ class EvAdventureCombatHandler(DefaultScript):
"Please report the problem to an admin."
)
logger.log_trace()
raise
# handle disengaging combatants
to_remove = []
for combatant in self.combatants:
# check disengaging combatants (these are combatants that managed
# not get their escape blocked last turn
if combatant in self.fleeing_combatants:
# see if fleeing characters managed to do two flee actions in a row.
if (combatant in self.fleeing_combatants) and (combatant in already_fleeing):
self.fleeing_combatants.remove(combatant)
to_remove.append(combatant)
if combatant.hp <= 0:
# check characters that are beaten down.
# characters roll on the death table here, npcs usually just die
combatant.at_defeat()
# tell everyone
self.msg(combatant.defeat_message(attacker, dmg), combatant=combatant)
if defender.hp > 0:
# death roll didn't kill them - they are weakened, but with hp
self.msg(
"You are alive, but out of the fight. If you want to press your luck, "
"you need to rejoin the combat.",
combatant=combatant,
broadcast=False,
)
defender.at_defeat() # note - NPC monsters may still 'die' here
else:
# outright killed
defender.at_death()
# no matter the result, the combatant is out
to_remove.append(combatant)
if combatant.hp <= 0:
# if character still < 0 after at_defeat, it means they are dead.
# force-remove from combat.
to_remove.append(combatant)
for combatant in to_remove:
# for clarity, we remove here rather than modifying the combatant list
@ -1050,7 +1025,7 @@ def node_select_wield_from_inventory(caller, raw_string, **kwargs):
"""
combat = caller.ndb._evmenu.combathandler
loadout = caller.inventory.display_loadout()
loadout = caller.equipment.display_loadout()
text = (
f"{loadout}\nSelect weapon, spell or shield to draw. It will swap out "
"anything already in the same hand (you can't change armor or helmet in combat)."
@ -1058,7 +1033,7 @@ def node_select_wield_from_inventory(caller, raw_string, **kwargs):
# get a list of all suitable weapons/spells/shields
options = []
for obj in caller.inventory.get_wieldable_objects_from_backpack():
for obj in caller.equipment.get_wieldable_objects_from_backpack():
if obj.quality <= 0:
# object is broken
options.append(

View file

@ -30,7 +30,7 @@ class EvAdventureNPC(LivingMixin, DefaultCharacter):
"""
hit_dice = AttributeProperty(default=1)
armor = AttributeProperty(default=11)
armor = AttributeProperty(default=1) # +10 to get armor defense
morale = AttributeProperty(default=9)
hp = AttributeProperty(default=8)
@ -92,8 +92,15 @@ class EvAdventureQuestGiver(EvAdventureNPC):
"""
class EvadventureMob(EvAdventureNPC):
class EvAdventureMob(EvAdventureNPC):
"""
Mob (mobile) NPC; this is usually an enemy.
"""
def at_defeat(self):
"""
Mobs die right away when defeated, no death-table rolls.
"""
self.at_death()

View file

@ -31,6 +31,54 @@ class EvAdventureObject(DefaultObject):
quality = AttributeProperty(1)
value = AttributeProperty(0)
help_text = AttributeProperty("")
def get_help(self):
"""
Get help text for the item.
Returns:
str: The help text, by default taken from the `.help_text` property.
"""
return self.help_text
def at_pre_use(self, user, *args, **kwargs):
"""
Called before this item is used.
Args:
user (Object): The one using the item.
*args, **kwargs: Optional arguments.
Return:
bool: False to stop usage.
"""
return self.uses > 0
def at_use(self, user, *args, **kwargs):
"""
Called when this item is used.
Args:
user (Object): The one using the item.
*args, **kwargs: Optional arguments.
"""
pass
def at_post_use(self, user, *args, **kwargs):
"""
Called after this item was used.
Args:
user (Object): The one using the item.
*args, **kwargs: Optional arguments.
"""
self.uses -= 1
class EvAdventureObjectFiller(EvAdventureObject):
"""
@ -59,7 +107,7 @@ class EvAdventureConsumable(EvAdventureObject):
size = AttributeProperty(0.25)
uses = AttributeProperty(1)
def use(self, user, *args, **kwargs):
def at_use(self, user, *args, **kwargs):
"""
Consume a 'use' of this item. Once it reaches 0 uses, it should normally
not be usable anymore and probably be deleted.
@ -71,6 +119,20 @@ class EvAdventureConsumable(EvAdventureObject):
"""
pass
def at_post_use(self, user, *args, **kwargs):
"""
Called after this item was used.
Args:
user (Object): The one using the item.
*args, **kwargs: Optional arguments.
"""
self.uses -= 1
if self.uses <= 0:
user.msg(f"{self.key} was used up.")
self.delete()
class EvAdventureWeapon(EvAdventureObject):
"""
@ -98,6 +160,9 @@ class WeaponEmptyHand:
damage_roll = "1d4"
quality = 100000 # let's assume fists are always available ...
def __repr__(self):
return "<WeaponEmptyHand>"
class EvAdventureRunestone(EvAdventureWeapon):
"""

View file

@ -200,8 +200,9 @@ class EvAdventureRollEngine:
Advantage and disadvantage cancel each other out.
"""
# what is stored on the character/npc is the bonus; we add 10 to get the defense target
defender_defense = getattr(defender, defense_type.value, 1) + 10
defender_defense = getattr(defender, defense_type.value, 1)
result, quality, txt = self.saving_throw(
attacker,
bonus_type=attack_type,

View file

@ -3,40 +3,63 @@ Test EvAdventure combat.
"""
from unittest.mock import patch, MagicMock
from evennia.utils.test_resources import BaseEvenniaTest
from unittest.mock import MagicMock, patch
from anything import Something
from evennia.utils import create
from .mixins import EvAdventureMixin
from evennia.utils.test_resources import BaseEvenniaTest
from .. import combat_turnbased
from ..characters import EvAdventureCharacter
from ..enums import WieldLocation
from ..npcs import EvAdventureMob
from ..objects import (
EvAdventureConsumable,
EvAdventureRunestone,
EvAdventureWeapon,
WeaponEmptyHand,
)
from ..rooms import EvAdventureRoom
from .mixins import EvAdventureMixin
class EvAdventureTurnbasedCombatHandlerTest(EvAdventureMixin, BaseEvenniaTest):
"""
Test the turn-based combat-handler implementation.
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_turnbased"
".EvAdventureCombatHandler.interval",
new=-1,
)
@patch(
"evennia.contrib.tutorials.evadventure.combat_turnbased.delay",
new=MagicMock(return_value=None),
)
def setUp(self):
super().setUp()
self.combatant = self.character
self.target = create.create_object(EvAdventureCharacter, key="testchar2")
self.target = create.create_object(
EvAdventureMob, key="testmonster", location=self.location
)
# this already starts turn 1
self.combathandler = combat_turnbased.join_combat(self.combatant, self.target)
def tearDown(self):
self.combathandler.delete()
self.target.delete()
def test_remove_combatant(self):
self.combathandler.remove_combatant(self.character)
self.assertTrue(bool(self.combatant.db.turnbased_combathandler))
self.combathandler.remove_combatant(self.combatant)
self.assertFalse(self.combatant in self.combathandler.combatants)
self.assertFalse(bool(self.combatant.db.turnbased_combathandler))
def test_start_turn(self):
self.combathandler._start_turn()
@ -47,6 +70,52 @@ class EvAdventureTurnbasedCombatHandlerTest(EvAdventureMixin, BaseEvenniaTest):
def test_end_of_turn__empty(self):
self.combathandler._end_turn()
def test_add_combatant(self):
self.combathandler._init_menu = MagicMock()
combatant3 = create.create_object(EvAdventureCharacter, key="testcharacter3")
self.combathandler.add_combatant(combatant3)
self.assertTrue(combatant3 in self.combathandler.combatants)
self.combathandler._init_menu.assert_called_once()
def test_start_combat(self):
self.combathandler._start_turn = MagicMock()
self.combathandler.start = MagicMock()
self.combathandler.start_combat()
self.combathandler._start_turn.assert_called_once()
self.combathandler.start.assert_called_once()
def test_combat_summary(self):
result = self.combathandler.get_combat_summary(self.combatant)
self.assertTrue("You (4 / 4 health)" in result)
self.assertTrue("testmonster" in result)
def test_msg(self):
self.location.msg_contents = MagicMock()
self.combathandler.msg("You hurt the target", combatant=self.combatant)
self.location.msg_contents.assert_called_with(
"You hurt the target",
from_obj=self.combatant,
exclude=[],
mapping={"testchar": self.combatant, "testmonster": self.target},
)
def test_gain_advantage(self):
self.combathandler.gain_advantage(self.combatant, self.target)
self.assertTrue(bool(self.combathandler.advantage_matrix[self.combatant][self.target]))
def test_gain_disadvantage(self):
self.combathandler.gain_disadvantage(self.combatant, self.target)
self.assertTrue(bool(self.combathandler.disadvantage_matrix[self.combatant][self.target]))
def test_flee(self):
self.combathandler.flee(self.combatant)
self.assertTrue(self.combatant in self.combathandler.fleeing_combatants)
def test_unflee(self):
self.combathandler.unflee(self.combatant)
self.assertFalse(self.combatant in self.combathandler.fleeing_combatants)
def test_register_and_run_action(self):
action_class = combat_turnbased.CombatActionAttack
action = self.combathandler.combatant_actions[self.combatant][action_class.key]
@ -60,10 +129,178 @@ class EvAdventureTurnbasedCombatHandlerTest(EvAdventureMixin, BaseEvenniaTest):
self.combathandler._end_turn()
action.use.assert_called_once()
def test_get_available_actions(self):
result = self.combathandler.get_available_actions(self.combatant)
self.assertTrue(len(result), 7)
class EvAdventureTurnbasedCombatActionTest(EvAdventureMixin, BaseEvenniaTest):
"""
Test actions in turn_based combat.
"""
@patch(
"evennia.contrib.tutorials.evadventure.combat_turnbased"
".EvAdventureCombatHandler.interval",
new=-1,
)
@patch(
"evennia.contrib.tutorials.evadventure.combat_turnbased.delay",
new=MagicMock(return_value=None),
)
def setUp(self):
super().setUp()
self.combatant = self.character
self.combatant2 = create.create_object(EvAdventureCharacter, key="testcharacter2")
self.target = create.create_object(EvAdventureMob, key="testmonster")
self.target.hp = 4
# this already starts turn 1
self.combathandler = combat_turnbased.join_combat(self.combatant, self.target)
def _run_action(self, action, *args, **kwargs):
self.combathandler.register_action(self.combatant, action.key, *args, **kwargs)
self.combathandler._end_turn()
def test_do_nothing(self):
self.combathandler.msg = MagicMock()
self._run_action(combat_turnbased.CombatActionDoNothing, None)
self.combathandler.msg.assert_called()
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
def test_attack(self, mock_randint):
mock_randint.return_value = 8
def test_attack__miss(self, mock_randint):
mock_randint.return_value = 8 # target has default armor 11, so 8+1 str will miss
self._run_action(combat_turnbased.CombatActionAttack, self.target)
self.assertEqual(self.target.hp, 4)
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
def test_attack__success__still_alive(self, mock_randint):
mock_randint.return_value = 11 # 11 + 1 str will hit beat armor 11
# make sure target survives
self.target.hp = 20
self._run_action(combat_turnbased.CombatActionAttack, self.target)
self.assertEqual(self.target.hp, 9)
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
def test_attack__success__kill(self, mock_randint):
mock_randint.return_value = 11 # 11 + 1 str will hit beat armor 11
self._run_action(combat_turnbased.CombatActionAttack, self.target)
self.assertEqual(self.target.hp, -7)
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
def test_stunt_fail(self, mock_randint):
mock_randint.return_value = 8 # fails 8+1 dex vs DEX 11 defence
self._run_action(combat_turnbased.CombatActionStunt, self.target)
self.assertEqual(self.combathandler.advantage_matrix[self.combatant], {})
self.assertEqual(self.combathandler.disadvantage_matrix[self.combatant], {})
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
def test_stunt_advantage__success(self, mock_randint):
mock_randint.return_value = 11 # 11+1 dex vs DEX 11 defence is success
self._run_action(combat_turnbased.CombatActionStunt, self.target)
self.assertEqual(
bool(self.combathandler.advantage_matrix[self.combatant][self.target]), True
)
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
def test_stunt_disadvantage__success(self, mock_randint):
mock_randint.return_value = 11 # 11+1 dex vs DEX 11 defence is success
action = combat_turnbased.CombatActionStunt
action.give_advantage = False
self._run_action(
action,
self.target,
)
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)]
)
self.assertEqual(item.uses, 2)
self._run_action(combat_turnbased.CombatActionUseItem, item, self.combatant)
self.assertEqual(item.uses, 1)
self._run_action(combat_turnbased.CombatActionUseItem, item, self.combatant)
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
self._run_action(combat_turnbased.CombatActionSwapWieldedWeaponOrSpell, None, sword)
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)
self._run_action(combat_turnbased.CombatActionSwapWieldedWeaponOrSpell, None, zweihander)
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)
self._run_action(combat_turnbased.CombatActionSwapWieldedWeaponOrSpell, None, runestone)
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
self._run_action(combat_turnbased.CombatActionSwapWieldedWeaponOrSpell, None, sword)
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.
"""
# first flee records the fleeing state
self._run_action(combat_turnbased.CombatActionFlee, None)
self.assertTrue(self.combatant in self.combathandler.fleeing_combatants)
# second flee should remove combatant
self._run_action(combat_turnbased.CombatActionFlee, None)
self.assertTrue(self.combatant not in self.combathandler.combatants)
@patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
def test_flee__blocked(self, mock_randint):
""" """
mock_randint.return_value = 11 # means block will succeed
self._run_action(combat_turnbased.CombatActionFlee, None)
self.assertTrue(self.combatant in self.combathandler.fleeing_combatants)
# other combatant blocks in the same turn
self.combathandler.register_action(
combat_turnbased.CombatActionAttack.key, self.combatant, self.target
self.combatant, combat_turnbased.CombatActionFlee.key, None
)
self.combathandler.register_action(
self.target, combat_turnbased.CombatActionBlock.key, self.combatant
)
self.combathandler._end_turn()
# the fleeing combatant should remain now
self.assertTrue(self.combatant not in self.combathandler.fleeing_combatants)
self.assertTrue(self.combatant in self.combathandler.combatants)