diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.md index eff9ebd946..f9a92f2ebc 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.md @@ -103,7 +103,7 @@ from the _Knave_ rulebook. While we added the ability to roll on a random table ``` # in mygame/evadventure/random_tables.py -character_generation = { +chargen_tables = { "physique": [ "athletic", "brawny", "corpulent", "delicate", "gaunt", "hulking", "lanky", "ripped", "rugged", "scrawny", "short", "sinewy", "slender", "flabby", @@ -135,20 +135,19 @@ During character generation we will need an entity to store/retain the changes, ```python # in mygame/evadventure/chargen.py -from .random_tables import chargen_table +from .random_tables import chargen_tables from .rules import dice class TemporaryCharacterSheet: - def __init__(self): - self.ability_changes = 0 # how many times we tried swap abilities - def _random_ability(self): return min(dice.roll("1d6"), dice.roll("1d6"), dice.roll("1d6")) - - def generate(self): + + def __init__(self): + self.ability_changes = 0 # how many times we tried swap abilities + # name will likely be modified later - self.name = dice.roll_random_table("1d282", chargen_table["name"]) + self.name = dice.roll_random_table("1d282", chargen_tables["name"]) # base attribute values self.strength = self._random_ability() @@ -159,17 +158,17 @@ class TemporaryCharacterSheet: self.charisma = self._random_ability() # physical attributes (only for rp purposes) - physique = dice.roll_random_table("1d20", chargen_table["physique"]) - face = dice.roll_random_table("1d20", chargen_table["face"]) - skin = dice.roll_random_table("1d20", chargen_table["skin"]) - hair = dice.roll_random_table("1d20", chargen_table["hair"]) - clothing = dice.roll_random_table("1d20", chargen_table["clothing"]) - speech = dice.roll_random_table("1d20", chargen_table["speech"]) - virtue = dice.roll_random_table("1d20", chargen_table["virtue"]) - vice = dice.roll_random_table("1d20", chargen_table["vice"]) - background = dice.roll_random_table("1d20", chargen_table["background"]) - misfortune = dice.roll_random_table("1d20", chargen_table["misfortune"]) - alignment = dice.roll_random_table("1d20", chargen_table["alignment"]) + physique = dice.roll_random_table("1d20", chargen_tables["physique"]) + face = dice.roll_random_table("1d20", chargen_tables["face"]) + skin = dice.roll_random_table("1d20", chargen_tables["skin"]) + hair = dice.roll_random_table("1d20", chargen_tables["hair"]) + clothing = dice.roll_random_table("1d20", chargen_tables["clothing"]) + speech = dice.roll_random_table("1d20", chargen_tables["speech"]) + virtue = dice.roll_random_table("1d20", chargen_tables["virtue"]) + vice = dice.roll_random_table("1d20", chargen_tables["vice"]) + background = dice.roll_random_table("1d20", chargen_tables["background"]) + misfortune = dice.roll_random_table("1d20", chargen_tables["misfortune"]) + alignment = dice.roll_random_table("1d20", chargen_tables["alignment"]) self.desc = ( f"You are {physique} with a {face} face, {skin} skin, {hair} hair, {speech} speech," @@ -185,21 +184,21 @@ class TemporaryCharacterSheet: self.level = 1 # random equipment - self.armor = dice.roll_random_table("1d20", chargen_table["armor"]) + self.armor = dice.roll_random_table("1d20", chargen_tables["armor"]) - _helmet_and_shield = dice.roll_random_table("1d20", chargen_table["helmets and shields"]) + _helmet_and_shield = dice.roll_random_table("1d20", chargen_tables["helmets and shields"]) self.helmet = "helmet" if "helmet" in _helmet_and_shield else "none" self.shield = "shield" if "shield" in _helmet_and_shield else "none" - self.weapon = dice.roll_random_table("1d20", chargen_table["starting weapon"]) + self.weapon = dice.roll_random_table("1d20", chargen_tables["starting weapon"]) self.backpack = [ "ration", "ration", - dice.roll_random_table("1d20", chargen_table["dungeoning gear"]), - dice.roll_random_table("1d20", chargen_table["dungeoning gear"]), - dice.roll_random_table("1d20", chargen_table["general gear 1"]), - dice.roll_random_table("1d20", chargen_table["general gear 2"]), + dice.roll_random_table("1d20", chargen_tables["dungeoning gear"]), + dice.roll_random_table("1d20", chargen_tables["dungeoning gear"]), + dice.roll_random_table("1d20", chargen_tables["general gear 1"]), + dice.roll_random_table("1d20", chargen_tables["general gear 2"]), ] ``` @@ -367,7 +366,6 @@ def start_chargen(caller, session=None): # this generates all random components of the character tmp_character = TemporaryCharacterSheet() - tmp_character.generate() EvMenu(caller, menutree, session=session, tmp_character=tmp_character) diff --git a/evennia/contrib/game_systems/turnbattle/tests.py b/evennia/contrib/game_systems/turnbattle/tests.py index b40289ba9b..6d40587876 100644 --- a/evennia/contrib/game_systems/turnbattle/tests.py +++ b/evennia/contrib/game_systems/turnbattle/tests.py @@ -3,12 +3,13 @@ Turnbattle tests. """ -from mock import patch, MagicMock from evennia.commands.default.tests import BaseEvenniaCommandTest +from evennia.objects.objects import DefaultRoom from evennia.utils.create import create_object from evennia.utils.test_resources import BaseEvenniaTest -from evennia.objects.objects import DefaultRoom -from . import tb_basic, tb_equip, tb_range, tb_items, tb_magic +from mock import MagicMock, patch + +from . import tb_basic, tb_equip, tb_items, tb_magic, tb_range class TestTurnBattleBasicCmd(BaseEvenniaCommandTest): diff --git a/evennia/contrib/tutorials/evadventure/chargen.py b/evennia/contrib/tutorials/evadventure/chargen.py index 25667c1924..48474cadb2 100644 --- a/evennia/contrib/tutorials/evadventure/chargen.py +++ b/evennia/contrib/tutorials/evadventure/chargen.py @@ -7,7 +7,7 @@ from evennia.prototypes.spawner import spawn from evennia.utils.evmenu import EvMenu from .characters import EvAdventureCharacter -from .random_tables import chargen_table +from .random_tables import chargen_tables from .rules import dice _ABILITIES = { @@ -56,21 +56,12 @@ class TemporaryCharacterSheet: """ - def __init__(self): - # you are only allowed to tweak abilities once - self.ability_changes = 0 - def _random_ability(self): return min(dice.roll("1d6"), dice.roll("1d6"), dice.roll("1d6")) - def generate(self): - """ - Generate random values for character. - - """ - + def __init__(self): # name will likely be modified later - self.name = dice.roll_random_table("1d282", chargen_table["name"]) + self.name = dice.roll_random_table("1d282", chargen_tables["name"]) # base attribute values self.strength = self._random_ability() @@ -81,23 +72,22 @@ class TemporaryCharacterSheet: self.charisma = self._random_ability() # physical attributes (only for rp purposes) - physique = dice.roll_random_table("1d20", chargen_table["physique"]) - face = dice.roll_random_table("1d20", chargen_table["face"]) - skin = dice.roll_random_table("1d20", chargen_table["skin"]) - hair = dice.roll_random_table("1d20", chargen_table["hair"]) - clothing = dice.roll_random_table("1d20", chargen_table["clothing"]) - speech = dice.roll_random_table("1d20", chargen_table["speech"]) - virtue = dice.roll_random_table("1d20", chargen_table["virtue"]) - vice = dice.roll_random_table("1d20", chargen_table["vice"]) - background = dice.roll_random_table("1d20", chargen_table["background"]) - misfortune = dice.roll_random_table("1d20", chargen_table["misfortune"]) - alignment = dice.roll_random_table("1d20", chargen_table["alignment"]) + physique = dice.roll_random_table("1d20", chargen_tables["physique"]) + face = dice.roll_random_table("1d20", chargen_tables["face"]) + skin = dice.roll_random_table("1d20", chargen_tables["skin"]) + hair = dice.roll_random_table("1d20", chargen_tables["hair"]) + clothing = dice.roll_random_table("1d20", chargen_tables["clothing"]) + speech = dice.roll_random_table("1d20", chargen_tables["speech"]) + virtue = dice.roll_random_table("1d20", chargen_tables["virtue"]) + vice = dice.roll_random_table("1d20", chargen_tables["vice"]) + background = dice.roll_random_table("1d20", chargen_tables["background"]) + misfortune = dice.roll_random_table("1d20", chargen_tables["misfortune"]) + alignment = dice.roll_random_table("1d20", chargen_tables["alignment"]) self.desc = ( - f"You are {physique} with a {face} face, {skin} skin, {hair} hair, {speech} speech," - f" and {clothing} clothing. You were a {background.title()}, but you were" - f" {misfortune} and ended up a knave. You are {virtue} but also {vice}. You are of the" - f" {alignment} alignment." + f"You are {physique} with a {face} face, {skin} skin, {hair} hair, {speech} speech, and" + f" {clothing} clothing. You were a {background.title()}, but you were {misfortune} and" + f" ended up a knave. You are {virtue} but also {vice}. You tend towards {alignment}." ) # same for all @@ -105,21 +95,21 @@ class TemporaryCharacterSheet: self.hp = self.hp_max # random equipment - self.armor = dice.roll_random_table("1d20", chargen_table["armor"]) + self.armor = dice.roll_random_table("1d20", chargen_tables["armor"]) - _helmet_and_shield = dice.roll_random_table("1d20", chargen_table["helmets and shields"]) + _helmet_and_shield = dice.roll_random_table("1d20", chargen_tables["helmets and shields"]) self.helmet = "helmet" if "helmet" in _helmet_and_shield else "none" self.shield = "shield" if "shield" in _helmet_and_shield else "none" - self.weapon = dice.roll_random_table("1d20", chargen_table["starting weapon"]) + self.weapon = dice.roll_random_table("1d20", chargen_tables["starting weapon"]) self.backpack = [ "ration", "ration", - dice.roll_random_table("1d20", chargen_table["dungeoning gear"]), - dice.roll_random_table("1d20", chargen_table["dungeoning gear"]), - dice.roll_random_table("1d20", chargen_table["general gear 1"]), - dice.roll_random_table("1d20", chargen_table["general gear 2"]), + dice.roll_random_table("1d20", chargen_tables["dungeoning gear"]), + dice.roll_random_table("1d20", chargen_tables["dungeoning gear"]), + dice.roll_random_table("1d20", chargen_tables["general gear 1"]), + dice.roll_random_table("1d20", chargen_tables["general gear 2"]), ] def show_sheet(self): @@ -155,7 +145,7 @@ class TemporaryCharacterSheet: new_character = create_object( EvAdventureCharacter, key=self.name, - attrs=( + attributes=( ("strength", self.strength), ("dexterity", self.dexterity), ("constitution", self.constitution), @@ -183,7 +173,7 @@ class TemporaryCharacterSheet: for item in self.backpack: item = spawn(item) - new_character.equipment.store(item) + new_character.equipment.move(item) return new_character diff --git a/evennia/contrib/tutorials/evadventure/combat_turnbased.py b/evennia/contrib/tutorials/evadventure/combat_turnbased.py index a9ede38457..417672108b 100644 --- a/evennia/contrib/tutorials/evadventure/combat_turnbased.py +++ b/evennia/contrib/tutorials/evadventure/combat_turnbased.py @@ -357,7 +357,7 @@ class CombatActionStunt(CombatAction): if self.give_advantage: self.combathandler.gain_advantage(attacker, defender) self.msg( - f"%You() $conj(gain) advantage against $You(defender.key! " + "%You() $conj(gain) advantage against $You(defender.key! " f"You must use it within {stunt_duration} turns." ) else: @@ -398,15 +398,6 @@ class CombatActionUseItem(CombatAction): def get_help(self, item, *args): return item.get_help(*args) - def pre_use(self, item, *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.at_use(self.combatant, target, *args, **kwargs) @@ -442,7 +433,7 @@ class CombatActionSwapWieldedWeaponOrSpell(CombatAction): def use(self, _, item, *args, **kwargs): # this will make use of the item - self.combatant.equipment.use(item) + self.combatant.equipment.move(item) class CombatActionFlee(CombatAction): diff --git a/evennia/contrib/tutorials/evadventure/dungeon.py b/evennia/contrib/tutorials/evadventure/dungeon.py index de60178862..38988e4bda 100644 --- a/evennia/contrib/tutorials/evadventure/dungeon.py +++ b/evennia/contrib/tutorials/evadventure/dungeon.py @@ -465,7 +465,7 @@ class EvAdventureDungeonStartRoom(EvAdventureDungeonRoom): branch_max_life = 60 * 60 * 24 * 7 # 1 week # allow for a custom room_generator function - room_generator = AttributeProperty(room_generator, autocreate=False) + room_generator = AttributeProperty(lambda: room_generator, autocreate=False) def get_display_footer(self, looker, **kwargs): return ( diff --git a/evennia/contrib/tutorials/evadventure/equipment.py b/evennia/contrib/tutorials/evadventure/equipment.py index 9da0ec91cb..8118c6448c 100644 --- a/evennia/contrib/tutorials/evadventure/equipment.py +++ b/evennia/contrib/tutorials/evadventure/equipment.py @@ -270,13 +270,15 @@ class EquipmentHandler: # it belongs in backpack, so goes back to it to_backpack = [obj] else: - # for others (body, head), just replace whatever's there - replaced = [obj] + # for others (body, head), just replace whatever's there and put the old + # thing in the backpack + to_backpack = [slots[use_slot]] slots[use_slot] = obj for to_backpack_obj in to_backpack: # put stuff in backpack - slots[use_slot].append(to_backpack_obj) + if to_backpack_obj: + slots[WieldLocation.BACKPACK].append(to_backpack_obj) # store new state self._save() diff --git a/evennia/contrib/tutorials/evadventure/random_tables.py b/evennia/contrib/tutorials/evadventure/random_tables.py index 12a81dd167..6b6b5070bd 100644 --- a/evennia/contrib/tutorials/evadventure/random_tables.py +++ b/evennia/contrib/tutorials/evadventure/random_tables.py @@ -5,7 +5,7 @@ Random tables - adopted from _Knave_. # Character generation tables -character_generation = { +chargen_tables = { "physique": [ "athletic", "brawny", diff --git a/evennia/contrib/tutorials/evadventure/rules.py b/evennia/contrib/tutorials/evadventure/rules.py index a198e4796d..d41f054abb 100644 --- a/evennia/contrib/tutorials/evadventure/rules.py +++ b/evennia/contrib/tutorials/evadventure/rules.py @@ -24,11 +24,7 @@ This module presents several singletons to import """ from random import randint -from evennia.utils.evform import EvForm -from evennia.utils.evtable import EvTable - from .enums import Ability -from .random_tables import character_generation as chargen_table from .random_tables import death_and_dismemberment as death_table # Basic rolls @@ -68,7 +64,7 @@ class EvAdventureRollEngine: roll_string = roll_string.lower() if "d" not in roll_string: raise TypeError( - f"Dice roll '{roll_string}' was not recognized. " "Must be `d`." + f"Dice roll '{roll_string}' was not recognized. Must be `d`." ) number, diesize = roll_string.split("d", 1) try: @@ -296,9 +292,6 @@ class EvAdventureRollEngine: Args: character (Character): The one resting. - Returns: - int: How much HP was healed. This is never more than how damaged we are. - """ character.heal(self.roll("1d8") + character.constitution) @@ -334,13 +327,16 @@ class EvAdventureRollEngine: character.at_death() else: # refresh health, but get permanent ability loss - self.heal(character, self.roll("1d4")) + new_hp = self.roll("1d4") + character.heal(new_hp) setattr(character, abi, current_abi) character.msg( - "~" * 78 + "\n|yYou survive your brush with death, " + "~" * 78 + + "\n|yYou survive your brush with death, " f"but are |r{result.upper()}|y and permanently |rlose {loss} {abi}|y.|n\n" - f"|GYou recover |g{new_hp}|G health|.\n" + "~" * 78 + f"|GYou recover |g{new_hp}|G health|.\n" + + "~" * 78 ) diff --git a/evennia/contrib/tutorials/evadventure/tests/test_characters.py b/evennia/contrib/tutorials/evadventure/tests/test_characters.py index d8ed026939..2e36110268 100644 --- a/evennia/contrib/tutorials/evadventure/tests/test_characters.py +++ b/evennia/contrib/tutorials/evadventure/tests/test_characters.py @@ -43,4 +43,4 @@ class TestCharacters(BaseEvenniaTest): # can't get more coins than we have result = self.character.at_pay(100) self.assertEqual(result, 40) - self.assertEqual(self.characer.coins, 0) + self.assertEqual(self.character.coins, 0) diff --git a/evennia/contrib/tutorials/evadventure/tests/test_chargen.py b/evennia/contrib/tutorials/evadventure/tests/test_chargen.py new file mode 100644 index 0000000000..7be208c0d1 --- /dev/null +++ b/evennia/contrib/tutorials/evadventure/tests/test_chargen.py @@ -0,0 +1,64 @@ +""" +Test chargen. + +""" + +from unittest.mock import MagicMock, patch + +from evennia import create_object +from evennia.utils.test_resources import BaseEvenniaTest +from parameterized import parameterized + +from .. import chargen, enums, objects + + +class EvAdventureCharacterGenerationTest(BaseEvenniaTest): + """ + Test the Character generator 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 = chargen.TemporaryCharacterSheet() + + def test_base_chargen(self): + self.assertEqual(self.chargen.strength, 10) # not realistic, due to mock + 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.desc, + "You are scrawny with a broken face, pockmarked skin, greased hair, hoarse speech, and " + "stained clothing. You were a Herbalist, but you were exiled and ended up a knave. You " + "are honest but also irascible. You tend towards neutrality.", + ) + + @patch("evennia.contrib.tutorials.evadventure.chargen.spawn") + def test_apply(self, mock_spawn): + + gambeson = create_object(objects.EvAdventureArmor, key="gambeson") + mock_spawn.return_value = gambeson + + character = self.chargen.apply() + + self.assertIn("Herbalist", character.db.desc) + self.assertEqual( + character.equipment.all(), + [ + (None, enums.WieldLocation.WEAPON_HAND), + (None, enums.WieldLocation.SHIELD_HAND), + (None, enums.WieldLocation.TWO_HANDS), + (gambeson, enums.WieldLocation.BODY), + (None, enums.WieldLocation.HEAD), + ], + ) + + gambeson.delete() + character.delete() diff --git a/evennia/contrib/tutorials/evadventure/tests/test_equipment.py b/evennia/contrib/tutorials/evadventure/tests/test_equipment.py index cecc0afd2f..7ea051c971 100644 --- a/evennia/contrib/tutorials/evadventure/tests/test_equipment.py +++ b/evennia/contrib/tutorials/evadventure/tests/test_equipment.py @@ -4,7 +4,10 @@ Test the EvAdventure equipment handler. """ +from unittest.mock import MagicMock, patch + from evennia.utils.test_resources import BaseEvenniaTest +from parameterized import parameterized from ..enums import Ability, WieldLocation from ..equipment import EquipmentError @@ -20,13 +23,6 @@ class TestEquipment(EvAdventureMixin, BaseEvenniaTest): setattr(self.character, Ability.CON.value, 3) self.assertEqual(self.character.equipment.max_slots, 13) - def test_validate_slot_usage(self): - helmet = self.helmet - self.assertTrue(self.character.equipment.validate_slot_usage(helmet)) - helmet.size = 20 # a very large helmet - with self.assertRaises(EquipmentError): - self.assertFalse(self.character.equipment.validate_slot_usage(helmet)) - def test_add__remove(self): self.character.equipment.add(self.helmet) self.assertEqual(self.character.equipment.slots[WieldLocation.BACKPACK], [self.helmet]) @@ -53,6 +49,141 @@ class TestEquipment(EvAdventureMixin, BaseEvenniaTest): ) self.assertEqual( - self.character.equipment.get_all(), - [(self.helmet, WieldLocation.BACKPACK), (self.weapon, WieldLocation.BACKPACK)], + self.character.equipment.all(), + [ + (None, WieldLocation.WEAPON_HAND), + (None, WieldLocation.SHIELD_HAND), + (None, WieldLocation.TWO_HANDS), + (None, WieldLocation.BODY), + (None, WieldLocation.HEAD), + (self.helmet, WieldLocation.BACKPACK), + (self.weapon, WieldLocation.BACKPACK), + ], ) + + def _get_empty_slots(self): + return { + WieldLocation.BACKPACK: [], + WieldLocation.WEAPON_HAND: None, + WieldLocation.SHIELD_HAND: None, + WieldLocation.TWO_HANDS: None, + WieldLocation.BODY: None, + 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 + + with patch("evennia.contrib.tutorials.evadventure.equipment.inherits_from") as mock_inherit: + mock_inherit.return_value = True + 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", WieldLocation.HEAD), + ("shield", WieldLocation.SHIELD_HAND), + ("armor", WieldLocation.BODY), + ("weapon", WieldLocation.WEAPON_HAND), + ("big_weapon", WieldLocation.TWO_HANDS), + ("item", WieldLocation.BACKPACK), + ] + ) + def test_move(self, itemname, where): + self.assertEqual(self.character.equipment.slots, self._get_empty_slots()) + + obj = getattr(self, itemname) + self.character.equipment.move(obj) + # check that item ended up in the right place + if where is WieldLocation.BACKPACK: + self.assertTrue(obj in self.character.equipment.slots[where]) + else: + self.assertEqual(self.character.equipment.slots[where], obj) + + def test_add(self): + self.character.equipment.add(self.weapon) + self.assertEqual(self.character.equipment.slots[WieldLocation.WEAPON_HAND], None) + self.assertTrue(self.weapon in self.character.equipment.slots[WieldLocation.BACKPACK]) + + def test_two_handed_exclusive(self): + """Two-handed weapons can't be used together with weapon+shield""" + self.character.equipment.move(self.big_weapon) + self.assertEqual(self.character.equipment.slots[WieldLocation.TWO_HANDS], self.big_weapon) + # equipping sword or shield removes two-hander + self.character.equipment.move(self.shield) + self.assertEqual(self.character.equipment.slots[WieldLocation.SHIELD_HAND], self.shield) + self.assertEqual(self.character.equipment.slots[WieldLocation.TWO_HANDS], None) + self.character.equipment.move(self.weapon) + self.assertEqual(self.character.equipment.slots[WieldLocation.WEAPON_HAND], self.weapon) + + # the two-hander removes the two weapons + self.character.equipment.move(self.big_weapon) + self.assertEqual(self.character.equipment.slots[WieldLocation.TWO_HANDS], self.big_weapon) + self.assertEqual(self.character.equipment.slots[WieldLocation.SHIELD_HAND], None) + self.assertEqual(self.character.equipment.slots[WieldLocation.WEAPON_HAND], None) + + def test_remove__with_obj(self): + self.character.equipment.move(self.shield) + self.character.equipment.move(self.item) + self.character.equipment.add(self.weapon) + + self.assertEqual(self.character.equipment.slots[WieldLocation.SHIELD_HAND], self.shield) + self.assertEqual( + self.character.equipment.slots[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[WieldLocation.SHIELD_HAND], None) + self.assertEqual(self.character.equipment.slots[WieldLocation.BACKPACK], [self.weapon]) + + def test_remove__with_slot(self): + self.character.equipment.move(self.shield) + self.character.equipment.move(self.item) + self.character.equipment.add(self.helmet) + + self.assertEqual(self.character.equipment.slots[WieldLocation.SHIELD_HAND], self.shield) + self.assertEqual( + self.character.equipment.slots[WieldLocation.BACKPACK], [self.item, self.helmet] + ) + + self.assertEqual(self.character.equipment.remove(WieldLocation.SHIELD_HAND), [self.shield]) + self.assertEqual( + self.character.equipment.remove(WieldLocation.BACKPACK), [self.item, self.helmet] + ) + + self.assertEqual(self.character.equipment.slots[WieldLocation.SHIELD_HAND], None) + self.assertEqual(self.character.equipment.slots[WieldLocation.BACKPACK], []) + + def test_properties(self): + self.character.equipment.move(self.armor) + self.assertEqual(self.character.equipment.armor, 1) + self.character.equipment.move(self.shield) + self.assertEqual(self.character.equipment.armor, 2) + self.character.equipment.move(self.helmet) + self.assertEqual(self.character.equipment.armor, 3) + + self.character.equipment.move(self.weapon) + self.assertEqual(self.character.equipment.weapon, self.weapon) + self.character.equipment.move(self.big_weapon) + self.assertEqual(self.character.equipment.weapon, self.big_weapon) diff --git a/evennia/contrib/tutorials/evadventure/tests/test_rules.py b/evennia/contrib/tutorials/evadventure/tests/test_rules.py index c7f5fcaf12..8d6756336e 100644 --- a/evennia/contrib/tutorials/evadventure/tests/test_rules.py +++ b/evennia/contrib/tutorials/evadventure/tests/test_rules.py @@ -151,24 +151,20 @@ class EvAdventureRollEngineTest(BaseEvenniaTest): mock_randint.return_value = 10 self.assertEqual( - self.roll_engine.roll_random_table( - "1d20", random_tables.character_generation["physique"] - ), + self.roll_engine.roll_random_table("1d20", random_tables.chargen_tables["physique"]), "scrawny", ) self.assertEqual( - self.roll_engine.roll_random_table("1d20", random_tables.character_generation["vice"]), + self.roll_engine.roll_random_table("1d20", random_tables.chargen_tables["vice"]), "irascible", ) self.assertEqual( - self.roll_engine.roll_random_table( - "1d20", random_tables.character_generation["alignment"] - ), + self.roll_engine.roll_random_table("1d20", random_tables.chargen_tables["alignment"]), "neutrality", ) self.assertEqual( self.roll_engine.roll_random_table( - "1d20", random_tables.character_generation["helmets and shields"] + "1d20", random_tables.chargen_tables["helmets and shields"] ), "no helmet or shield", ) @@ -176,14 +172,14 @@ class EvAdventureRollEngineTest(BaseEvenniaTest): mock_randint.return_value = 25 self.assertEqual( self.roll_engine.roll_random_table( - "1d20", random_tables.character_generation["helmets and shields"] + "1d20", random_tables.chargen_tables["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"] + "1d20", random_tables.chargen_tables["helmets and shields"] ), "no helmet or shield", ) @@ -202,17 +198,15 @@ class EvAdventureRollEngineTest(BaseEvenniaTest): @patch("evennia.contrib.tutorials.evadventure.rules.randint") def test_heal_from_rest(self, mock_randint): character = MagicMock() + character.heal = 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 + character.heal.assert_called_with(6) # roll + constitution bonus @patch("evennia.contrib.tutorials.evadventure.rules.randint") def test_roll_death(self, mock_randint): @@ -229,233 +223,3 @@ class EvAdventureRollEngineTest(BaseEvenniaTest): mock_randint.return_value = 3 self.roll_engine.roll_death(character) self.assertEqual(character.strength, 10) - - -class EvAdventureCharacterGenerationTest(BaseEvenniaTest): - """ - Test the Character generator 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.add.assert_called() - - -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(equipment.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_add(self): - self.character.equipment.add(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.add(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.add(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], []) - - def test_properties(self): - self.character.equipment.use(self.armor) - self.assertEqual(self.character.equipment.armor, 1) - self.character.equipment.use(self.shield) - self.assertEqual(self.character.equipment.armor, 2) - self.character.equipment.use(self.helmet) - self.assertEqual(self.character.equipment.armor, 3) - - self.character.equipment.use(self.weapon) - self.assertEqual(self.character.equipment.weapon, self.weapon) - self.character.equipment.use(self.big_weapon) - self.assertEqual(self.character.equipment.weapon, self.big_weapon) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 07fe566d03..570398894a 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -882,14 +882,18 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): return False # check if source location lets us go try: - if not source_location.at_pre_object_leave(self, destination, **kwargs): + if source_location and not source_location.at_pre_object_leave( + self, destination, **kwargs + ): return False except Exception as err: logerr(errtxt.format(err="at_pre_object_leave()"), err) return False # check if destination accepts us try: - if not self.at_pre_object_receive(self, source_location, **kwargs): + if destination and not destination.at_pre_object_receive( + self, source_location, **kwargs + ): return False except Exception as err: logerr(errtxt.format(err="at_pre_object_receive()"), err) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index 4af0f34f0a..274459adbc 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -2732,7 +2732,7 @@ _INT2STR_MAP_NOUN = { _INT2STR_MAP_ADJ = {1: "1st", 2: "2nd", 3: "3rd"} # rest is Xth. -def int2str(self, number, adjective=False): +def int2str(number, adjective=False): """ Convert a number to an English string for better display; so 1 -> one, 2 -> two etc up until 12, after which it will be '13', '14' etc.