"""Tests for Tiny-or-smaller reach zero and square sharing (PF1e S4c). SRD "Big and Little Creatures in Combat": Tiny, Diminutive and Fine creatures "take up less than 1 square of space", have "a natural reach of 0 feet", do not threaten the squares around them, and "must enter an opponent's square to attack" — so a Tiny-or-smaller creature and a strictly larger one may share a square (move through and end on it), while two creatures of the same (Small+) size never share. """ from __future__ import annotations from pf1e_simulator.combat import _effective_reach_ft from tests.test_creature_sizes import make_combatant, make_engine, make_state class TestTinyReachZero: """A Tiny creature whose melee attack has reach_ft 0 reaches only its cell.""" def test_threatens_no_squares(self) -> None: tiny = make_combatant("tiny", size="Tiny", reach_ft=0) foe = make_combatant("foe", size="Medium") s_tiny = make_state(tiny, "a", (3, 3)) s_foe = make_state(foe, "b", (3, 4)) eng = make_engine([s_tiny, s_foe]) assert eng.threatened_squares(s_tiny) == frozenset() def test_attacks_a_creature_in_its_own_cell(self) -> None: tiny = make_combatant("tiny", size="Tiny", reach_ft=0) foe = make_combatant("foe", size="Medium") s_tiny = make_state(tiny, "a", (3, 3)) s_foe = make_state(foe, "b", (3, 3)) # sharing the square eng = make_engine([s_tiny, s_foe]) assert eng.weapon_for(s_tiny, s_foe) is not None def test_cannot_reach_an_adjacent_creature(self) -> None: tiny = make_combatant("tiny", size="Tiny", reach_ft=0) foe = make_combatant("foe", size="Medium") s_tiny = make_state(tiny, "a", (3, 3)) s_foe = make_state(foe, "b", (3, 4)) eng = make_engine([s_tiny, s_foe]) assert eng.weapon_for(s_tiny, s_foe) is None def test_authored_reach_is_kept(self) -> None: # A natural attack authored with reach_ft 5 (e.g. ierlieth's bite) # overrides the size default: the creature reaches and threatens 5 ft. tiny = make_combatant("tiny", size="Tiny", reach_ft=5) foe = make_combatant("foe", size="Medium") s_tiny = make_state(tiny, "a", (3, 3)) s_foe = make_state(foe, "b", (3, 4)) eng = make_engine([s_tiny, s_foe]) assert _effective_reach_ft(s_tiny, tiny.attacks[0]) == 5 assert (3, 4) in eng.threatened_squares(s_tiny) assert eng.weapon_for(s_tiny, s_foe) is not None class TestCoOccupancy: """A Tiny-or-smaller creature shares a square with a strictly larger one.""" def test_tiny_may_end_on_a_larger_creature_cell(self) -> None: tiny = make_combatant("tiny", size="Tiny", reach_ft=0) foe = make_combatant("foe", size="Medium") s_tiny = make_state(tiny, "a", (3, 3)) s_foe = make_state(foe, "b", (3, 4)) eng = make_engine([s_tiny, s_foe]) assert eng._destination_reachable(s_tiny, (3, 4)) is True def test_larger_may_end_on_a_tiny_cell(self) -> None: tiny = make_combatant("tiny", size="Tiny", reach_ft=0) foe = make_combatant("foe", size="Medium") s_tiny = make_state(tiny, "a", (3, 4)) s_foe = make_state(foe, "b", (3, 3)) eng = make_engine([s_tiny, s_foe]) assert eng._destination_reachable(s_foe, (3, 4)) is True def test_two_medium_never_share(self) -> None: m1 = make_combatant("m1", size="Medium") m2 = make_combatant("m2", size="Medium") s1 = make_state(m1, "a", (3, 3)) s2 = make_state(m2, "b", (3, 4)) eng = make_engine([s1, s2]) assert eng._destination_reachable(s1, (3, 4)) is False assert eng._cells_free(s2.occupied_cells(), s1) is False def test_tiny_may_cross_a_larger_hostile_cell(self) -> None: tiny = make_combatant("tiny", size="Tiny", reach_ft=0) foe = make_combatant("foe", size="Medium") s_tiny = make_state(tiny, "a", (3, 3)) s_foe = make_state(foe, "b", (3, 4)) eng = make_engine([s_tiny, s_foe]) assert eng._cells_walkable(s_foe.occupied_cells(), s_tiny) is True def test_medium_cannot_cross_a_medium_hostile_cell(self) -> None: m1 = make_combatant("m1", size="Medium") m2 = make_combatant("m2", size="Medium") s1 = make_state(m1, "a", (3, 3)) s2 = make_state(m2, "b", (3, 4)) eng = make_engine([s1, s2]) assert eng._cells_walkable(s2.occupied_cells(), s1) is False