118 lines
4.7 KiB
Python
118 lines
4.7 KiB
Python
import pytest
|
|
|
|
from engine.entity import Entity, EntityPosition
|
|
from engine.map import Map
|
|
from engine.tile import Tile, TileLayerDef, TileLayerInstance
|
|
from engine.world import World
|
|
|
|
|
|
class FakeRegistry:
|
|
def get_tile_layer_def(self, layer, def_id):
|
|
if layer == "room" and def_id == "wood_wall":
|
|
return TileLayerDef(id="wood_wall", layer="room", walkable=False, blocks_los=True)
|
|
return None
|
|
|
|
|
|
def make_world():
|
|
parent = Map("world", 10, 10, 1)
|
|
for x in range(10):
|
|
for y in range(10):
|
|
parent.set_tile(x, y, 0, Tile(flooring=TileLayerInstance("grass")))
|
|
|
|
ship = Map("ship", 4, 3, 1)
|
|
for x in range(4):
|
|
for y in range(3):
|
|
ship.set_tile(x, y, 0, Tile(flooring=TileLayerInstance("wood_deck")))
|
|
for x, y in [(0, 0), (1, 0), (3, 0), (0, 1), (3, 1), (0, 2), (1, 2), (2, 2), (3, 2)]:
|
|
ship.get_tile(x, y, 0).room = TileLayerInstance("wood_wall") # (2,0) is the open doorway
|
|
|
|
parent.embed(ship, (6, 6, 0))
|
|
|
|
world = World()
|
|
world.maps = {"world": parent, "ship": ship}
|
|
world.registry = FakeRegistry()
|
|
return world, parent, ship
|
|
|
|
|
|
def test_continuous_move_lands_at_a_fractional_position():
|
|
world, _parent, _ship = make_world()
|
|
player = Entity(position=EntityPosition("world", 2.0, 2.0, 0))
|
|
assert world.try_move_continuous(player, 1, 0, 0, distance=0.35) is True
|
|
assert player.position.x == pytest.approx(2.35)
|
|
assert player.position.y == pytest.approx(2.0)
|
|
|
|
|
|
def test_continuous_move_normalizes_diagonal_direction():
|
|
world, _parent, _ship = make_world()
|
|
player = Entity(position=EntityPosition("world", 2.0, 2.0, 0))
|
|
assert world.try_move_continuous(player, 1, 1, 0, distance=1.0) is True
|
|
# moved exactly 1 unit total, split evenly between x and y (not 1 in each axis)
|
|
import math
|
|
|
|
dx = player.position.x - 2.0
|
|
dy = player.position.y - 2.0
|
|
assert math.hypot(dx, dy) == pytest.approx(1.0)
|
|
assert dx == pytest.approx(dy)
|
|
|
|
|
|
def test_continuous_move_blocked_by_wall_leaves_position_unchanged():
|
|
world, _parent, ship = make_world()
|
|
# ship-local (0,0) is a wood_wall; approach it from inside at (1.2,1.2) moving toward it
|
|
player = Entity(position=EntityPosition("ship", 1.2, 1.2, 0))
|
|
assert world.try_move_continuous(player, -1, -1, 0, distance=1.5) is False
|
|
assert player.position.x == pytest.approx(1.2)
|
|
assert player.position.y == pytest.approx(1.2)
|
|
|
|
|
|
def test_continuous_move_crosses_into_embedded_ship_at_fractional_local_position():
|
|
world, _parent, ship = make_world()
|
|
# doorway column is parent x=8 (ship-local x=2); approach from just north of it
|
|
player = Entity(position=EntityPosition("world", 8.3, 5.6, 0))
|
|
assert world.try_move_continuous(player, 0, 1, 0, distance=0.6) is True
|
|
assert player.position.map_id == "ship"
|
|
assert player.position.x == pytest.approx(2.3)
|
|
assert player.position.y == pytest.approx(0.2)
|
|
|
|
|
|
def test_continuous_move_disembarks_at_fractional_parent_position():
|
|
world, _parent, ship = make_world()
|
|
player = Entity(position=EntityPosition("ship", 2.3, 0.2, 0))
|
|
assert world.try_move_continuous(player, 0, -1, 0, distance=0.6) is True
|
|
assert player.position.map_id == "world"
|
|
assert player.position.x == pytest.approx(8.3)
|
|
assert player.position.y == pytest.approx(5.6)
|
|
|
|
|
|
def test_entity_at_matches_by_tile_membership_for_fractional_positions():
|
|
world, _parent, _ship = make_world()
|
|
entity = Entity(position=EntityPosition("world", 3.7, 4.2, 0))
|
|
world.add_entity(entity)
|
|
assert world.entity_at("world", 3, 4, 0) is entity
|
|
assert world.entity_at("world", 4, 4, 0) is None
|
|
|
|
|
|
def test_rotate_position_round_trips_through_a_rotated_embedding():
|
|
world, _parent, ship = make_world()
|
|
embedding = ship.parent_embedding
|
|
embedding.rotate_to(1)
|
|
|
|
for lx, ly in [(0.5, 0.5), (2.7, 1.1), (3.99, 0.01)]:
|
|
px, py, pz = embedding.local_to_parent_pos(lx, ly, 0)
|
|
rx, ry, rz = embedding.parent_to_local_pos(px, py, pz)
|
|
assert rx == pytest.approx(lx)
|
|
assert ry == pytest.approx(ly)
|
|
|
|
|
|
def test_continuous_move_crosses_into_rotated_embedded_ship():
|
|
world, _parent, ship = make_world()
|
|
embedding = ship.parent_embedding
|
|
embedding.rotate_to(1) # 90 deg CW: 4x3 footprint becomes 3x4 in parent space
|
|
|
|
# find where the doorway (ship-local 2,0) lands post-rotation, approach from just outside it
|
|
door_parent = embedding.local_to_parent(2, 0, 0)
|
|
start_x = door_parent[0] + 0.5 - 1 # one tile back along x, offset into the tile
|
|
start_y = door_parent[1] + 0.5
|
|
player = Entity(position=EntityPosition("world", start_x, start_y, 0))
|
|
assert world.try_move_continuous(player, 1, 0, 0, distance=1.0) is True
|
|
assert player.position.map_id == "ship"
|