evennia/evennia/contrib/tutorials/evadventure/tests.py
2022-05-29 13:40:42 +02:00

466 lines
18 KiB
Python

"""
Tests for EvAdventure.
"""
from parameterized import parameterized
from unittest.mock import patch, MagicMock, call
from evennia.utils import create
from evennia.utils.test_resources import BaseEvenniaTest
from .characters import EvAdventureCharacter, EquipmentHandler, EquipmentError
from .objects import EvAdventureObject
from . import enums
from . import combat_turnbased
from . import rules
from . import random_tables
class EvAdventureMixin:
def setUp(self):
super().setUp()
self.character = create.create_object(EvAdventureCharacter, key="testchar")
self.helmet = create.create_object(
EvAdventureObject, key="helmet",
attributes=[("inventory_use_slot", enums.WieldLocation.HEAD)])
self.shield = create.create_object(
EvAdventureObject, key="shield",
attributes=[("inventory_use_slot", enums.WieldLocation.SHIELD_HAND)])
self.armor = create.create_object(
EvAdventureObject, key="armor",
attributes=[("inventory_use_slot", enums.WieldLocation.BODY)])
self.weapon = create.create_object(
EvAdventureObject, key="weapon",
attributes=[("inventory_use_slot", enums.WieldLocation.WEAPON_HAND)])
self.big_weapon = create.create_object(
EvAdventureObject, key="big_weapon",
attributes=[("inventory_use_slot", enums.WieldLocation.TWO_HANDS)])
self.item = create.create_object(EvAdventureObject, key="backpack item")
class EvAdventureEquipmentTest(EvAdventureMixin, BaseEvenniaTest):
"""
Test the equipment mechanism.
"""
def _get_empty_slots(self):
return {
enums.WieldLocation.BACKPACK: [],
enums.WieldLocation.WEAPON_HAND: None,
enums.WieldLocation.SHIELD_HAND: None,
enums.WieldLocation.TWO_HANDS: None,
enums.WieldLocation.BODY: None,
enums.WieldLocation.HEAD: None,
}
def test_equipmenthandler_max_slots(self):
self.assertEqual(self.character.equipment.max_slots, 11)
@parameterized.expand([
# size, pass_validation?
(1, True),
(2, True),
(11, True),
(12, False),
(20, False),
(25, False)
])
def test_validate_slot_usage(self, size, is_ok):
obj = MagicMock()
obj.size = size
if is_ok:
self.assertTrue(self.character.equipment.validate_slot_usage(obj))
else:
with self.assertRaises(EquipmentError):
self.character.equipment.validate_slot_usage(obj)
@parameterized.expand([
# item, where
("helmet", enums.WieldLocation.HEAD),
("shield", enums.WieldLocation.SHIELD_HAND),
("armor", enums.WieldLocation.BODY),
("weapon", enums.WieldLocation.WEAPON_HAND),
("big_weapon", enums.WieldLocation.TWO_HANDS),
("item", enums.WieldLocation.BACKPACK),
])
def test_use(self, itemname, where):
self.assertEqual(self.character.equipment.slots, self._get_empty_slots())
obj = getattr(self, itemname)
self.character.equipment.use(obj)
# check that item ended up in the right place
if where is enums.WieldLocation.BACKPACK:
self.assertTrue(obj in self.character.equipment.slots[where])
else:
self.assertEqual(self.character.equipment.slots[where], obj)
def test_store(self):
self.character.equipment.store(self.weapon)
self.assertEqual(self.character.equipment.slots[enums.WieldLocation.WEAPON_HAND], None)
self.assertTrue(
self.weapon in self.character.equipment.slots[enums.WieldLocation.BACKPACK])
def test_two_handed_exclusive(self):
"""Two-handed weapons can't be used together with weapon+shield"""
self.character.equipment.use(self.big_weapon)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.TWO_HANDS], self.big_weapon)
# equipping sword or shield removes two-hander
self.character.equipment.use(self.shield)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.SHIELD_HAND], self.shield)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.TWO_HANDS], None)
self.character.equipment.use(self.weapon)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.WEAPON_HAND], self.weapon)
# the two-hander removes the two weapons
self.character.equipment.use(self.big_weapon)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.TWO_HANDS], self.big_weapon)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.SHIELD_HAND], None)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.WEAPON_HAND], None)
def test_remove__with_obj(self):
self.character.equipment.use(self.shield)
self.character.equipment.use(self.item)
self.character.equipment.store(self.weapon)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.SHIELD_HAND], self.shield)
self.assertEqual(self.character.equipment.slots[enums.WieldLocation.BACKPACK],
[self.item, self.weapon])
self.assertEqual(self.character.equipment.remove(self.shield), [self.shield])
self.assertEqual(self.character.equipment.remove(self.item), [self.item])
self.assertEqual(self.character.equipment.slots[enums.WieldLocation.SHIELD_HAND], None)
self.assertEqual(self.character.equipment.slots[enums.WieldLocation.BACKPACK],
[self.weapon])
def test_remove__with_slot(self):
self.character.equipment.use(self.shield)
self.character.equipment.use(self.item)
self.character.equipment.store(self.helmet)
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.SHIELD_HAND], self.shield)
self.assertEqual(self.character.equipment.slots[enums.WieldLocation.BACKPACK],
[self.item, self.helmet])
self.assertEqual(self.character.equipment.remove(enums.WieldLocation.SHIELD_HAND),
[self.shield])
self.assertEqual(self.character.equipment.remove(enums.WieldLocation.BACKPACK),
[self.item, self.helmet])
self.assertEqual(
self.character.equipment.slots[enums.WieldLocation.SHIELD_HAND], None)
self.assertEqual(self.character.equipment.slots[enums.WieldLocation.BACKPACK], [])
class EvAdventureTurnbasedCombatHandlerTest(EvAdventureMixin, BaseEvenniaTest):
"""
Test the turn-based combat-handler implementation.
"""
def setUp(self):
super().setUp()
self.combathandler = combat_turnbased.EvAdventureCombatHandler()
self.combathandler.add_combatant(self.character)
def test_remove_combatant(self):
self.combathandler.remove_combatant(self.character)
class EvAdventureRollEngineTest(BaseEvenniaTest):
"""
Test the roll engine in the rules module. This is the core of any RPG.
"""
def setUp(self):
super().setUp()
self.roll_engine = rules.EvAdventureRollEngine()
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def test_roll(self, mock_randint):
mock_randint.return_value = 8
self.assertEqual(self.roll_engine.roll("1d6"), 8)
mock_randint.assert_called_with(1, 6)
self.assertEqual(self.roll_engine.roll("2d8"), 2 * 8)
mock_randint.assert_called_with(1, 8)
self.assertEqual(self.roll_engine.roll("4d12"), 4 * 8)
mock_randint.assert_called_with(1, 12)
self.assertEqual(self.roll_engine.roll("8d100"), 8 * 8)
mock_randint.assert_called_with(1, 100)
def test_roll_limits(self):
with self.assertRaises(TypeError):
self.roll_engine.roll('100d6', max_number=10) # too many die
with self.assertRaises(TypeError):
self.roll_engine.roll('100') # no d
with self.assertRaises(TypeError):
self.roll_engine.roll('dummy') # non-numerical
with self.assertRaises(TypeError):
self.roll_engine.roll('Ad4') # non-numerical
with self.assertRaises(TypeError):
self.roll_engine.roll('1d10000') # limit is d1000
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def test_roll_with_advantage_disadvantage(self, mock_randint):
mock_randint.return_value = 9
# no advantage/disadvantage
self.assertEqual(self.roll_engine.roll_with_advantage_or_disadvantage(), 9)
mock_randint.assert_called_once()
mock_randint.reset_mock()
# cancel each other out
self.assertEqual(
self.roll_engine.roll_with_advantage_or_disadvantage(
disadvantage=True, advantage=True), 9)
mock_randint.assert_called_once()
mock_randint.reset_mock()
# run with advantage/disadvantage
self.assertEqual(
self.roll_engine.roll_with_advantage_or_disadvantage(advantage=True), 9)
mock_randint.assert_has_calls([call(1, 20), call(1, 20)])
mock_randint.reset_mock()
self.assertEqual(
self.roll_engine.roll_with_advantage_or_disadvantage(disadvantage=True), 9)
mock_randint.assert_has_calls([call(1, 20), call(1, 20)])
mock_randint.reset_mock()
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def test_saving_throw(self, mock_randint):
mock_randint.return_value = 8
character = MagicMock()
character.strength = 2
character.dexterity = 1
self.assertEqual(
self.roll_engine.saving_throw(character, bonus_type=enums.Ability.STR),
(False, None))
self.assertEqual(
self.roll_engine.saving_throw(character, bonus_type=enums.Ability.DEX, modifier=1),
(False, None))
self.assertEqual(
self.roll_engine.saving_throw(
character,
advantage=True,
bonus_type=enums.Ability.DEX, modifier=6),
(False, None))
self.assertEqual(
self.roll_engine.saving_throw(
character,
disadvantage=True,
bonus_type=enums.Ability.DEX, modifier=7),
(True, None))
mock_randint.return_value = 1
self.assertEqual(
self.roll_engine.saving_throw(
character,
disadvantage=True,
bonus_type=enums.Ability.STR, modifier=2),
(False, enums.Ability.CRITICAL_FAILURE))
mock_randint.return_value = 20
self.assertEqual(
self.roll_engine.saving_throw(
character,
disadvantage=True,
bonus_type=enums.Ability.STR, modifier=2),
(True, enums.Ability.CRITICAL_SUCCESS))
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def test_opposed_saving_throw(self, mock_randint):
mock_randint.return_value = 10
attacker, defender = MagicMock(), MagicMock()
attacker.strength = 1
defender.armor = 2
self.assertEqual(
self.roll_engine.opposed_saving_throw(
attacker, defender,
attack_type=enums.Ability.STR, defense_type=enums.Ability.ARMOR
),
(False, None)
)
self.assertEqual(
self.roll_engine.opposed_saving_throw(
attacker, defender,
attack_type=enums.Ability.STR, defense_type=enums.Ability.ARMOR,
modifier=2
),
(True, None)
)
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def test_roll_random_table(self, mock_randint):
mock_randint.return_value = 10
self.assertEqual(
self.roll_engine.roll_random_table(
"1d20", random_tables.character_generation['physique']),
"scrawny"
)
self.assertEqual(
self.roll_engine.roll_random_table(
"1d20", random_tables.character_generation['vice']),
"irascible"
)
self.assertEqual(
self.roll_engine.roll_random_table(
"1d20", random_tables.character_generation['alignment']),
"neutrality"
)
self.assertEqual(
self.roll_engine.roll_random_table(
"1d20", random_tables.character_generation['helmets and shields']),
"no helmet or shield"
)
# testing faulty rolls outside of the table ranges
mock_randint.return_value = 25
self.assertEqual(
self.roll_engine.roll_random_table(
"1d20", random_tables.character_generation['helmets and shields']),
"helmet and shield"
)
mock_randint.return_value = -10
self.assertEqual(
self.roll_engine.roll_random_table(
"1d20", random_tables.character_generation['helmets and shields']),
"no helmet or shield"
)
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def test_morale_check(self, mock_randint):
defender = MagicMock()
defender.morale = 12
mock_randint.return_value = 7 # 2d6 is rolled, so this will become 14
self.assertEqual(self.roll_engine.morale_check(defender), False)
mock_randint.return_value = 3 # 2d6 is rolled, so this will become 6
self.assertEqual(self.roll_engine.morale_check(defender), True)
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def test_heal_from_rest(self, mock_randint):
character = MagicMock()
character.hp_max = 8
character.hp = 1
character.constitution = 1
mock_randint.return_value = 5
self.roll_engine.heal_from_rest(character)
self.assertEqual(character.hp, 7) # hp + 1d8 + consititution bonus
mock_randint.assert_called_with(1, 8) # 1d8
self.roll_engine.heal_from_rest(character)
self.assertEqual(character.hp, 8) # can't have more than max hp
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def test_roll_death(self, mock_randint):
character = MagicMock()
character.strength = 13
character.hp = 0
character.hp_max = 8
# death
mock_randint.return_value = 1
self.roll_engine.roll_death(character)
character.handle_death.assert_called()
# strength loss
mock_randint.return_value = 3
self.roll_engine.roll_death(character)
self.assertEqual(character.strength, 10)
class EvAdventureCharacterGenerationTest(BaseEvenniaTest):
"""
Test the Character generator tracing object in the rule engine.
"""
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
def setUp(self, mock_randint):
super().setUp()
mock_randint.return_value = 10
self.chargen = rules.EvAdventureCharacterGeneration()
def test_base_chargen(self):
self.assertEqual(self.chargen.strength, 2)
self.assertEqual(self.chargen.physique, "scrawny")
self.assertEqual(self.chargen.skin, "pockmarked")
self.assertEqual(self.chargen.hair, "greased")
self.assertEqual(self.chargen.clothing, "stained")
self.assertEqual(self.chargen.misfortune, "exiled")
self.assertEqual(self.chargen.armor, "gambeson")
self.assertEqual(self.chargen.shield, "shield")
self.assertEqual(self.chargen.backpack, ['ration', 'ration', 'waterskin',
'waterskin', 'drill', 'twine'])
def test_build_desc(self):
self.assertEqual(
self.chargen.build_desc(),
"Herbalist. Wears stained clothes, and has hoarse speech. Has a scrawny physique, "
"a broken face, pockmarked skin and greased hair. Is honest, but irascible. "
"Has been exiled in the past. Favors neutrality."
)
@parameterized.expand([
# source, target, value, new_source_val, new_target_val
(enums.Ability.CON, enums.Ability.STR, 1, 1, 3),
(enums.Ability.INT, enums.Ability.DEX, 1, 1, 3),
(enums.Ability.CHA, enums.Ability.CON, 1, 1, 3),
(enums.Ability.STR, enums.Ability.WIS, 1, 1, 3),
(enums.Ability.WIS, enums.Ability.CHA, 1, 1, 3),
(enums.Ability.DEX, enums.Ability.DEX, 1, 2, 2),
])
def test_adjust_attribute(self, source, target, value, new_source_val, new_target_val):
self.chargen.adjust_attribute(source, target, value)
self.assertEqual(
getattr(self.chargen, source.value), new_source_val, f"{source}->{target}")
self.assertEqual(
getattr(self.chargen, target.value), new_target_val, f"{source}->{target}")
def test_adjust_consecutive(self):
# gradually shift all to STR (starts at 2)
self.chargen.adjust_attribute(enums.Ability.CON, enums.Ability.STR, 1)
self.chargen.adjust_attribute(enums.Ability.CHA, enums.Ability.STR, 1)
self.chargen.adjust_attribute(enums.Ability.DEX, enums.Ability.STR, 1)
self.chargen.adjust_attribute(enums.Ability.WIS, enums.Ability.STR, 1)
self.assertEqual(self.chargen.constitution, 1)
self.assertEqual(self.chargen.strength, 6)
# max is 6
with self.assertRaises(ValueError):
self.chargen.adjust_attribute(enums.Ability.INT, enums.Ability.STR, 1)
# minimum is 1
with self.assertRaises(ValueError):
self.chargen.adjust_attribute(enums.Ability.DEX, enums.Ability.WIS, 1)
# move all from str to wis
self.chargen.adjust_attribute(enums.Ability.STR, enums.Ability.WIS, 5)
self.assertEqual(self.chargen.strength, 1)
self.assertEqual(self.chargen.wisdom, 6)
def test_apply(self):
character = MagicMock()
self.chargen.apply(character)
self.assertTrue(character.db.desc.startswith("Herbalist"))
self.assertEqual(character.armor, "gambeson")
character.equipment.store.assert_called()