extends GutTest ## The item registry. Small, but two of these are load-bearing: the wire index ## and the art table both key off Items.ORDER, and a mismatch in either is ## silent rather than loud. func test_every_registered_id_has_a_definition() -> void: for id in Items.ORDER: assert_not_null(Items.get_def(id), "%s is in ORDER with no definition" % id) func test_the_wire_index_round_trips() -> void: for id in Items.ORDER: assert_eq(Items.by_index(Items.index_of(id)), id) ## Index 0 is reserved for "empty slot". If an item ever landed on it, every ## empty slot in the game would quietly contain that item. func test_index_zero_is_reserved_for_nothing() -> void: assert_eq(Items.by_index(0), Items.NONE) assert_eq(Items.index_of(Items.NONE), 0) for id in Items.ORDER: assert_gt(Items.index_of(id), 0, "%s must not occupy the empty index" % id) ## An id or index this build does not know reads as empty, never as whatever ## happens to sit nearby. A client one version behind must see an unknown item ## as an empty slot, not as a potion. func test_unknown_items_decay_to_nothing() -> void: assert_eq(Items.index_of(&"not_a_real_item"), 0) assert_eq(Items.by_index(200), Items.NONE) assert_eq(Items.by_index(-1), Items.NONE) assert_null(Items.get_def(&"not_a_real_item")) func test_the_index_fits_in_the_byte_the_wire_gives_it() -> void: assert_lte(Items.ORDER.size(), 254, "item ids are sent as a single byte; past this the format has to change") func test_there_is_an_icon_for_every_item() -> void: for id in Items.ORDER: var def := Items.get_def(id) assert_lt(def.visual, Art.ITEM_ICONS.size(), "%s has visual %d with no icon" % [id, def.visual]) var icon := Art.item_icon(id) assert_lte(icon.end.x, float(Art.TILESET.get_width())) assert_lte(icon.end.y, float(Art.TILESET.get_height())) ## Healing is a percentage of maximum health so a potion is worth the same slot ## at level 15 as at level 1. A flat value would be a full heal early and noise ## late, which is the wrong shape for the only consumable in the game. func test_the_health_potion_heals_a_share_of_maximum_health() -> void: var def := Items.get_def(Items.HEALTH_POTION) assert_eq(def.effect, ItemDef.Effect.HEAL) assert_gt(def.effect_value, 0.0) assert_lte(def.effect_value, 100.0) var p := SimPlayer.new() p.max_hp = 200 p.hp = 10 assert_eq(p.heal_percent(def.effect_value), int(200.0 * def.effect_value / 100.0)) func test_healing_never_overshoots_maximum_health() -> void: var p := SimPlayer.new() p.max_hp = 100 p.hp = 95 assert_eq(p.heal_percent(80.0), 5) assert_eq(p.hp, 100) assert_eq(p.heal_percent(80.0), 0, "already full heals for nothing") ## The ration's entire job is to exercise the player-instanced loot path. If it ## ever gains an effect, that job needs a new holder. func test_the_ration_does_nothing() -> void: assert_eq(Items.get_def(Items.WARDENS_RATION).effect, ItemDef.Effect.NONE) func test_a_player_starts_with_the_configured_number_of_empty_slots() -> void: var p := SimPlayer.new() assert_eq(p.inventory.size(), SimConfig.INVENTORY_SLOTS) assert_eq(p.free_slot(), 0) for item in p.inventory: assert_eq(item, Items.NONE) func test_slot_operations_refuse_indices_off_the_end() -> void: # The index arrives off the wire, so nonsense has to be safe rather than # merely unlikely. var p := SimPlayer.new() p.add_item(Items.HEALTH_POTION) assert_eq(p.take_slot(-1), Items.NONE) assert_eq(p.take_slot(9999), Items.NONE) assert_eq(p.inventory[0], Items.HEALTH_POTION, "and must not disturb the bag") func test_a_full_bag_refuses_more() -> void: var p := SimPlayer.new() for i in SimConfig.INVENTORY_SLOTS: assert_eq(p.add_item(Items.HEALTH_POTION), i) assert_eq(p.free_slot(), -1) assert_eq(p.add_item(Items.HEALTH_POTION), -1) ## A save written when the game had a different slot count must still load. func test_setting_an_inventory_pads_and_trims_to_the_slot_count() -> void: var p := SimPlayer.new() var short: Array[StringName] = [Items.HEALTH_POTION] p.set_inventory(short) assert_eq(p.inventory.size(), SimConfig.INVENTORY_SLOTS) assert_eq(p.inventory[0], Items.HEALTH_POTION) assert_eq(p.inventory[SimConfig.INVENTORY_SLOTS - 1], Items.NONE) var long: Array[StringName] = [] for _i in SimConfig.INVENTORY_SLOTS + 5: long.append(Items.WARDENS_RATION) p.set_inventory(long) assert_eq(p.inventory.size(), SimConfig.INVENTORY_SLOTS)