mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Continue making unit tests for combathandler
This commit is contained in:
parent
3f96004e99
commit
b0d6af9cc3
3 changed files with 224 additions and 48 deletions
|
|
@ -492,10 +492,15 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
# how many actions can be queued at a time (per combatant)
|
||||
max_action_queue_size = 1
|
||||
|
||||
# available actions
|
||||
# available actions in combat
|
||||
action_classes = {
|
||||
"nothing": CombatActionDoNothing,
|
||||
"attack": CombatActionAttack,
|
||||
"stunt": CombatActionStunt,
|
||||
"use": CombatActionUseItem,
|
||||
"wield": CombatActionWield,
|
||||
"flee": CombatActionFlee,
|
||||
"hinder": CombatActionHinder,
|
||||
}
|
||||
|
||||
# fallback action if not selecting anything
|
||||
|
|
@ -509,7 +514,7 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
|
||||
# who is involved in combat, and their action queue,
|
||||
# as {combatant: [actiondict, actiondict,...]}
|
||||
combatants = AttributeProperty(defaultdict(list))
|
||||
combatants = AttributeProperty(defaultdict(deque))
|
||||
|
||||
advantage_matrix = AttributeProperty(defaultdict(dict))
|
||||
disadvantage_matrix = AttributeProperty(defaultdict(dict))
|
||||
|
|
@ -549,21 +554,22 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
mapping={locobj.key: locobj for locobj in location_objs},
|
||||
)
|
||||
|
||||
def add_combatant(self, combatant):
|
||||
def add_combatants(self, *combatants):
|
||||
"""
|
||||
Add a new combatant to the battle.
|
||||
|
||||
Args:
|
||||
combatant (EvAdventureCharacter, EvAdventureNPC): A combatant to add to
|
||||
*combatants (EvAdventureCharacter, EvAdventureNPC): Any number of combatants to add to
|
||||
the combat.
|
||||
Returns:
|
||||
bool: True if the combatant was added, False otherwise (that is, they
|
||||
were already added from before).
|
||||
|
||||
"""
|
||||
if combatant not in self.combatants:
|
||||
self.combatants[combatant] = deque((), self.max_action_queue_size)
|
||||
return True
|
||||
for combatant in combatants:
|
||||
if combatant not in self.combatants:
|
||||
self.combatants[combatant] = deque((), self.max_action_queue_size)
|
||||
return True
|
||||
|
||||
def remove_combatant(self, combatant):
|
||||
"""
|
||||
|
|
@ -656,14 +662,14 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
queue will be rotated to the left and be `[b, c, a]` (so next time, `b` will be used).
|
||||
|
||||
"""
|
||||
queue = self.combatants[combatant]
|
||||
action_dict = queue[0] if queue else COMBAT_ACTION_DICT_DONOTHING
|
||||
action_queue = self.combatants[combatant]
|
||||
action_dict = action_queue[0] if action_queue else COMBAT_ACTION_DICT_DONOTHING
|
||||
# rotate the queue to the left so that the first element is now the last one
|
||||
queue.rotate(-1)
|
||||
action_queue.rotate(-1)
|
||||
|
||||
# use the action-dict to select and create an action from an action class
|
||||
action_class = self.action_classes[action_dict["key"]]
|
||||
action = action_class(combatant, action_dict)
|
||||
action = action_class(self, combatant, action_dict)
|
||||
|
||||
action.execute()
|
||||
|
||||
|
|
@ -674,7 +680,8 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
"""
|
||||
self.turn += 1
|
||||
# random turn order
|
||||
combatants = random.shuffle(list(self.combatants.keys()))
|
||||
combatants = list(self.combatants.keys())
|
||||
random.shuffle(combatants) # shuffles in place
|
||||
|
||||
# do everyone's next queued combat action
|
||||
for combatant in combatants:
|
||||
|
|
@ -704,11 +711,11 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
allies, enemies = (), ()
|
||||
else:
|
||||
# grab a random survivor and check of they have any living enemies.
|
||||
surviving_combatant = random.choice(list(self.combatant.keys()))
|
||||
surviving_combatant = random.choice(list(self.combatants.keys()))
|
||||
allies, enemies = self.get_sides(surviving_combatant)
|
||||
|
||||
if not enemies:
|
||||
# one way or another, there are no more enemies to fight
|
||||
# if one way or another, there are no more enemies to fight
|
||||
still_standing = list_to_string(f"$You({comb.key})" for comb in allies)
|
||||
knocked_out = list_to_string(
|
||||
f"$You({comb.key})" for comb in self.defeated_combatants if comb.hp > 0
|
||||
|
|
@ -729,6 +736,37 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
self.stop_combat()
|
||||
|
||||
|
||||
def get_or_create_combathandler(combatant, combathandler_name="combathandler", combat_tick=5):
|
||||
"""
|
||||
Joins or continues combat. This is a access function that will either get the
|
||||
combathandler on the current room or create a new one.
|
||||
|
||||
Args:
|
||||
combatant (EvAdventureCharacter, EvAdventureNPC): The one to
|
||||
|
||||
Returns:
|
||||
CombatHandler: The new or created combathandler.
|
||||
|
||||
"""
|
||||
|
||||
location = combatant.location
|
||||
|
||||
if not location:
|
||||
raise CombatFailure("Cannot start combat without a location.")
|
||||
|
||||
combathandler = location.scripts.get(combathandler_name)
|
||||
if not combathandler:
|
||||
combathandler = create_script(
|
||||
EvAdventureCombatHandler,
|
||||
key=combathandler_name,
|
||||
obj=location,
|
||||
interval=combat_tick,
|
||||
persistent=True,
|
||||
)
|
||||
combathandler.add_combatants(combatant)
|
||||
return combathandler
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Tick-based fast combat (Diku-style)
|
||||
|
|
@ -770,15 +808,10 @@ class _CmdCombatBase(Command):
|
|||
|
||||
@property
|
||||
def combathandler(self):
|
||||
self.combathandler = self.caller.location.scripts.get(self.combathandler_name)
|
||||
if not self.combathandler:
|
||||
self.combathandler = create_script(
|
||||
EvAdventureCombatHandler,
|
||||
key=combathandler_name,
|
||||
obj=location,
|
||||
interval=self.combat_tick,
|
||||
persistent=True,
|
||||
)
|
||||
combathandler = getattr(self, "combathandler", None)
|
||||
if not combathandler:
|
||||
self.combathandler = combathandler = get_or_create_combathandler(self.caller)
|
||||
return combathandler
|
||||
|
||||
def parse(self):
|
||||
super().parse()
|
||||
|
|
@ -847,7 +880,7 @@ class CmdAttack(_CmdCombatBase):
|
|||
return
|
||||
|
||||
# this can be done over and over
|
||||
is_new = self.combathandler.add_combatant(self)
|
||||
is_new = self.combathandler.add_combatants(self)
|
||||
if is_new:
|
||||
# just joined combat - add the combat cmdset
|
||||
self.caller.cmdset.add(CombatCmdSet)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,23 @@ class EvAdventureConsumable(EvAdventureObject):
|
|||
self.delete()
|
||||
|
||||
|
||||
class EvAdventureWeapon(EvAdventureObject):
|
||||
"""
|
||||
Base weapon class for all EvAdventure weapons.
|
||||
|
||||
"""
|
||||
|
||||
obj_type = ObjType.WEAPON
|
||||
inventory_use_slot = AttributeProperty(WieldLocation.WEAPON_HAND)
|
||||
quality = AttributeProperty(3)
|
||||
|
||||
# what ability used to attack with this weapon
|
||||
attack_type = AttributeProperty(Ability.STR)
|
||||
# what defense stat of the enemy it must defeat
|
||||
defense_type = AttributeProperty(Ability.ARMOR)
|
||||
damage_roll = AttributeProperty("1d6")
|
||||
|
||||
|
||||
class EvAdventureThrowable(EvAdventureWeapon, EvAdventureConsumable):
|
||||
"""
|
||||
Something you can throw at an enemy to harm them once, like a knife or exploding potion/grenade.
|
||||
|
|
@ -179,23 +196,6 @@ class WeaponEmptyHand:
|
|||
return "<WeaponEmptyHand>"
|
||||
|
||||
|
||||
class EvAdventureWeapon(EvAdventureObject):
|
||||
"""
|
||||
Base weapon class for all EvAdventure weapons.
|
||||
|
||||
"""
|
||||
|
||||
obj_type = ObjType.WEAPON
|
||||
inventory_use_slot = AttributeProperty(WieldLocation.WEAPON_HAND)
|
||||
quality = AttributeProperty(3)
|
||||
|
||||
# what ability used to attack with this weapon
|
||||
attack_type = AttributeProperty(Ability.STR)
|
||||
# what defense stat of the enemy it must defeat
|
||||
defense_type = AttributeProperty(Ability.ARMOR)
|
||||
damage_roll = AttributeProperty("1d6")
|
||||
|
||||
|
||||
class EvAdventureRunestone(EvAdventureWeapon, EvAdventureConsumable):
|
||||
"""
|
||||
Base class for magic runestones. In _Knave_, every spell is represented by a rune stone
|
||||
|
|
|
|||
|
|
@ -3,20 +3,22 @@ Test EvAdventure combat.
|
|||
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
from collections import deque
|
||||
from unittest.mock import Mock, call, patch
|
||||
|
||||
from evennia.utils import create
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
|
||||
from .. import combat_turnbased
|
||||
from .. import combat_turnbased as combat
|
||||
from ..characters import EvAdventureCharacter
|
||||
from ..enums import WieldLocation
|
||||
from ..npcs import EvAdventureMob
|
||||
from ..objects import EvAdventureConsumable, EvAdventureRunestone, EvAdventureWeapon
|
||||
from ..rooms import EvAdventureRoom
|
||||
from .mixins import EvAdventureMixin
|
||||
|
||||
|
||||
class EvAdventureCombatHandlerTest(EvAdventureMixin, BaseEvenniaTest):
|
||||
class EvAdventureCombatHandlerTest(BaseEvenniaTest):
|
||||
"""
|
||||
Test methods on the turn-based combat handler
|
||||
|
||||
|
|
@ -31,13 +33,19 @@ class EvAdventureCombatHandlerTest(EvAdventureMixin, BaseEvenniaTest):
|
|||
)
|
||||
@patch(
|
||||
"evennia.contrib.tutorials.evadventure.combat_turnbased.delay",
|
||||
new=MagicMock(return_value=None),
|
||||
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.combatant = self.character
|
||||
|
||||
self.target = create.create_object(
|
||||
EvAdventureMob,
|
||||
key="testmonster",
|
||||
|
|
@ -45,8 +53,143 @@ class EvAdventureCombatHandlerTest(EvAdventureMixin, BaseEvenniaTest):
|
|||
attributes=(("is_idle", True),),
|
||||
)
|
||||
|
||||
# this already starts turn 1
|
||||
self.combathandler = combat_turnbased.join_combat(self.combatant, self.target)
|
||||
self.combathandler = combat.get_or_create_combathandler(self.combatant)
|
||||
# add target to combat
|
||||
self.combathandler.add_combatants(self.target)
|
||||
|
||||
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),
|
||||
{
|
||||
"nothing": combat.CombatActionDoNothing,
|
||||
"attack": combat.CombatActionAttack,
|
||||
"stunt": combat.CombatActionStunt,
|
||||
"use": combat.CombatActionUseItem,
|
||||
"wield": combat.CombatActionWield,
|
||||
"flee": combat.CombatActionFlee,
|
||||
"hinder": combat.CombatActionHinder,
|
||||
},
|
||||
)
|
||||
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_combatants(combatant2, 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_queue_and_execute_action(self):
|
||||
"""Queue actions and execute"""
|
||||
|
||||
donothing = {"key": "nothing"}
|
||||
|
||||
self.combathandler.queue_action(self.combatant, donothing)
|
||||
self.assertEqual(
|
||||
dict(self.combathandler.combatants),
|
||||
{self.combatant: deque([donothing]), self.target: deque()},
|
||||
)
|
||||
|
||||
mock_action = Mock()
|
||||
self.combathandler.action_classes["nothing"] = Mock(return_value=mock_action)
|
||||
|
||||
self.combathandler.execute_next_action(self.combatant)
|
||||
|
||||
self.combathandler.action_classes["nothing"].assert_called_with(
|
||||
self.combathandler, self.combatant, donothing
|
||||
)
|
||||
mock_action.execute.assert_called_once()
|
||||
|
||||
def test_execute_full_turn(self):
|
||||
"""Run a full (passive) turn"""
|
||||
|
||||
donothing = {"key": "nothing"}
|
||||
|
||||
self.combathandler.queue_action(self.combatant, donothing)
|
||||
self.combathandler.queue_action(self.target, donothing)
|
||||
|
||||
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 _get_action(self, action_dict, action_dict2={"key": "nothing"}):
|
||||
|
||||
self.combathandler.queue_action(self.combatant, action_dict)
|
||||
self.combathandler.queue_action(self.target, action_dict2)
|
||||
|
||||
action_cls1 = self.combathandler.action_classes[action_dict["key"]]
|
||||
action_cls2 = self.combathandler.action_classes[action_dict2["key"]]
|
||||
|
||||
return action_cls1, action_cls2
|
||||
|
||||
def test_action__do_nothing(self):
|
||||
"""Do nothing"""
|
||||
|
||||
actiondict = {"key": "nothing"}
|
||||
|
||||
actioncls1, actioncls2 = self._get_action(actiondict, actiondict)
|
||||
|
||||
self.assertEqual(actioncls1, actioncls2)
|
||||
|
||||
self.combathandler.execute_full_turn()
|
||||
|
||||
self.assertEqual(self.combathandler.turn, 1)
|
||||
|
||||
# @patch("evennia.contrib.tutorials.evadventure.combat_turnbased.rules.randint")
|
||||
|
||||
|
||||
# class EvAdventureTurnbasedCombatHandlerTest(EvAdventureMixin, BaseEvenniaTest):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue