tilemap-parser 4.2.2__tar.gz → 4.2.4__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-4.2.2 → tilemap_parser-4.2.4}/PKG-INFO +1 -1
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/pyproject.toml +1 -1
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/__init__.py +1 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/parser/map_parse.py +5 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/parser/tmx_converter.py +1 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/__init__.py +2 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/map_object.py +3 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/object_collision.py +3 -10
- tilemap_parser-4.2.4/src/tilemap_parser/runtime/protocols.py +37 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/renderer.py +41 -28
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/tile_collision.py +12 -11
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser.egg-info/PKG-INFO +1 -1
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser.egg-info/SOURCES.txt +2 -0
- tilemap_parser-4.2.4/tests/test_tile_layer_renderer_y_sort.py +348 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/LICENSE +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/README.md +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/setup.cfg +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/parser/__init__.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/parser/animation.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/parser/collision.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/parser/collision_loader.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/parser/node_parse.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/parser/particle.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/animation_player.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/area_node.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/camera.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/collision_cache.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/map_loader.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/particles.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/utils/__init__.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/utils/geometry.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser.egg-info/requires.txt +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser.egg-info/top_level.txt +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_camera.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_collision.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_geometry.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_gid_collision.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_integration_animation.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_integration_collision.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_integration_map_loader.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_map_loader.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_map_object.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_move_grounded.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_object_collision.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_object_surfaces.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_particle_emitter.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_render_scale.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_tile_collision.py +0 -0
- {tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/tests/test_tmx_converter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tilemap-parser
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.4
|
|
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 = "4.2.
|
|
7
|
+
version = "4.2.4"
|
|
8
8
|
description = "Standalone parser/loader for tilemap-editor JSON maps, sprite animations, and collision detection runtime."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -159,6 +159,8 @@ class ParsedLayer:
|
|
|
159
159
|
locked: bool
|
|
160
160
|
opacity: float
|
|
161
161
|
z_index: int
|
|
162
|
+
y_sort: bool = False
|
|
163
|
+
y_sort_origin: int = 0
|
|
162
164
|
properties: JsonDict = field(default_factory=dict)
|
|
163
165
|
tiles: Dict[Point, ParsedTile] = field(default_factory=dict)
|
|
164
166
|
objects: Dict[int, ParsedObject] = field(default_factory=dict)
|
|
@@ -285,6 +287,8 @@ def _parse_layer(layer_obj: JsonDict, layer_id: int, ctx: str) -> ParsedLayer:
|
|
|
285
287
|
locked=_coerce_bool(layer_obj.get("locked", False), f"{ctx}.locked"),
|
|
286
288
|
opacity=_coerce_float(layer_obj.get("opacity", 1.0), f"{ctx}.opacity"),
|
|
287
289
|
z_index=_coerce_int(layer_obj.get("z_index", layer_id), f"{ctx}.z_index"),
|
|
290
|
+
y_sort=_coerce_bool(layer_obj.get("y_sort", False), f"{ctx}.y_sort"),
|
|
291
|
+
y_sort_origin=layer_obj.get("y_sort_origin", 0),
|
|
288
292
|
properties=_optional_dict(layer_obj.get("properties"), f"{ctx}.properties")
|
|
289
293
|
or {},
|
|
290
294
|
)
|
|
@@ -409,6 +413,7 @@ def _expand_ongrid_to_layer(data_obj: JsonDict, ctx: str) -> List[ParsedLayer]:
|
|
|
409
413
|
locked=False,
|
|
410
414
|
opacity=1.0,
|
|
411
415
|
z_index=0,
|
|
416
|
+
y_sort=False, y_sort_origin=0,
|
|
412
417
|
properties={},
|
|
413
418
|
)
|
|
414
419
|
for loc_str, tile_data in raw_ongrid.items():
|
|
@@ -18,6 +18,7 @@ from .tile_collision import (
|
|
|
18
18
|
MovementMode,
|
|
19
19
|
rect_vs_tilemap,
|
|
20
20
|
)
|
|
21
|
+
from .protocols import ICollidable
|
|
21
22
|
from .map_loader import TilemapData, load_map
|
|
22
23
|
from .object_collision import (
|
|
23
24
|
CollisionHit,
|
|
@@ -45,6 +46,7 @@ __all__ = [
|
|
|
45
46
|
"CollisionHit",
|
|
46
47
|
"CollisionResult",
|
|
47
48
|
"CollisionRunner",
|
|
49
|
+
"ICollidable",
|
|
48
50
|
"ICollidableObject",
|
|
49
51
|
"ICollidableSprite",
|
|
50
52
|
"LayerRenderStats",
|
|
@@ -56,6 +56,7 @@ class MapObject:
|
|
|
56
56
|
"collision_shapes",
|
|
57
57
|
"collision_layer",
|
|
58
58
|
"collision_mask",
|
|
59
|
+
"y_sort_origin",
|
|
59
60
|
)
|
|
60
61
|
|
|
61
62
|
def __init__(
|
|
@@ -68,6 +69,7 @@ class MapObject:
|
|
|
68
69
|
collision_shapes: Optional[List[CollisionPolygon]] = None,
|
|
69
70
|
collision_layer: int = 1,
|
|
70
71
|
collision_mask: int = 0xFFFFFFFF,
|
|
72
|
+
y_sort_origin: Optional[int] = None,
|
|
71
73
|
) -> None:
|
|
72
74
|
self.x = x
|
|
73
75
|
self.y = y
|
|
@@ -76,6 +78,7 @@ class MapObject:
|
|
|
76
78
|
self.collision_shape = self.collision_shapes[0]
|
|
77
79
|
self.collision_layer = collision_layer
|
|
78
80
|
self.collision_mask = collision_mask
|
|
81
|
+
self.y_sort_origin = y_sort_origin
|
|
79
82
|
|
|
80
83
|
|
|
81
84
|
def _resolve_object_collision_filename(tileset_path: Union[str, Path]) -> str:
|
{tilemap_parser-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/object_collision.py
RENAMED
|
@@ -33,27 +33,20 @@ from ..utils.geometry import (
|
|
|
33
33
|
rect_vs_circle,
|
|
34
34
|
rect_vs_rect,
|
|
35
35
|
)
|
|
36
|
+
from .protocols import ICollidable
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
class ICollidableObject(Protocol):
|
|
39
|
+
class ICollidableObject(ICollidable, Protocol):
|
|
39
40
|
"""
|
|
40
41
|
Protocol for objects that can collide.
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
x: World X position
|
|
44
|
-
y: World Y position
|
|
45
|
-
collision_shape: Shape for collision
|
|
46
|
-
(RectangleShape, CircleShape, CapsuleShape, or CollisionPolygon)
|
|
43
|
+
All required attributes (x, y, collision_shape) are inherited from ICollidable.
|
|
47
44
|
|
|
48
45
|
Optional attributes (with defaults):
|
|
49
46
|
collision_layer: Layer this object is on (default: 1)
|
|
50
47
|
collision_mask: Layers to collide with (default: 0xFFFFFFFF)
|
|
51
48
|
"""
|
|
52
49
|
|
|
53
|
-
x: float
|
|
54
|
-
y: float
|
|
55
|
-
collision_shape: Union[RectangleShape, CircleShape, CapsuleShape, CollisionPolygon]
|
|
56
|
-
|
|
57
50
|
|
|
58
51
|
@dataclass(slots=True)
|
|
59
52
|
class CollisionHit:
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
import pygame
|
|
6
|
+
from pygame import Surface
|
|
7
|
+
|
|
8
|
+
from ..parser.collision import (
|
|
9
|
+
CapsuleShape,
|
|
10
|
+
CircleShape,
|
|
11
|
+
CollisionPolygon,
|
|
12
|
+
RectangleShape,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ExtraObject(Protocol):
|
|
17
|
+
surface: Surface | None
|
|
18
|
+
x: float
|
|
19
|
+
y: float
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ICollidable(Protocol):
|
|
23
|
+
"""Base protocol for anything with a world position and collision shape.
|
|
24
|
+
|
|
25
|
+
This is the minimal interface for participating in collision detection.
|
|
26
|
+
All collidable protocols (ICollidableSprite, ICollidableObject) extend this.
|
|
27
|
+
|
|
28
|
+
Required attributes:
|
|
29
|
+
x (float): World X position
|
|
30
|
+
y (float): World Y position
|
|
31
|
+
collision_shape: Shape used for collision detection
|
|
32
|
+
(RectangleShape, CircleShape, CapsuleShape, or CollisionPolygon)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
x: float
|
|
36
|
+
y: float
|
|
37
|
+
collision_shape: RectangleShape | CircleShape | CapsuleShape | CollisionPolygon
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Sequence
|
|
3
4
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Dict, List, Optional, Tuple, Union
|
|
5
5
|
|
|
6
6
|
import pygame
|
|
7
7
|
from pygame import Rect, Surface, transform
|
|
8
8
|
|
|
9
9
|
from .map_loader import TilemapData
|
|
10
|
+
from .protocols import ExtraObject
|
|
10
11
|
|
|
11
12
|
CHUNK_SIZE = 32
|
|
12
13
|
|
|
@@ -19,19 +20,15 @@ class LayerRenderStats:
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class TileLayerRenderer:
|
|
22
|
-
def __init__(
|
|
23
|
-
self, data: TilemapData, *, include_hidden_layers: bool = False
|
|
24
|
-
) -> None:
|
|
23
|
+
def __init__(self, data: TilemapData, *, include_hidden_layers: bool = False) -> None:
|
|
25
24
|
self.data = data
|
|
26
|
-
self.tile_layers = data.get_tile_layers_dict(
|
|
27
|
-
include_hidden=include_hidden_layers
|
|
28
|
-
)
|
|
25
|
+
self.tile_layers = data.get_tile_layers_dict(include_hidden=include_hidden_layers)
|
|
29
26
|
self._sorted_layer_ids = sorted(
|
|
30
27
|
self.tile_layers.keys(),
|
|
31
28
|
key=lambda lid: (self.tile_layers[lid].z_index, lid),
|
|
32
29
|
)
|
|
33
30
|
|
|
34
|
-
self._variant_cache:
|
|
31
|
+
self._variant_cache: dict[tuple[int, int], Surface | None] = {}
|
|
35
32
|
|
|
36
33
|
self._tile_w, self._tile_h = data.tile_size
|
|
37
34
|
self._rs = data.render_scale
|
|
@@ -45,7 +42,7 @@ class TileLayerRenderer:
|
|
|
45
42
|
f"got tile_size=({self._tile_w}, {self._tile_h}) render_scale={self._rs}"
|
|
46
43
|
)
|
|
47
44
|
|
|
48
|
-
self._tileset_animations:
|
|
45
|
+
self._tileset_animations: dict[int, dict] = {}
|
|
49
46
|
for ts_idx, ts in enumerate(data.parsed.tilesets):
|
|
50
47
|
if ts.animation is not None:
|
|
51
48
|
self._tileset_animations[ts_idx] = {
|
|
@@ -56,9 +53,9 @@ class TileLayerRenderer:
|
|
|
56
53
|
"animation_mode": ts.animation.animation_mode,
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
self._layer_chunks:
|
|
56
|
+
self._layer_chunks: dict[int, dict[tuple[int, int], list[tuple[int, int]]]] = {}
|
|
60
57
|
for layer_id, layer in self.tile_layers.items():
|
|
61
|
-
chunks:
|
|
58
|
+
chunks: dict[tuple[int, int], list[tuple[int, int]]] = {}
|
|
62
59
|
for (x, y), tile in layer.tiles.items():
|
|
63
60
|
if not isinstance(tile.ttype, int):
|
|
64
61
|
continue
|
|
@@ -69,10 +66,10 @@ class TileLayerRenderer:
|
|
|
69
66
|
chunks[chunk_key].append((x, y))
|
|
70
67
|
self._layer_chunks[layer_id] = chunks
|
|
71
68
|
|
|
72
|
-
def get_layer_dict(self) ->
|
|
69
|
+
def get_layer_dict(self) -> dict[int, object]:
|
|
73
70
|
return dict(self.tile_layers)
|
|
74
71
|
|
|
75
|
-
def _get_cached_variant(self, ttype: int, variant: int) ->
|
|
72
|
+
def _get_cached_variant(self, ttype: int, variant: int) -> Surface | None:
|
|
76
73
|
key = (ttype, variant)
|
|
77
74
|
if key not in self._variant_cache:
|
|
78
75
|
cell = self.data.get_tile_surface(ttype, variant, copy_surface=True)
|
|
@@ -121,17 +118,21 @@ class TileLayerRenderer:
|
|
|
121
118
|
def render(
|
|
122
119
|
self,
|
|
123
120
|
target: Surface,
|
|
124
|
-
camera_xy:
|
|
125
|
-
viewport_size:
|
|
121
|
+
camera_xy: tuple[float, float] | tuple[int, int] = (0, 0),
|
|
122
|
+
viewport_size: tuple[int, int] | None = None,
|
|
126
123
|
*,
|
|
127
|
-
|
|
124
|
+
extra_objects: Sequence[ExtraObject] | None = None,
|
|
125
|
+
current_time_ms: float | None = None,
|
|
128
126
|
) -> LayerRenderStats:
|
|
129
|
-
"""Render visible tile layers.
|
|
127
|
+
"""Render visible tile layers, optionally merged with extra objects.
|
|
128
|
+
|
|
129
|
+
When *extra_objects* is provided, each item must have ``surface``,
|
|
130
|
+
``x``, and ``y`` attributes (duck-typed). Extras are blitted after
|
|
131
|
+
all tile layers, preserving caller order.
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
the cost of a full scan.
|
|
133
|
+
Tiles are blitted per layer in natural chunk order. When
|
|
134
|
+
``layer.y_sort`` is enabled, tiles within each chunk are sorted by
|
|
135
|
+
their tile-space Y coordinate before blitting.
|
|
135
136
|
"""
|
|
136
137
|
cam_x, cam_y = float(camera_xy[0]), float(camera_xy[1])
|
|
137
138
|
if viewport_size is None:
|
|
@@ -171,11 +172,14 @@ class TileLayerRenderer:
|
|
|
171
172
|
if not chunk:
|
|
172
173
|
continue
|
|
173
174
|
|
|
174
|
-
|
|
175
|
+
tile_iter = chunk
|
|
176
|
+
if layer.y_sort:
|
|
177
|
+
origin = layer.y_sort_origin
|
|
178
|
+
tile_iter = sorted(chunk, key=lambda p: p[1] * self._eff_h + origin)
|
|
179
|
+
|
|
180
|
+
for x, y in tile_iter:
|
|
175
181
|
tile = layer.tiles[(x, y)]
|
|
176
|
-
display_variant = self._compute_display_variant(
|
|
177
|
-
tile.variant, tile.ttype, x, y, time_ms
|
|
178
|
-
)
|
|
182
|
+
display_variant = self._compute_display_variant(tile.variant, tile.ttype, x, y, time_ms)
|
|
179
183
|
cell = self._get_cached_variant(tile.ttype, display_variant)
|
|
180
184
|
if cell is None:
|
|
181
185
|
skipped += 1
|
|
@@ -187,6 +191,15 @@ class TileLayerRenderer:
|
|
|
187
191
|
)
|
|
188
192
|
drawn += 1
|
|
189
193
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
if extra_objects:
|
|
195
|
+
for obj in extra_objects:
|
|
196
|
+
surf = obj.surface
|
|
197
|
+
if surf is None:
|
|
198
|
+
continue
|
|
199
|
+
target.blit(surf, (obj.x - cam_x, obj.y - cam_y))
|
|
200
|
+
|
|
201
|
+
return LayerRenderStats(drawn_tiles=drawn, skipped_tiles=skipped, visible_layers=visible_layers)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def sorted_layer_ids(self) -> list[int]:
|
|
205
|
+
return list(self._sorted_layer_ids)
|
|
@@ -23,12 +23,13 @@ from ..parser.collision import (
|
|
|
23
23
|
RectangleShape,
|
|
24
24
|
TilesetCollision,
|
|
25
25
|
)
|
|
26
|
+
from .protocols import ICollidable
|
|
26
27
|
|
|
27
28
|
Point = Tuple[float, float]
|
|
28
29
|
Vector2 = Tuple[float, float]
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
class ICollidableSprite(Protocol):
|
|
32
|
+
class ICollidableSprite(ICollidable, Protocol):
|
|
32
33
|
"""
|
|
33
34
|
Interface that any sprite/character class must implement to use collision runners.
|
|
34
35
|
|
|
@@ -303,7 +304,7 @@ def _circle_polygon_collision_offset(
|
|
|
303
304
|
return False
|
|
304
305
|
|
|
305
306
|
|
|
306
|
-
def get_shape_bounds(sprite:
|
|
307
|
+
def get_shape_bounds(sprite: ICollidable) -> Tuple[float, float, float, float]:
|
|
307
308
|
"""Get AABB bounds for sprite (left, top, right, bottom)"""
|
|
308
309
|
shape = sprite.collision_shape
|
|
309
310
|
if isinstance(shape, RectangleShape):
|
|
@@ -328,7 +329,7 @@ def get_shape_bounds(sprite: ICollidableSprite) -> Tuple[float, float, float, fl
|
|
|
328
329
|
|
|
329
330
|
|
|
330
331
|
def check_sprite_polygon_collision(
|
|
331
|
-
sprite:
|
|
332
|
+
sprite: ICollidable, polygon: CollisionPolygon
|
|
332
333
|
) -> bool:
|
|
333
334
|
"""Check if sprite collides with a world-space polygon (legacy / public API)."""
|
|
334
335
|
shape = sprite.collision_shape
|
|
@@ -349,7 +350,7 @@ def check_sprite_polygon_collision(
|
|
|
349
350
|
|
|
350
351
|
|
|
351
352
|
def _check_sprite_polygon_offset(
|
|
352
|
-
sprite:
|
|
353
|
+
sprite: ICollidable,
|
|
353
354
|
polygon: CollisionPolygon,
|
|
354
355
|
ox: float,
|
|
355
356
|
oy: float,
|
|
@@ -489,7 +490,7 @@ class CollisionRunner:
|
|
|
489
490
|
self,
|
|
490
491
|
tileset_collision: TilesetCollision,
|
|
491
492
|
tile_map: dict,
|
|
492
|
-
sprite:
|
|
493
|
+
sprite: ICollidable,
|
|
493
494
|
margin: int = 1,
|
|
494
495
|
) -> List[CollisionPolygon]:
|
|
495
496
|
"""
|
|
@@ -529,7 +530,7 @@ class CollisionRunner:
|
|
|
529
530
|
|
|
530
531
|
def _collides_at(
|
|
531
532
|
self,
|
|
532
|
-
sprite:
|
|
533
|
+
sprite: ICollidable,
|
|
533
534
|
tileset_collision: TilesetCollision,
|
|
534
535
|
tile_map: dict,
|
|
535
536
|
margin: int = 1,
|
|
@@ -567,7 +568,7 @@ class CollisionRunner:
|
|
|
567
568
|
|
|
568
569
|
def _first_colliding_shape(
|
|
569
570
|
self,
|
|
570
|
-
sprite:
|
|
571
|
+
sprite: ICollidable,
|
|
571
572
|
tileset_collision: TilesetCollision,
|
|
572
573
|
tile_map: dict,
|
|
573
574
|
margin: int = 1,
|
|
@@ -603,7 +604,7 @@ class CollisionRunner:
|
|
|
603
604
|
|
|
604
605
|
def move_and_slide(
|
|
605
606
|
self,
|
|
606
|
-
sprite:
|
|
607
|
+
sprite: ICollidable,
|
|
607
608
|
tileset_collision: TilesetCollision,
|
|
608
609
|
tile_map: dict,
|
|
609
610
|
delta_x: float,
|
|
@@ -729,7 +730,7 @@ class CollisionRunner:
|
|
|
729
730
|
|
|
730
731
|
def _get_collision_normal_from_motion(
|
|
731
732
|
self,
|
|
732
|
-
sprite:
|
|
733
|
+
sprite: ICollidable,
|
|
733
734
|
polygon: CollisionPolygon,
|
|
734
735
|
ox: float,
|
|
735
736
|
oy: float,
|
|
@@ -1745,7 +1746,7 @@ class CollisionRunner:
|
|
|
1745
1746
|
|
|
1746
1747
|
def move_rpg(
|
|
1747
1748
|
self,
|
|
1748
|
-
sprite:
|
|
1749
|
+
sprite: ICollidable,
|
|
1749
1750
|
tileset_collision: TilesetCollision,
|
|
1750
1751
|
tile_map: dict,
|
|
1751
1752
|
delta_x: float,
|
|
@@ -1814,7 +1815,7 @@ class CollisionRunner:
|
|
|
1814
1815
|
|
|
1815
1816
|
def move(
|
|
1816
1817
|
self,
|
|
1817
|
-
sprite:
|
|
1818
|
+
sprite: ICollidable,
|
|
1818
1819
|
tileset_collision: TilesetCollision,
|
|
1819
1820
|
tile_map: dict,
|
|
1820
1821
|
delta_x: float = 0.0,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tilemap-parser
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.4
|
|
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
|
|
@@ -24,6 +24,7 @@ src/tilemap_parser/runtime/map_loader.py
|
|
|
24
24
|
src/tilemap_parser/runtime/map_object.py
|
|
25
25
|
src/tilemap_parser/runtime/object_collision.py
|
|
26
26
|
src/tilemap_parser/runtime/particles.py
|
|
27
|
+
src/tilemap_parser/runtime/protocols.py
|
|
27
28
|
src/tilemap_parser/runtime/renderer.py
|
|
28
29
|
src/tilemap_parser/runtime/tile_collision.py
|
|
29
30
|
src/tilemap_parser/utils/__init__.py
|
|
@@ -43,4 +44,5 @@ tests/test_object_surfaces.py
|
|
|
43
44
|
tests/test_particle_emitter.py
|
|
44
45
|
tests/test_render_scale.py
|
|
45
46
|
tests/test_tile_collision.py
|
|
47
|
+
tests/test_tile_layer_renderer_y_sort.py
|
|
46
48
|
tests/test_tmx_converter.py
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for TileLayerRenderer y-sort and extra_objects.
|
|
3
|
+
|
|
4
|
+
Covers:
|
|
5
|
+
- y_sort read from parsed layer data
|
|
6
|
+
- y-major iteration when layer.y_sort=True
|
|
7
|
+
- extra_objects merged and sorted by (z_index, y)
|
|
8
|
+
- Duck-typed extra objects (surface, x, y only)
|
|
9
|
+
- Smoke test: render with extra_objects does not crash, stats correct
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
16
|
+
|
|
17
|
+
import pygame
|
|
18
|
+
import pytest
|
|
19
|
+
from pygame import Surface
|
|
20
|
+
|
|
21
|
+
from tilemap_parser.parser.map_parse import (
|
|
22
|
+
ParsedLayer,
|
|
23
|
+
ParsedMeta,
|
|
24
|
+
ParsedMap,
|
|
25
|
+
_parse_layer,
|
|
26
|
+
)
|
|
27
|
+
from tilemap_parser.runtime.map_loader import TilemapData
|
|
28
|
+
from tilemap_parser.runtime.renderer import TileLayerRenderer
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# ParsedLayer y_sort field
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestParsedLayerYSort:
|
|
37
|
+
def test_default_is_false(self):
|
|
38
|
+
"""y_sort defaults to False when not present in data."""
|
|
39
|
+
d = {
|
|
40
|
+
"name": "test",
|
|
41
|
+
"type": "tile",
|
|
42
|
+
"visible": True,
|
|
43
|
+
"locked": False,
|
|
44
|
+
"opacity": 1.0,
|
|
45
|
+
"z_index": 0,
|
|
46
|
+
}
|
|
47
|
+
layer = _parse_layer(d, 0, "test")
|
|
48
|
+
assert layer.y_sort is False
|
|
49
|
+
|
|
50
|
+
def test_reads_true(self):
|
|
51
|
+
d = {
|
|
52
|
+
"name": "test",
|
|
53
|
+
"type": "tile",
|
|
54
|
+
"visible": True,
|
|
55
|
+
"locked": False,
|
|
56
|
+
"opacity": 1.0,
|
|
57
|
+
"z_index": 0,
|
|
58
|
+
"y_sort": True,
|
|
59
|
+
}
|
|
60
|
+
layer = _parse_layer(d, 0, "test")
|
|
61
|
+
assert layer.y_sort is True
|
|
62
|
+
|
|
63
|
+
def test_reads_false_explicit(self):
|
|
64
|
+
d = {
|
|
65
|
+
"name": "test",
|
|
66
|
+
"type": "tile",
|
|
67
|
+
"visible": True,
|
|
68
|
+
"locked": False,
|
|
69
|
+
"opacity": 1.0,
|
|
70
|
+
"z_index": 0,
|
|
71
|
+
"y_sort": False,
|
|
72
|
+
}
|
|
73
|
+
layer = _parse_layer(d, 0, "test")
|
|
74
|
+
assert layer.y_sort is False
|
|
75
|
+
|
|
76
|
+
def test_y_sort_origin_defaults_to_zero(self):
|
|
77
|
+
d = {"name": "test", "type": "tile", "visible": True, "locked": False, "opacity": 1.0, "z_index": 0}
|
|
78
|
+
layer = _parse_layer(d, 0, "test")
|
|
79
|
+
assert layer.y_sort_origin == 0
|
|
80
|
+
|
|
81
|
+
def test_y_sort_origin_reads_from_json(self):
|
|
82
|
+
d = {"name": "test", "type": "tile", "visible": True, "locked": False, "opacity": 1.0, "z_index": 0, "y_sort_origin": 16}
|
|
83
|
+
layer = _parse_layer(d, 0, "test")
|
|
84
|
+
assert layer.y_sort_origin == 16
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# Helpers — minimal map for renderer tests
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _dummy_tileset_surface(w=16, h=16) -> Surface:
|
|
93
|
+
"""Create a solid-colour surface to serve as a tileset cell."""
|
|
94
|
+
surf = Surface((w, h))
|
|
95
|
+
surf.fill((200, 100, 50))
|
|
96
|
+
return surf
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DummyVisual:
|
|
100
|
+
"""Minimal duck-typed visual — surface, x, y, optional z_index."""
|
|
101
|
+
|
|
102
|
+
__slots__ = ("surface", "x", "y", "z_index")
|
|
103
|
+
|
|
104
|
+
def __init__(self, surface, x, y, z_index=0):
|
|
105
|
+
self.surface = surface
|
|
106
|
+
self.x = x
|
|
107
|
+
self.y = y
|
|
108
|
+
self.z_index = z_index
|
|
109
|
+
|
|
110
|
+
def __repr__(self):
|
|
111
|
+
return f"DummyVisual(x={self.x}, y={self.y}, z={self.z_index})"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _make_tile(pos, ttype=0, variant=0):
|
|
115
|
+
from tilemap_parser.parser.map_parse import ParsedTile
|
|
116
|
+
|
|
117
|
+
return ParsedTile(pos=pos, ttype=ttype, variant=variant, gid=None, properties=None)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _make_map_data(layers, tile_size=(16, 16)) -> TilemapData:
|
|
121
|
+
"""Build a TilemapData from a list of ParsedLayer objects."""
|
|
122
|
+
from pathlib import Path
|
|
123
|
+
from tilemap_parser.parser.map_parse import ParsedTileset
|
|
124
|
+
|
|
125
|
+
meta = ParsedMeta(
|
|
126
|
+
tile_size=tile_size,
|
|
127
|
+
map_size=(10, 10),
|
|
128
|
+
initial_map_size=(10, 10),
|
|
129
|
+
zoom_level=1.0,
|
|
130
|
+
scroll=(0, 0),
|
|
131
|
+
version="1.1",
|
|
132
|
+
render_scale=1.0,
|
|
133
|
+
)
|
|
134
|
+
mock_map = object.__new__(ParsedMap)
|
|
135
|
+
mock_map.meta = meta
|
|
136
|
+
mock_map.layers = layers
|
|
137
|
+
mock_map.tilesets = [ParsedTileset(path="dummy.png", type="tile")]
|
|
138
|
+
tw, th = tile_size
|
|
139
|
+
resolved_paths = [Path("dummy.png")]
|
|
140
|
+
return TilemapData(mock_map, [_dummy_tileset_surface(tw, th)], resolved_paths, [])
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
# TileLayerRenderer — extra_objects
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class TestExtraObjects:
|
|
149
|
+
def make_map_data(self) -> TilemapData:
|
|
150
|
+
"""Minimal 1-layer map with a single tile at (0,0)."""
|
|
151
|
+
layer = ParsedLayer(
|
|
152
|
+
id=0,
|
|
153
|
+
name="Ground",
|
|
154
|
+
layer_type="tile",
|
|
155
|
+
visible=True,
|
|
156
|
+
locked=False,
|
|
157
|
+
opacity=1.0,
|
|
158
|
+
z_index=0,
|
|
159
|
+
)
|
|
160
|
+
layer.tiles[(0, 0)] = _make_tile((0, 0))
|
|
161
|
+
return _make_map_data([layer])
|
|
162
|
+
|
|
163
|
+
def test_extra_objects_smoke(self):
|
|
164
|
+
"""Render with extra_objects does not crash."""
|
|
165
|
+
data = self.make_map_data()
|
|
166
|
+
renderer = TileLayerRenderer(data)
|
|
167
|
+
target = Surface((100, 100))
|
|
168
|
+
objs = [DummyVisual(_dummy_tileset_surface(), 10, 20)]
|
|
169
|
+
stats = renderer.render(target, (0, 0), extra_objects=objs)
|
|
170
|
+
assert stats.drawn_tiles == 1
|
|
171
|
+
assert stats.visible_layers == 1
|
|
172
|
+
|
|
173
|
+
def test_extra_objects_merged_into_drawn_count(self):
|
|
174
|
+
"""drawn_tiles only counts tiles, not extras (unchanged stat)."""
|
|
175
|
+
data = self.make_map_data()
|
|
176
|
+
renderer = TileLayerRenderer(data)
|
|
177
|
+
target = Surface((100, 100))
|
|
178
|
+
objs = [DummyVisual(_dummy_tileset_surface(), 10, 20)]
|
|
179
|
+
stats = renderer.render(target, (0, 0), extra_objects=objs)
|
|
180
|
+
assert stats.drawn_tiles == 1 # extras not counted here
|
|
181
|
+
|
|
182
|
+
def test_multiple_extra_objects(self):
|
|
183
|
+
"""Multiple extra objects all blit without error."""
|
|
184
|
+
data = self.make_map_data()
|
|
185
|
+
renderer = TileLayerRenderer(data)
|
|
186
|
+
target = Surface((200, 200))
|
|
187
|
+
objs = [
|
|
188
|
+
DummyVisual(_dummy_tileset_surface(), 10, 20, z_index=0),
|
|
189
|
+
DummyVisual(_dummy_tileset_surface(), 30, 40, z_index=1),
|
|
190
|
+
DummyVisual(_dummy_tileset_surface(), 50, 60, z_index=0),
|
|
191
|
+
]
|
|
192
|
+
renderer.render(target, (0, 0), extra_objects=objs)
|
|
193
|
+
# no crash is the assertion
|
|
194
|
+
|
|
195
|
+
def test_duck_typed_minimal(self):
|
|
196
|
+
"""Object with only surface/x/y works (no z_index)."""
|
|
197
|
+
data = self.make_map_data()
|
|
198
|
+
renderer = TileLayerRenderer(data)
|
|
199
|
+
target = Surface((100, 100))
|
|
200
|
+
|
|
201
|
+
class MinimalVisual:
|
|
202
|
+
def __init__(self):
|
|
203
|
+
self.surface = _dummy_tileset_surface()
|
|
204
|
+
self.x = 5
|
|
205
|
+
self.y = 15
|
|
206
|
+
|
|
207
|
+
renderer.render(target, (0, 0), extra_objects=[MinimalVisual()])
|
|
208
|
+
|
|
209
|
+
def test_extra_object_with_none_surface_skipped(self):
|
|
210
|
+
"""Objects with surface=None are silently skipped."""
|
|
211
|
+
data = self.make_map_data()
|
|
212
|
+
renderer = TileLayerRenderer(data)
|
|
213
|
+
target = Surface((100, 100))
|
|
214
|
+
|
|
215
|
+
class NoneSurfaceVisual:
|
|
216
|
+
def __init__(self):
|
|
217
|
+
self.surface = None
|
|
218
|
+
self.x = 0
|
|
219
|
+
self.y = 0
|
|
220
|
+
|
|
221
|
+
renderer.render(target, (0, 0), extra_objects=[NoneSurfaceVisual()])
|
|
222
|
+
|
|
223
|
+
def test_render_without_extra_objects_still_works(self):
|
|
224
|
+
"""Calling render() without extra_objects is unchanged."""
|
|
225
|
+
data = self.make_map_data()
|
|
226
|
+
renderer = TileLayerRenderer(data)
|
|
227
|
+
target = Surface((100, 100))
|
|
228
|
+
stats = renderer.render(target, (0, 0))
|
|
229
|
+
assert stats.drawn_tiles == 1
|
|
230
|
+
assert stats.visible_layers == 1
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ---------------------------------------------------------------------------
|
|
234
|
+
# TileLayerRenderer — y_sort iteration
|
|
235
|
+
# ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class _SpySurface(pygame.Surface):
|
|
239
|
+
"""Records (dest_x, dest_y) for each blit call."""
|
|
240
|
+
|
|
241
|
+
def __init__(self, size):
|
|
242
|
+
super().__init__(size)
|
|
243
|
+
self.blit_calls: list[tuple[float, float]] = []
|
|
244
|
+
|
|
245
|
+
def blit(self, source, dest, *args, **kwargs):
|
|
246
|
+
if isinstance(dest, tuple):
|
|
247
|
+
self.blit_calls.append((float(dest[0]), float(dest[1])))
|
|
248
|
+
else:
|
|
249
|
+
self.blit_calls.append((float(dest.x), float(dest.y)))
|
|
250
|
+
return super().blit(source, dest, *args, **kwargs)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class TestLayerYSort:
|
|
254
|
+
"""Verify that y_sort changes iteration order within a chunk."""
|
|
255
|
+
|
|
256
|
+
def make_map_data_two_tiles(self, y_sort: bool = True) -> TilemapData:
|
|
257
|
+
"""One layer with two tiles at different Y positions."""
|
|
258
|
+
layer = ParsedLayer(
|
|
259
|
+
id=0,
|
|
260
|
+
name="Bushes",
|
|
261
|
+
layer_type="tile",
|
|
262
|
+
visible=True,
|
|
263
|
+
locked=False,
|
|
264
|
+
opacity=1.0,
|
|
265
|
+
z_index=0,
|
|
266
|
+
y_sort=y_sort,
|
|
267
|
+
)
|
|
268
|
+
layer.tiles[(0, 0)] = _make_tile((0, 0))
|
|
269
|
+
layer.tiles[(0, 5)] = _make_tile((0, 5))
|
|
270
|
+
return _make_map_data([layer])
|
|
271
|
+
|
|
272
|
+
def test_y_sort_layer_does_not_crash(self):
|
|
273
|
+
"""Layer with y_sort=True renders without error."""
|
|
274
|
+
data = self.make_map_data_two_tiles()
|
|
275
|
+
renderer = TileLayerRenderer(data)
|
|
276
|
+
target = Surface((200, 200))
|
|
277
|
+
stats = renderer.render(target, (0, 0))
|
|
278
|
+
assert stats.drawn_tiles == 2
|
|
279
|
+
|
|
280
|
+
def test_y_major_order(self):
|
|
281
|
+
"""y-sort within a chunk produces y-major iteration."""
|
|
282
|
+
data = self.make_map_data_two_tiles(y_sort=True)
|
|
283
|
+
renderer = TileLayerRenderer(data)
|
|
284
|
+
target = _SpySurface((200, 200))
|
|
285
|
+
renderer.render(target, (0, 0))
|
|
286
|
+
ys = [y for _, y in target.blit_calls]
|
|
287
|
+
assert ys == sorted(ys)
|
|
288
|
+
|
|
289
|
+
def test_no_ysort_preserves_input_order(self):
|
|
290
|
+
"""Without y-sort the chunk iteration is insertion order."""
|
|
291
|
+
data = self.make_map_data_two_tiles(y_sort=False)
|
|
292
|
+
renderer = TileLayerRenderer(data)
|
|
293
|
+
target = _SpySurface((200, 200))
|
|
294
|
+
renderer.render(target, (0, 0))
|
|
295
|
+
ef = 16
|
|
296
|
+
assert target.blit_calls == [(0.0, 0.0), (0.0, 5.0 * ef)]
|
|
297
|
+
|
|
298
|
+
def test_y_sort_origin_shifts_sort_order(self):
|
|
299
|
+
"""y_sort_origin offsets the Y used for sorting."""
|
|
300
|
+
layer = ParsedLayer(
|
|
301
|
+
id=0, name="test", layer_type="tile", visible=True,
|
|
302
|
+
locked=False, opacity=1.0, z_index=0,
|
|
303
|
+
y_sort=True, y_sort_origin=32,
|
|
304
|
+
)
|
|
305
|
+
layer.tiles[(0, 0)] = _make_tile((0, 0))
|
|
306
|
+
layer.tiles[(0, 1)] = _make_tile((0, 1))
|
|
307
|
+
data = _make_map_data([layer])
|
|
308
|
+
renderer = TileLayerRenderer(data)
|
|
309
|
+
target = _SpySurface((200, 200))
|
|
310
|
+
renderer.render(target, (0, 0))
|
|
311
|
+
ef = 16
|
|
312
|
+
assert target.blit_calls == [(0.0, 0.0), (0.0, 1.0 * ef)]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
# Integration — combined y_sort + extra_objects
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class TestIntegration:
|
|
321
|
+
def test_y_sort_with_extra_objects(self):
|
|
322
|
+
"""y_sort layer renders tiles first, then extras in caller order."""
|
|
323
|
+
layer = ParsedLayer(
|
|
324
|
+
id=0,
|
|
325
|
+
name="Bushes",
|
|
326
|
+
layer_type="tile",
|
|
327
|
+
visible=True,
|
|
328
|
+
locked=False,
|
|
329
|
+
opacity=1.0,
|
|
330
|
+
z_index=0,
|
|
331
|
+
y_sort=True,
|
|
332
|
+
)
|
|
333
|
+
layer.tiles[(0, 0)] = _make_tile((0, 0))
|
|
334
|
+
layer.tiles[(0, 5)] = _make_tile((0, 5))
|
|
335
|
+
data = _make_map_data([layer])
|
|
336
|
+
renderer = TileLayerRenderer(data)
|
|
337
|
+
target = _SpySurface((200, 200))
|
|
338
|
+
ef = 16
|
|
339
|
+
objs = [
|
|
340
|
+
DummyVisual(_dummy_tileset_surface(), 5, 25),
|
|
341
|
+
DummyVisual(_dummy_tileset_surface(), 5, 3),
|
|
342
|
+
]
|
|
343
|
+
renderer.render(target, (0, 0), extra_objects=objs)
|
|
344
|
+
assert len(target.blit_calls) == 4
|
|
345
|
+
assert target.blit_calls[0] == (0.0, 0.0) # tile y=0
|
|
346
|
+
assert target.blit_calls[1] == (0.0, 5.0 * ef) # tile y=5
|
|
347
|
+
assert target.blit_calls[2] == (5.0, 25.0) # extra in caller order
|
|
348
|
+
assert target.blit_calls[3] == (5.0, 3.0) # extra in caller order
|
|
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-4.2.2 → tilemap_parser-4.2.4}/src/tilemap_parser/runtime/animation_player.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-4.2.2 → tilemap_parser-4.2.4}/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
|