from pathlib import Path import pytest from engine.character import Character from engine.defs import DefRegistry from engine.entity import Entity, EntityPosition from engine.inventory import Inventory from engine.item import ItemInstance from engine.map_loader import MapLoader from engine.world import World from resources.logic.actions import activate_hand from resources.logic.steering import advance_ship_rotation, at_helm, try_steer_ship, try_turn_ship from resources.logic.weather import is_exposed DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs" @pytest.fixture() def registry(): return DefRegistry.load(DEFS_DIR) @pytest.fixture() def world_and_ship(registry): loader = MapLoader(DEFS_DIR / "maps") harbor = loader.load("harbor_world.json") embedding = harbor.embeddings[0] ship = embedding.child_map world = World(registry=registry) world.add_map(harbor) world.add_map(ship) return world, harbor, ship, embedding def make_human(registry, **kwargs) -> Character: return Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"), **kwargs) # --- embedding + rotation --------------------------------------------------------------------- def test_ship_is_embedded_with_a_quarter_turn_rotation(world_and_ship): _world, _harbor, _ship, embedding = world_and_ship assert embedding.rotation == 1 assert embedding.child_map.map_id == "ship_test" def test_rotated_footprint_places_local_cells_at_the_expected_parent_cells(world_and_ship): _world, _harbor, _ship, embedding = world_and_ship # rotation=1 on a square footprint: local (x, y) -> parent (anchor + (height-1-y, x)) assert embedding.local_to_parent(0, 2, 0) == (6, 4, 0) # hold doorway assert embedding.local_to_parent(3, 3, 0) == (5, 7, 0) # hold stairs_up assert embedding.local_to_parent(1, 1, 1) == (7, 5, 1) # helm (not reachable from parent, see below) # --- boarding: parent field -> ship hold via a wall doorway ----------------------------------- def test_walking_through_the_doorway_boards_the_ship(world_and_ship, registry): world, harbor, _ship, embedding = world_and_ship door_parent = embedding.local_to_parent(0, 2, 0) outside = (door_parent[0] - 1, door_parent[1], door_parent[2]) assert harbor.get_tile(*outside).is_walkable(registry) player = Entity(position=EntityPosition("harbor_world", *outside)) assert world.try_move(player, 1, 0, 0) is True assert (player.position.map_id, player.position.x, player.position.y, player.position.z) == ( "ship_test", 0, 2, 0, ) def test_ship_perimeter_wall_blocks_boarding_anywhere_else(world_and_ship, registry): world, _harbor, _ship, embedding = world_and_ship wall_parent = embedding.local_to_parent(0, 1, 0) # a walled hold cell, not the doorway outside = (wall_parent[0] - 1, wall_parent[1], wall_parent[2]) player = Entity(position=EntityPosition("harbor_world", *outside)) assert world.try_move(player, 1, 0, 0) is False # --- staircase: hold (z=0) <-> main deck (z=1), same (x, y) column ---------------------------- def test_staircase_carries_player_from_main_deck_down_to_the_hold(world_and_ship, registry): world, _harbor, _ship, _embedding = world_and_ship player = make_human(registry, position=EntityPosition("ship_test", 3, 2, 1)) world.add_entity(player) assert world.try_move(player, 0, 1, 0) is True # steps onto (3,3,1) stairs_down assert (player.position.x, player.position.y, player.position.z) == (3, 3, 0) def test_staircase_round_trip_back_up_after_stepping_off_and_on(world_and_ship, registry): world, _harbor, _ship, _embedding = world_and_ship player = make_human(registry, position=EntityPosition("ship_test", 3, 2, 1)) world.add_entity(player) world.try_move(player, 0, 1, 0) # down to the hold, standing on stairs_up at (3,3,0) assert world.try_move(player, -1, 0, 0) is True # step off, to (2,3,0) assert player.position.z == 0 assert world.try_move(player, 1, 0, 0) is True # step back onto stairs_up at (3,3,0) assert (player.position.x, player.position.y, player.position.z) == (3, 3, 1) # --- steering: move and turn the ship from its helm -------------------------------------------- def test_standing_at_the_helm_is_detected(world_and_ship, registry): world, _harbor, _ship, embedding = world_and_ship helmsman = make_human(registry, position=EntityPosition("ship_test", 1, 1, 1)) world.add_entity(helmsman) assert at_helm(helmsman, world) is embedding def test_steering_moves_the_ships_anchor(world_and_ship, registry): world, _harbor, _ship, embedding = world_and_ship helmsman = make_human(registry, position=EntityPosition("ship_test", 1, 1, 1)) world.add_entity(helmsman) original_anchor = embedding.anchor assert try_steer_ship(helmsman, world, 1, 0, 0) is True assert embedding.anchor == (original_anchor[0] + 1, original_anchor[1], original_anchor[2]) def test_turning_the_ship_changes_its_rotation(world_and_ship, registry): world, _harbor, _ship, embedding = world_and_ship helmsman = make_human(registry, position=EntityPosition("ship_test", 1, 1, 1)) world.add_entity(helmsman) assert try_turn_ship(helmsman, world, 1) is True assert embedding.desired_rotation == 2 # ship doesn't snap - it turns toward the desired heading at its own max_rotation_speed assert advance_ship_rotation(embedding, world, now=0.0) is True assert embedding.rotation == 2 def test_not_at_helm_cannot_steer(world_and_ship, registry): world, _harbor, _ship, embedding = world_and_ship bystander = make_human(registry, position=EntityPosition("ship_test", 3, 1, 1)) world.add_entity(bystander) original_anchor = embedding.anchor assert try_steer_ship(bystander, world, 1, 0, 0) is False assert embedding.anchor == original_anchor # --- weather exposure: roofed hold/helm booth vs. unroofed open deck -------------------------- def test_hold_is_sheltered_from_weather(world_and_ship, registry): _world, _harbor, ship, _embedding = world_and_ship assert is_exposed(ship, 2, 2, 0, registry) is False def test_helm_booth_is_sheltered_but_the_open_deck_around_it_is_not(world_and_ship, registry): _world, _harbor, ship, _embedding = world_and_ship assert is_exposed(ship, 1, 1, 1, registry) is False # under the helm booth's roof assert is_exposed(ship, 3, 1, 1, registry) is True # open deck, no roof # --- deconstructor/constructor tools work on the ship's own tiles ----------------------------- def test_deconstructor_removes_the_helm_booths_wall(world_and_ship, registry): world, _harbor, ship, _embedding = world_and_ship character = make_human(registry, position=EntityPosition("ship_test", 1, 1, 1)) character.wield_item("right_hand", "multi_deconstructor", registry) character.inventory = Inventory(4, 4) world.add_entity(character) assert ship.get_tile(2, 1, 1).room is not None result = activate_hand(character, world, registry, "right_hand", (1, 0, 0)) assert result is True assert ship.get_tile(2, 1, 1).room is None def test_constructor_rebuilds_a_wall_from_carried_planks(world_and_ship, registry): world, _harbor, ship, _embedding = world_and_ship ship.get_tile(2, 1, 1).room = None # bare slot, as if just deconstructed character = make_human(registry, position=EntityPosition("ship_test", 1, 1, 1)) character.wield_item("right_hand", "constructor_tool", registry) character.inventory = Inventory(4, 4) character.inventory.place(ItemInstance("planks-1", "wood_planks"), registry.get_item_def("wood_planks"), 0, 0) world.add_entity(character) result = activate_hand(character, world, registry, "right_hand", (1, 0, 0)) assert result is True assert ship.get_tile(2, 1, 1).room.def_id == "wood_wall"