tilemap-parser 5.0.4__tar.gz → 5.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/PKG-INFO +1 -1
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/pyproject.toml +1 -1
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/platformer.py +2 -3
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/queries.py +24 -13
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/runner.py +10 -5
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/navigation/nav_grid.py +45 -19
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/world.py +70 -5
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser.egg-info/PKG-INFO +1 -1
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser.egg-info/SOURCES.txt +1 -0
- tilemap_parser-5.1.0/tests/test_gid_routing.py +293 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/LICENSE +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/README.md +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/pypi-readme.md +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/setup.cfg +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/__init__.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/parser/__init__.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/parser/animation.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/parser/collision.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/parser/collision_loader.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/parser/map_parse.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/parser/node_parse.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/parser/particle.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/parser/tmx_converter.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/__init__.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/animation_player.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/area_node.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/body.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/camera.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/collision/__init__.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/collision/hit.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/collision/manager.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/collision/shapes.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/collision_cache.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/map_loader.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/map_object.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/__init__.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/grounded.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/rpg.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/slide.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/types.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/navigation/__init__.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/navigation/path_follower.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/navigation/pathfinder.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/object_collision.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/particles.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/polygon_query.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/protocols.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/renderer.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/tile_collision.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/utils/__init__.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/utils/geometry.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser.egg-info/requires.txt +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser.egg-info/top_level.txt +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_body.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_camera.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_collision.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_geometry.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_gid_collision.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_integration_animation.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_integration_collision.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_integration_map_loader.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_map_loader.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_map_object.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_move_grounded.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_navigation.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_object_collision.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_object_surfaces.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_particle_emitter.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_render_scale.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_tile_collision.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_tile_layer_renderer_y_sort.py +0 -0
- {tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/tests/test_tmx_converter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tilemap-parser
|
|
3
|
-
Version: 5.0
|
|
3
|
+
Version: 5.1.0
|
|
4
4
|
Summary: Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime.
|
|
5
5
|
Author: tilemap parser contributors
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tilemap-parser"
|
|
7
|
-
version = "5.0
|
|
7
|
+
version = "5.1.0"
|
|
8
8
|
description = "Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime."
|
|
9
9
|
readme = "pypi-readme.md"
|
|
10
10
|
requires-python = ">=3.10"
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/platformer.py
RENAMED
|
@@ -8,6 +8,7 @@ from ...parser.collision import TilesetCollision
|
|
|
8
8
|
from ..polygon_query import _check_sprite_polygon_offset, get_shape_bounds
|
|
9
9
|
from ..protocols import ICollidableSprite
|
|
10
10
|
from ..world import PhysicsWorld
|
|
11
|
+
from .queries import _resolve_tile_data
|
|
11
12
|
from .types import CollisionResult, Vector2
|
|
12
13
|
|
|
13
14
|
|
|
@@ -118,9 +119,7 @@ def move_platformer(
|
|
|
118
119
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
119
120
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
120
121
|
tile_id = tile_map.get((tile_x, tile_y))
|
|
121
|
-
|
|
122
|
-
continue
|
|
123
|
-
tile_data = tileset_collision.tiles.get(tile_id)
|
|
122
|
+
tile_data = _resolve_tile_data(world, tileset_collision, tile_id)
|
|
124
123
|
if tile_data is None:
|
|
125
124
|
continue
|
|
126
125
|
ox = tile_x * tw
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/queries.py
RENAMED
|
@@ -4,12 +4,31 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
6
|
|
|
7
|
-
from ...parser.collision import CollisionPolygon, TilesetCollision
|
|
7
|
+
from ...parser.collision import CollisionPolygon, TileCollisionData, TilesetCollision
|
|
8
8
|
from ..collision.hit import should_collide
|
|
9
9
|
from ..polygon_query import _check_sprite_polygon_offset, get_shape_bounds
|
|
10
10
|
from ..protocols import ICollidable, ICollidableSprite
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
def _resolve_tile_data(
|
|
14
|
+
world,
|
|
15
|
+
tileset_collision: TilesetCollision,
|
|
16
|
+
tile_id: int | None,
|
|
17
|
+
) -> TileCollisionData | None:
|
|
18
|
+
"""Fetch collision data for *tile_id*, GID-routed when a world is attached.
|
|
19
|
+
|
|
20
|
+
With an attached world built via ``from_map(..., use_gids=True)`` the id
|
|
21
|
+
is range-routed against the map's grid resources (decoration tilesets can
|
|
22
|
+
never alias into the collision owner's local keys). Without a world the
|
|
23
|
+
historic literal lookup applies.
|
|
24
|
+
"""
|
|
25
|
+
if tile_id is None:
|
|
26
|
+
return None
|
|
27
|
+
if world is not None:
|
|
28
|
+
return world.resolve_collision(tile_id)
|
|
29
|
+
return tileset_collision.tiles.get(tile_id)
|
|
30
|
+
|
|
31
|
+
|
|
13
32
|
def _collides_at(
|
|
14
33
|
self,
|
|
15
34
|
sprite: ICollidable,
|
|
@@ -35,9 +54,7 @@ def _collides_at(
|
|
|
35
54
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
36
55
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
37
56
|
tile_id = tile_map.get((tile_x, tile_y))
|
|
38
|
-
|
|
39
|
-
continue
|
|
40
|
-
tile_data = tileset_collision.tiles.get(tile_id)
|
|
57
|
+
tile_data = _resolve_tile_data(world, tileset_collision, tile_id)
|
|
41
58
|
if tile_data is None:
|
|
42
59
|
continue
|
|
43
60
|
ox = tile_x * tw
|
|
@@ -72,9 +89,7 @@ def _first_colliding_shape(
|
|
|
72
89
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
73
90
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
74
91
|
tile_id = tile_map.get((tile_x, tile_y))
|
|
75
|
-
|
|
76
|
-
continue
|
|
77
|
-
tile_data = tileset_collision.tiles.get(tile_id)
|
|
92
|
+
tile_data = _resolve_tile_data(world, tileset_collision, tile_id)
|
|
78
93
|
if tile_data is None:
|
|
79
94
|
continue
|
|
80
95
|
ox = tile_x * tw
|
|
@@ -113,9 +128,7 @@ def _collides_at_platformer(
|
|
|
113
128
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
114
129
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
115
130
|
tile_id = tile_map.get((tile_x, tile_y))
|
|
116
|
-
|
|
117
|
-
continue
|
|
118
|
-
tile_data = tileset_collision.tiles.get(tile_id)
|
|
131
|
+
tile_data = _resolve_tile_data(world, tileset_collision, tile_id)
|
|
119
132
|
if tile_data is None:
|
|
120
133
|
continue
|
|
121
134
|
ox = tile_x * tw
|
|
@@ -218,9 +231,7 @@ def _find_walkable_ground_y(
|
|
|
218
231
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
219
232
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
220
233
|
tile_id = tile_map.get((tile_x, tile_y))
|
|
221
|
-
|
|
222
|
-
continue
|
|
223
|
-
tile_data = tileset_collision.tiles.get(tile_id)
|
|
234
|
+
tile_data = _resolve_tile_data(world, tileset_collision, tile_id)
|
|
224
235
|
if tile_data is None:
|
|
225
236
|
continue
|
|
226
237
|
ox = tile_x * tw
|
|
@@ -151,13 +151,19 @@ class CollisionRunner:
|
|
|
151
151
|
tile_x, tile_y = self.get_tile_at(world_x, world_y)
|
|
152
152
|
tile_id = tile_map.get((tile_x, tile_y))
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
world = self._resolve_world(None)
|
|
155
|
+
tile_data = queries._resolve_tile_data(world, tileset_collision, tile_id)
|
|
156
|
+
if tile_data is None:
|
|
155
157
|
return []
|
|
156
158
|
|
|
157
159
|
tile_world_x = tile_x * self._eff_tw
|
|
158
160
|
tile_world_y = tile_y * self._eff_th
|
|
159
161
|
|
|
160
|
-
return
|
|
162
|
+
return [
|
|
163
|
+
shape.transform(tile_world_x, tile_world_y, self.render_scale)
|
|
164
|
+
for shape in tile_data.shapes
|
|
165
|
+
if shape.is_valid()
|
|
166
|
+
]
|
|
161
167
|
|
|
162
168
|
def get_nearby_tile_shapes(
|
|
163
169
|
self,
|
|
@@ -182,12 +188,11 @@ class CollisionRunner:
|
|
|
182
188
|
max_tile_y = int(bottom // th) + margin
|
|
183
189
|
|
|
184
190
|
shapes = []
|
|
191
|
+
world = self._resolve_world(None)
|
|
185
192
|
for tile_y in range(min_tile_y, max_tile_y + 1):
|
|
186
193
|
for tile_x in range(min_tile_x, max_tile_x + 1):
|
|
187
194
|
tile_id = tile_map.get((tile_x, tile_y))
|
|
188
|
-
|
|
189
|
-
continue
|
|
190
|
-
tile_data = tileset_collision.tiles.get(tile_id)
|
|
195
|
+
tile_data = queries._resolve_tile_data(world, tileset_collision, tile_id)
|
|
191
196
|
if tile_data is None:
|
|
192
197
|
continue
|
|
193
198
|
tile_world_x = tile_x * tw
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/navigation/nav_grid.py
RENAMED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import math
|
|
4
4
|
from typing import Dict, List, Optional, Tuple
|
|
5
5
|
|
|
6
|
-
from ...parser.collision import TilesetCollision
|
|
6
|
+
from ...parser.collision import TileCollisionData, TilesetCollision
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class NavGrid:
|
|
@@ -15,14 +15,15 @@ class NavGrid:
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
__slots__ = (
|
|
18
|
-
"tile_map",
|
|
19
|
-
"tileset_collision",
|
|
20
|
-
"tile_size",
|
|
21
|
-
"_eff_tw",
|
|
22
18
|
"_eff_th",
|
|
23
|
-
"
|
|
19
|
+
"_eff_tw",
|
|
20
|
+
"_gid_resolver",
|
|
24
21
|
"_height",
|
|
25
22
|
"_walkable",
|
|
23
|
+
"_width",
|
|
24
|
+
"tile_map",
|
|
25
|
+
"tile_size",
|
|
26
|
+
"tileset_collision",
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
def __init__(
|
|
@@ -32,9 +33,14 @@ class NavGrid:
|
|
|
32
33
|
tile_size: Tuple[int, int],
|
|
33
34
|
render_scale: float = 1.0,
|
|
34
35
|
map_size: Optional[Tuple[int, int]] = None,
|
|
36
|
+
gid_resolver=None,
|
|
35
37
|
) -> None:
|
|
36
38
|
self.tile_map = tile_map
|
|
37
39
|
self.tileset_collision = tileset_collision
|
|
40
|
+
# Optional callable (int) -> TileCollisionData | None. Pass
|
|
41
|
+
# ``physics_world.resolve_collision`` for GID-routed maps so ids from
|
|
42
|
+
# non-collidable grid tilesets resolve to None instead of aliasing.
|
|
43
|
+
self._gid_resolver = gid_resolver
|
|
38
44
|
self.tile_size = tile_size
|
|
39
45
|
self._eff_tw = tile_size[0] * render_scale
|
|
40
46
|
self._eff_th = tile_size[1] * render_scale
|
|
@@ -52,11 +58,15 @@ class NavGrid:
|
|
|
52
58
|
|
|
53
59
|
self._walkable = [[self._is_tile_walkable(x, y) for x in range(self._width)] for y in range(self._height)]
|
|
54
60
|
|
|
55
|
-
def
|
|
56
|
-
tile_id = self.tile_map.get((tx, ty))
|
|
61
|
+
def _resolve(self, tile_id):
|
|
57
62
|
if tile_id is None:
|
|
58
|
-
return
|
|
59
|
-
|
|
63
|
+
return None
|
|
64
|
+
if self._gid_resolver is not None:
|
|
65
|
+
return self._gid_resolver(tile_id)
|
|
66
|
+
return self.tileset_collision.tiles.get(tile_id)
|
|
67
|
+
|
|
68
|
+
def _is_tile_walkable(self, tx: int, ty: int) -> bool:
|
|
69
|
+
tile_data = self._resolve(self.tile_map.get((tx, ty)))
|
|
60
70
|
if tile_data is None:
|
|
61
71
|
return True
|
|
62
72
|
for poly in tile_data.shapes:
|
|
@@ -78,10 +88,7 @@ class NavGrid:
|
|
|
78
88
|
return self._walkable[ty][tx]
|
|
79
89
|
|
|
80
90
|
def is_one_way(self, tx: int, ty: int) -> bool:
|
|
81
|
-
|
|
82
|
-
if tile_id is None:
|
|
83
|
-
return False
|
|
84
|
-
tile_data = self.tileset_collision.tiles.get(tile_id)
|
|
91
|
+
tile_data = self._resolve(self.tile_map.get((tx, ty)))
|
|
85
92
|
if tile_data is None:
|
|
86
93
|
return False
|
|
87
94
|
has_one_way = False
|
|
@@ -104,6 +111,10 @@ class NavGrid:
|
|
|
104
111
|
new._width = self._width
|
|
105
112
|
new._height = self._height
|
|
106
113
|
new._walkable = [row[:] for row in self._walkable]
|
|
114
|
+
# _gid_resolver lives in __slots__: dropping it here made every
|
|
115
|
+
# derived grid raise AttributeError once _resolve() ran (e.g. via
|
|
116
|
+
# is_one_way), and silently lost GID routing before that.
|
|
117
|
+
new._gid_resolver = self._gid_resolver
|
|
107
118
|
return new
|
|
108
119
|
|
|
109
120
|
def erode(self, margin: float) -> NavGrid:
|
|
@@ -155,16 +166,31 @@ class NavGrid:
|
|
|
155
166
|
sprite_height: Optional[float] = None,
|
|
156
167
|
render_scale: float = 1.0,
|
|
157
168
|
map_size: Optional[Tuple[int, int]] = None,
|
|
158
|
-
cache: Optional[
|
|
169
|
+
cache: Optional[dict[tuple[float, bool], NavGrid]] = None,
|
|
170
|
+
gid_resolver=None,
|
|
159
171
|
) -> NavGrid:
|
|
172
|
+
"""Build (or fetch from *cache*) an eroded grid for one entity size.
|
|
173
|
+
|
|
174
|
+
The cache is scoped to a single map/routing context and is keyed by
|
|
175
|
+
``(margin, resolver_is_literal)`` so literal and GID-routed grids can
|
|
176
|
+
never cross-contaminate through a shared cache dict.
|
|
177
|
+
"""
|
|
160
178
|
tw = tile_size[0] * render_scale
|
|
161
179
|
size = max(sprite_width, sprite_height if sprite_height is not None else sprite_width)
|
|
162
180
|
margin = (size / 2.0) / tw
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
181
|
+
key = (margin, gid_resolver is None)
|
|
182
|
+
if cache is not None and key in cache:
|
|
183
|
+
return cache[key]
|
|
184
|
+
nav = cls(
|
|
185
|
+
tile_map,
|
|
186
|
+
tileset_collision,
|
|
187
|
+
tile_size,
|
|
188
|
+
render_scale,
|
|
189
|
+
map_size,
|
|
190
|
+
gid_resolver=gid_resolver,
|
|
191
|
+
).erode(margin)
|
|
166
192
|
if cache is not None:
|
|
167
|
-
cache[
|
|
193
|
+
cache[key] = nav
|
|
168
194
|
return nav
|
|
169
195
|
|
|
170
196
|
def get_neighbors(self, tx: int, ty: int, *, diagonals: bool = False) -> List[Tuple[int, int]]:
|
|
@@ -9,14 +9,18 @@ resolves movement against the world's tiles AND bodies uniformly.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
|
13
14
|
|
|
14
|
-
from ..parser.collision import TilesetCollision
|
|
15
|
+
from ..parser.collision import TileCollisionData, TilesetCollision
|
|
15
16
|
from .body import Body
|
|
16
17
|
from .collision.hit import check_collision
|
|
17
18
|
from .map_loader import TilemapData
|
|
18
19
|
from .protocols import ICollidable
|
|
19
20
|
|
|
21
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
22
|
+
from ..parser.map_parse import ParsedTileset
|
|
23
|
+
|
|
20
24
|
|
|
21
25
|
class PhysicsWorld:
|
|
22
26
|
"""A space containing a tile layer and solid bodies."""
|
|
@@ -45,6 +49,11 @@ class PhysicsWorld:
|
|
|
45
49
|
self.tile_size = tuple(tile_size)
|
|
46
50
|
self.render_scale = render_scale
|
|
47
51
|
self.bodies: List[Body] = []
|
|
52
|
+
# GID routing (see `resolve_collision`): (firstgid, tile_count, stem)
|
|
53
|
+
# for every *grid* resource of the source map, plus the stem that the
|
|
54
|
+
# single collision file belongs to. Empty => literal local lookups.
|
|
55
|
+
self._grid_ranges: List[Tuple[int, int, str]] = []
|
|
56
|
+
self._collision_owner_stem: Optional[str] = None
|
|
48
57
|
if self.tile_map and self.tileset_collision is None:
|
|
49
58
|
raise ValueError(
|
|
50
59
|
"tile_map requires tileset_collision: a world with solid "
|
|
@@ -91,12 +100,68 @@ class PhysicsWorld:
|
|
|
91
100
|
"tiles cannot resolve movement without collision data"
|
|
92
101
|
)
|
|
93
102
|
world.tileset_collision = tileset_collision
|
|
103
|
+
if use_gids:
|
|
104
|
+
world._capture_grid_ownership(tilemap_data, tileset_collision)
|
|
94
105
|
return world
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
|
|
108
|
+
def _capture_grid_ownership(
|
|
109
|
+
self, map_data: TilemapData, tileset_collision: TilesetCollision
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Record every grid resource's GID range and the collision owner.
|
|
99
112
|
|
|
113
|
+
Only ``type=="tile"`` resources participate; object tilesets are
|
|
114
|
+
never part of the physics grid. Routing activates only when the
|
|
115
|
+
collision file's ``tileset_name`` stem-matches one of them —
|
|
116
|
+
otherwise lookups stay literal (legacy / pre-merged files).
|
|
117
|
+
"""
|
|
118
|
+
owner = tileset_collision.tileset_name
|
|
119
|
+
ranges: List[Tuple[int, int, str]] = []
|
|
120
|
+
matched: Optional[str] = None
|
|
121
|
+
for ts in map_data.parsed.tilesets:
|
|
122
|
+
if getattr(ts, "type", "tile") != "tile":
|
|
123
|
+
continue
|
|
124
|
+
stem = Path(ts.path).stem
|
|
125
|
+
ranges.append((ts.firstgid, ts.tile_count, stem))
|
|
126
|
+
if stem == owner and matched is None:
|
|
127
|
+
matched = stem
|
|
128
|
+
if matched is not None:
|
|
129
|
+
self._grid_ranges = ranges
|
|
130
|
+
self._collision_owner_stem = matched
|
|
131
|
+
|
|
132
|
+
def resolve_collision(self, tile_id: int) -> Optional[TileCollisionData]:
|
|
133
|
+
"""Resolve a possibly-global tile id to its collision data.
|
|
134
|
+
|
|
135
|
+
With GID routing active (``from_map(..., use_gids=True)`` plus a
|
|
136
|
+
stem-matched collision file):
|
|
137
|
+
|
|
138
|
+
1. find the grid resource whose ``[firstgid, firstgid+count)``
|
|
139
|
+
window contains *tile_id*;
|
|
140
|
+
2. if that resource is **not** the collision owner → ``None``
|
|
141
|
+
(decoration grids are never solid, no cross-set aliasing);
|
|
142
|
+
3. else translate ``tile_id - firstgid`` to the local key.
|
|
143
|
+
|
|
144
|
+
Without routing (literal mode, or a pre-merged GID-keyed
|
|
145
|
+
collision), falls back to a plain dictionary lookup — identical
|
|
146
|
+
to historic behaviour.
|
|
147
|
+
"""
|
|
148
|
+
if self._grid_ranges and self.tileset_collision is not None:
|
|
149
|
+
for firstgid, count, stem in self._grid_ranges:
|
|
150
|
+
if firstgid <= tile_id < firstgid + count:
|
|
151
|
+
if stem != self._collision_owner_stem:
|
|
152
|
+
return None
|
|
153
|
+
return self.tileset_collision.tiles.get(tile_id - firstgid)
|
|
154
|
+
return None
|
|
155
|
+
if self.tileset_collision is None:
|
|
156
|
+
return None
|
|
157
|
+
return self.tileset_collision.tiles.get(tile_id)
|
|
158
|
+
|
|
159
|
+
def has_collision_gid(self, tile_id: int) -> bool:
|
|
160
|
+
"""Convenience boolean form of :meth:`resolve_collision`."""
|
|
161
|
+
data = self.resolve_collision(tile_id)
|
|
162
|
+
return data is not None and data.has_collision()
|
|
163
|
+
|
|
164
|
+
# body
|
|
100
165
|
def add_body(self, body: Body) -> None:
|
|
101
166
|
"""Add a body to the world. Adding the same body twice is a no-op."""
|
|
102
167
|
if body not in self.bodies:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tilemap-parser
|
|
3
|
-
Version: 5.0
|
|
3
|
+
Version: 5.1.0
|
|
4
4
|
Summary: Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime.
|
|
5
5
|
Author: tilemap parser contributors
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""GID range-routing tests: single grid collision file, multi-tileset maps.
|
|
2
|
+
|
|
3
|
+
Regression ground: with ``use_gids=True`` and a collision file whose
|
|
4
|
+
``firstgid != 0``, literal lookups silently missed (e.g. only 1 of many
|
|
5
|
+
collidable tiles resolved). Naive per-lookup offset subtraction also aliases:
|
|
6
|
+
a decoration grid whose GID window lands inside the owner's *local*
|
|
7
|
+
key space (e.g. gid ``1813 - 90 = 1723``) would become falsely solid
|
|
8
|
+
the moment the owner gains that local key.
|
|
9
|
+
|
|
10
|
+
Routing must therefore resolve ownership against the map's grid
|
|
11
|
+
resource table BEFORE translating.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from tilemap_parser.parser.collision import (
|
|
20
|
+
CollisionPolygon,
|
|
21
|
+
TileCollisionData,
|
|
22
|
+
TilesetCollision,
|
|
23
|
+
)
|
|
24
|
+
from tilemap_parser.runtime.navigation.nav_grid import NavGrid
|
|
25
|
+
from tilemap_parser.runtime.world import PhysicsWorld
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# builders
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
def solid(x=8.0, y=8.0):
|
|
32
|
+
return CollisionPolygon(vertices=[(0, 0), (x, 0), (x, y), (0, y)])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def make_col(name: str, keys: dict[int, list]) -> TilesetCollision:
|
|
36
|
+
return TilesetCollision(
|
|
37
|
+
tileset_name=name,
|
|
38
|
+
tile_size=(8, 8),
|
|
39
|
+
tiles={k: TileCollisionData(tile_id=k, shapes=v) for k, v in keys.items()},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def stamp(world: PhysicsWorld, ranges, owner_stem: str | None) -> None:
|
|
44
|
+
"""Simulate what from_map captures from map_data.parsed.tilesets."""
|
|
45
|
+
world._grid_ranges = list(ranges)
|
|
46
|
+
world._collision_owner_stem = owner_stem
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
RANGES = [
|
|
51
|
+
(0, 90, "cave"), # decoration grid
|
|
52
|
+
(90, 1750, "tileset"), # collision owner
|
|
53
|
+
(1840, 56, "cave_hole"), # decoration grid
|
|
54
|
+
(1896, 1296, "bg"), # decoration grid
|
|
55
|
+
]
|
|
56
|
+
COL_KEYS = {1: [solid()], 2: [solid()], 575: [solid()], 981: [solid()]}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.fixture()
|
|
60
|
+
def routed_world() -> PhysicsWorld:
|
|
61
|
+
w = PhysicsWorld(
|
|
62
|
+
tile_map={(0, 6): 92, (3, 6): 1813},
|
|
63
|
+
tileset_collision=make_col("tileset", COL_KEYS),
|
|
64
|
+
tile_size=(8, 8),
|
|
65
|
+
render_scale=3.0,
|
|
66
|
+
)
|
|
67
|
+
stamp(w, RANGES, "tileset")
|
|
68
|
+
return w
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestRoutingMatrix:
|
|
72
|
+
def test_owner_gid_translates_to_local(self, routed_world):
|
|
73
|
+
assert routed_world.resolve_collision(92) is not None # local 2
|
|
74
|
+
assert routed_world.has_collision_gid(92) is True
|
|
75
|
+
|
|
76
|
+
def test_decoration_grid_never_solids__alias_regression(self, routed_world):
|
|
77
|
+
# gid 1813 belongs to cave_hole; naive `gid-90` would hit local 1723.
|
|
78
|
+
assert routed_world.resolve_collision(1813) is None
|
|
79
|
+
assert routed_world.has_collision_gid(1813) is False
|
|
80
|
+
|
|
81
|
+
def test_other_owner_windows_rejected(self, routed_world):
|
|
82
|
+
assert routed_world.resolve_collision(5) is None # cave window
|
|
83
|
+
assert routed_world.resolve_collision(1900) is None # bg window
|
|
84
|
+
assert routed_world.resolve_collision(1840 + 4) is None # hole window
|
|
85
|
+
|
|
86
|
+
def test_unmapped_gap_is_none(self, routed_world):
|
|
87
|
+
# 90+1750+56+1296 == 3192: first id past every grid window
|
|
88
|
+
assert routed_world.resolve_collision(3192) is None
|
|
89
|
+
assert routed_world.resolve_collision(5000) is None
|
|
90
|
+
|
|
91
|
+
def test_owner_local_keys_without_collision_shapes(self, routed_world):
|
|
92
|
+
# owner-local 50 exists as a gid but has no entry -> None, not crash
|
|
93
|
+
assert routed_world.resolve_collision(90 + 50) is None
|
|
94
|
+
|
|
95
|
+
def test_boundary_edges(self, routed_world):
|
|
96
|
+
assert routed_world.resolve_collision(89) is None # last cave gid
|
|
97
|
+
# inclusive owner lower edge: local 0 exists in the window but has no
|
|
98
|
+
# collision entry -> None (must not fall through to another range)
|
|
99
|
+
assert routed_world.resolve_collision(90) is None
|
|
100
|
+
assert routed_world.resolve_collision(90 + 1749) is None # last owner gid, keyless
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestLiteralFallback:
|
|
104
|
+
def test_no_routing_keeps_literal_lookup(self):
|
|
105
|
+
w = PhysicsWorld(
|
|
106
|
+
tile_map={(0, 0): 2},
|
|
107
|
+
tileset_collision=make_col("tileset", COL_KEYS),
|
|
108
|
+
tile_size=(8, 8),
|
|
109
|
+
)
|
|
110
|
+
assert w._grid_ranges == []
|
|
111
|
+
assert w.resolve_collision(2) is not None
|
|
112
|
+
assert w.has_collision_gid(2) is True
|
|
113
|
+
|
|
114
|
+
def test_merged_gid_keyed_file_stays_literal(self):
|
|
115
|
+
# merge() output: keys ARE gids, no stem match possible
|
|
116
|
+
merged = make_col("merged", {92: [solid()]})
|
|
117
|
+
w = PhysicsWorld(
|
|
118
|
+
tile_map={(0, 6): 92}, tileset_collision=merged, tile_size=(8, 8)
|
|
119
|
+
)
|
|
120
|
+
stamp(w, [], None)
|
|
121
|
+
assert w.resolve_collision(92) is not None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class TestFromMapCapture:
|
|
125
|
+
def _payload(self, tmp: Path):
|
|
126
|
+
(tmp / "a.png").write_bytes(b"x") # existence not required (skip_missing_images)
|
|
127
|
+
(tmp / "b.png").write_bytes(b"x")
|
|
128
|
+
return {
|
|
129
|
+
"meta": {
|
|
130
|
+
"tile_size": "8;8",
|
|
131
|
+
"map_size": "4;4",
|
|
132
|
+
"offset": "0;0",
|
|
133
|
+
"render_scale": 1.0,
|
|
134
|
+
"version": "1.1",
|
|
135
|
+
},
|
|
136
|
+
"resources": {
|
|
137
|
+
"tilesets": [
|
|
138
|
+
{"path": str(tmp / "a.png"), "type": "tile", "tile_count": 4, "firstgid": 0},
|
|
139
|
+
{"path": str(tmp / "b.png"), "type": "tile", "tile_count": 4, "firstgid": 4},
|
|
140
|
+
{"path": str(tmp / "c.png"), "type": "object", "tile_count": 2, "firstgid": 8},
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
"project_state": {"rules": []},
|
|
144
|
+
"data": {
|
|
145
|
+
"layers": [
|
|
146
|
+
{
|
|
147
|
+
"name": "L", "type": "tile", "visible": True,
|
|
148
|
+
"tiles": {
|
|
149
|
+
"0;0": {"pos": "0;0", "ttype": 1, "variant": 0, "gid": 4},
|
|
150
|
+
"1;0": {"pos": "1;0", "ttype": 0, "variant": 1, "gid": 1},
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
def test_from_map_auto_captures_owner_and_routes(self, tmp_path):
|
|
158
|
+
from tilemap_parser.runtime.map_loader import load_map
|
|
159
|
+
|
|
160
|
+
payload_path = tmp_path / "m.json"
|
|
161
|
+
payload_path.write_text(json.dumps(self._payload(tmp_path)))
|
|
162
|
+
|
|
163
|
+
map_data = load_map(payload_path)
|
|
164
|
+
col = make_col("b", {0: [solid()]}) # local-keyed file for resource b
|
|
165
|
+
|
|
166
|
+
world = PhysicsWorld.from_map(map_data, col, use_gids=True)
|
|
167
|
+
|
|
168
|
+
# gid 4 -> owner b -> local 0 -> solid; gid 1 -> owner a -> decor
|
|
169
|
+
assert world.has_collision_gid(4) is True
|
|
170
|
+
assert world.has_collision_gid(1) is False
|
|
171
|
+
# object resource c never participates even though its window follows
|
|
172
|
+
assert world.resolve_collision(8) is None
|
|
173
|
+
|
|
174
|
+
def test_from_map_literal_mode_untouched(self, tmp_path):
|
|
175
|
+
from tilemap_parser.runtime.map_loader import load_map
|
|
176
|
+
|
|
177
|
+
payload_path = tmp_path / "m.json"
|
|
178
|
+
payload_path.write_text(json.dumps(self._payload(tmp_path)))
|
|
179
|
+
map_data = load_map(payload_path)
|
|
180
|
+
col = make_col("b", {0: [solid(), solid()]})
|
|
181
|
+
|
|
182
|
+
world = PhysicsWorld.from_map(map_data, col, use_gids=False)
|
|
183
|
+
assert world._grid_ranges == []
|
|
184
|
+
assert world.resolve_collision(0) is not None # literal local id
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# NavGrid resolver propagation (copy / erode / for_entity)
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
class TestNavGridResolverPropagation:
|
|
192
|
+
def _world(self):
|
|
193
|
+
world = PhysicsWorld(
|
|
194
|
+
tile_map={(0, 6): 92, (3, 6): 1813},
|
|
195
|
+
tileset_collision=make_col("tileset", COL_KEYS),
|
|
196
|
+
tile_size=(8, 8),
|
|
197
|
+
)
|
|
198
|
+
stamp(world, RANGES, "tileset")
|
|
199
|
+
return world
|
|
200
|
+
|
|
201
|
+
def test_copy_preserves_resolver_and_does_not_crash(self):
|
|
202
|
+
w = self._world()
|
|
203
|
+
base = NavGrid(w.tile_map, w.tileset_collision, w.tile_size, gid_resolver=w.resolve_collision)
|
|
204
|
+
|
|
205
|
+
derived = base.copy() # used to lack the slot entirely
|
|
206
|
+
assert derived._gid_resolver is not None
|
|
207
|
+
|
|
208
|
+
# is_one_way hits _resolve(): raised AttributeError on copies before
|
|
209
|
+
derived.is_one_way(0, 6)
|
|
210
|
+
# routed semantics survive the copy: owner solid, decoration not
|
|
211
|
+
assert derived.is_solid(0, 6) is True # gid 92 -> owner local 2
|
|
212
|
+
assert derived.is_solid(3, 6) is False # gid 1813 -> other grid resource
|
|
213
|
+
|
|
214
|
+
eroded = base.erode(1.0)
|
|
215
|
+
eroded.is_one_way(0, 6)
|
|
216
|
+
assert eroded._gid_resolver is not None
|
|
217
|
+
|
|
218
|
+
def test_literal_grid_unchanged_without_resolver(self):
|
|
219
|
+
grid = NavGrid({(0, 0): 2}, make_col("t", COL_KEYS), (8, 8))
|
|
220
|
+
assert grid.copy()._gid_resolver is None
|
|
221
|
+
assert grid.is_solid(0, 0) is True # literal local key 2
|
|
222
|
+
grid.is_one_way(0, 0) # must not raise
|
|
223
|
+
|
|
224
|
+
def test_cache_keys_separate_resolver_modes(self):
|
|
225
|
+
w = self._world()
|
|
226
|
+
cache: dict = {}
|
|
227
|
+
|
|
228
|
+
literal = NavGrid.for_entity(
|
|
229
|
+
w.tile_map, w.tileset_collision, w.tile_size,
|
|
230
|
+
sprite_width=10.0, sprite_height=10.0, cache=cache,
|
|
231
|
+
)
|
|
232
|
+
routed = NavGrid.for_entity(
|
|
233
|
+
w.tile_map, w.tileset_collision, w.tile_size,
|
|
234
|
+
sprite_width=10.0, sprite_height=10.0, cache=cache,
|
|
235
|
+
gid_resolver=w.resolve_collision,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
assert len(cache) == 2, "modes must occupy distinct cache slots"
|
|
239
|
+
assert literal is not routed
|
|
240
|
+
assert literal._gid_resolver is None
|
|
241
|
+
assert routed._gid_resolver is not None
|
|
242
|
+
# same inputs, different semantics: owner tile solid only when routed
|
|
243
|
+
assert literal.is_solid(0, 6) is False # literal lookup of gid 92 misses
|
|
244
|
+
assert routed.is_solid(0, 6) is True # routed resolves to local 2
|
|
245
|
+
|
|
246
|
+
def test_cache_reuses_entry_within_same_mode(self):
|
|
247
|
+
w = self._world()
|
|
248
|
+
cache: dict = {}
|
|
249
|
+
a = NavGrid.for_entity(
|
|
250
|
+
w.tile_map, w.tileset_collision, w.tile_size,
|
|
251
|
+
sprite_width=16.0, cache=cache, gid_resolver=w.resolve_collision,
|
|
252
|
+
)
|
|
253
|
+
b = NavGrid.for_entity(
|
|
254
|
+
w.tile_map, w.tileset_collision, w.tile_size,
|
|
255
|
+
sprite_width=16.0, cache=cache, gid_resolver=w.resolve_collision,
|
|
256
|
+
)
|
|
257
|
+
assert a is b, "same margin + same mode must hit the cached grid"
|
|
258
|
+
|
|
259
|
+
lit_a = NavGrid.for_entity(
|
|
260
|
+
w.tile_map, w.tileset_collision, w.tile_size,
|
|
261
|
+
sprite_width=16.0, cache=cache,
|
|
262
|
+
)
|
|
263
|
+
lit_b = NavGrid.for_entity(
|
|
264
|
+
w.tile_map, w.tileset_collision, w.tile_size,
|
|
265
|
+
sprite_width=16.0, cache=cache,
|
|
266
|
+
)
|
|
267
|
+
assert lit_a is lit_b and lit_a is not a
|
|
268
|
+
assert len(cache) == 2
|
|
269
|
+
|
|
270
|
+
def test_for_entity_forwards_resolver(self):
|
|
271
|
+
w = self._world()
|
|
272
|
+
grid = NavGrid.for_entity(
|
|
273
|
+
w.tile_map,
|
|
274
|
+
w.tileset_collision,
|
|
275
|
+
w.tile_size,
|
|
276
|
+
sprite_width=10.0,
|
|
277
|
+
sprite_height=10.0,
|
|
278
|
+
gid_resolver=w.resolve_collision,
|
|
279
|
+
)
|
|
280
|
+
assert grid._gid_resolver is not None
|
|
281
|
+
# owner tile stays solid under entity clearance; decoration never was
|
|
282
|
+
assert grid.is_solid(0, 6) is True
|
|
283
|
+
assert grid.is_solid(3, 6) is False
|
|
284
|
+
|
|
285
|
+
# parity: identical construction without a resolver would alias the
|
|
286
|
+
# decoration gid into local 1723 (keyless today, so also False) --
|
|
287
|
+
# assert via direct literal probe that routing, not luck, decides.
|
|
288
|
+
literal = NavGrid.for_entity(
|
|
289
|
+
w.tile_map, w.tileset_collision, w.tile_size, sprite_width=10.0
|
|
290
|
+
)
|
|
291
|
+
assert literal._gid_resolver is None
|
|
292
|
+
assert literal.is_solid(0, 6) is False # literal lookup of gid 92 misses
|
|
293
|
+
assert grid.is_solid(0, 6) is True # routed lookup resolves it
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/animation_player.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/collision/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/collision/manager.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/collision/shapes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/__init__.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/movement/grounded.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/navigation/__init__.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/navigation/path_follower.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/navigation/pathfinder.py
RENAMED
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser/runtime/object_collision.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tilemap_parser-5.0.4 → tilemap_parser-5.1.0}/src/tilemap_parser.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|