tilemap-parser 3.1.22__tar.gz → 3.1.23__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.
Files changed (43) hide show
  1. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/PKG-INFO +1 -1
  2. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/pyproject.toml +1 -1
  3. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/map_loader.py +83 -0
  4. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser.egg-info/PKG-INFO +1 -1
  5. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_map_loader.py +81 -0
  6. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/LICENSE +0 -0
  7. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/README.md +0 -0
  8. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/setup.cfg +0 -0
  9. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/__init__.py +0 -0
  10. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/parser/__init__.py +0 -0
  11. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/parser/animation.py +0 -0
  12. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/parser/collision.py +0 -0
  13. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/parser/collision_loader.py +0 -0
  14. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/parser/map_parse.py +0 -0
  15. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/parser/node_parse.py +0 -0
  16. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/parser/particle.py +0 -0
  17. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/__init__.py +0 -0
  18. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/animation_player.py +0 -0
  19. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/area_node.py +0 -0
  20. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/camera.py +0 -0
  21. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/collision_cache.py +0 -0
  22. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/object_collision.py +0 -0
  23. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/particles.py +0 -0
  24. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/renderer.py +0 -0
  25. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/runtime/tile_collision.py +0 -0
  26. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/utils/__init__.py +0 -0
  27. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser/utils/geometry.py +0 -0
  28. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser.egg-info/SOURCES.txt +0 -0
  29. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
  30. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser.egg-info/requires.txt +0 -0
  31. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/src/tilemap_parser.egg-info/top_level.txt +0 -0
  32. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_camera.py +0 -0
  33. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_collision.py +0 -0
  34. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_geometry.py +0 -0
  35. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_integration_animation.py +0 -0
  36. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_integration_collision.py +0 -0
  37. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_integration_map_loader.py +0 -0
  38. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_move_grounded.py +0 -0
  39. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_object_collision.py +0 -0
  40. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_object_surfaces.py +0 -0
  41. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_particle_emitter.py +0 -0
  42. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_render_scale.py +0 -0
  43. {tilemap_parser-3.1.22 → tilemap_parser-3.1.23}/tests/test_tile_collision.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-parser
3
- Version: 3.1.22
3
+ Version: 3.1.23
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 = "3.1.22"
7
+ version = "3.1.23"
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"
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import math
4
5
  from copy import deepcopy
5
6
  from pathlib import Path
6
7
  from typing import Dict, List, Optional, Tuple, Union
@@ -31,6 +32,8 @@ class TilemapData:
31
32
  self.resolved_paths = resolved_paths
32
33
  self.warnings = warnings
33
34
  self.map_path = map_path
35
+ # Pixel/world offset applied while normalizing negative source coordinates.
36
+ self.origin_offset = (0, 0)
34
37
  self.area_nodes: List[AreaNode] = []
35
38
  self.particle_emitters: List[ParticleEmitterNode] = []
36
39
  self._tw, self._th = parsed.meta.tile_size
@@ -98,7 +101,9 @@ class TilemapData:
98
101
  warnings.append(f"Failed to load nodes: {e}")
99
102
  break
100
103
 
104
+ origin_offset = _normalize_origin(parsed)
101
105
  result = cls(parsed, surfaces, resolved_paths, warnings, map_path=p)
106
+ result.origin_offset = origin_offset
102
107
  result.area_nodes = [AreaNode(n) for n in parsed.nodes if n.node_type == "area"]
103
108
  result.particle_emitters = [
104
109
  ParticleEmitterNode(n) for n in parsed.nodes if n.node_type == "particle_emitter"
@@ -312,5 +317,83 @@ def _resolve_resource_path(path_str: str, map_dir: Path, extra_search_base: Opti
312
317
  return candidate
313
318
 
314
319
 
320
+ def _normalize_origin(parsed: ParsedMap) -> Tuple[int, int]:
321
+ tw, th = parsed.meta.tile_size
322
+ rs = parsed.meta.render_scale
323
+ eff_w = int(tw * rs)
324
+ eff_h = int(th * rs)
325
+ if eff_w <= 0 or eff_h <= 0:
326
+ return (0, 0)
327
+
328
+ min_x = 0
329
+ min_y = 0
330
+ max_x = parsed.meta.map_size[0]
331
+ max_y = parsed.meta.map_size[1]
332
+
333
+ for layer in parsed.layers:
334
+ for x, y in layer.tiles.keys():
335
+ min_x = min(min_x, x)
336
+ min_y = min(min_y, y)
337
+ max_x = max(max_x, x + 1)
338
+ max_y = max(max_y, y + 1)
339
+
340
+ for obj in layer.objects.values():
341
+ left = math.floor(obj.area.x / eff_w)
342
+ top = math.floor(obj.area.y / eff_h)
343
+ right = math.ceil((obj.area.x + obj.area.w) / eff_w)
344
+ bottom = math.ceil((obj.area.y + obj.area.h) / eff_h)
345
+ min_x = min(min_x, left)
346
+ min_y = min(min_y, top)
347
+ max_x = max(max_x, right)
348
+ max_y = max(max_y, bottom)
349
+
350
+ for node in parsed.nodes:
351
+ left = math.floor(node.area.x / eff_w)
352
+ top = math.floor(node.area.y / eff_h)
353
+ right = math.ceil((node.area.x + node.area.w) / eff_w)
354
+ bottom = math.ceil((node.area.y + node.area.h) / eff_h)
355
+ min_x = min(min_x, left)
356
+ min_y = min(min_y, top)
357
+ max_x = max(max_x, right)
358
+ max_y = max(max_y, bottom)
359
+
360
+ if min_x >= 0 and min_y >= 0:
361
+ parsed.meta.map_size = (max_x, max_y)
362
+ return (0, 0)
363
+
364
+ shift_x = -min_x
365
+ shift_y = -min_y
366
+ pixel_shift_x = shift_x * eff_w
367
+ pixel_shift_y = shift_y * eff_h
368
+
369
+ for layer in parsed.layers:
370
+ if layer.tiles:
371
+ shifted_tiles = {}
372
+ for (x, y), tile in layer.tiles.items():
373
+ new_pos = (x + shift_x, y + shift_y)
374
+ tile.pos = new_pos
375
+ shifted_tiles[new_pos] = tile
376
+ layer.tiles = shifted_tiles
377
+
378
+ for obj in layer.objects.values():
379
+ obj.area.x += pixel_shift_x
380
+ obj.area.y += pixel_shift_y
381
+
382
+ for node in parsed.nodes:
383
+ node.area.x += pixel_shift_x
384
+ node.area.y += pixel_shift_y
385
+
386
+ parsed.meta.map_size = (max_x + shift_x, max_y + shift_y)
387
+ parsed.meta.initial_map_size = (
388
+ parsed.meta.initial_map_size[0] + shift_x,
389
+ parsed.meta.initial_map_size[1] + shift_y,
390
+ )
391
+ parsed.meta.scroll = (
392
+ parsed.meta.scroll[0] + pixel_shift_x,
393
+ parsed.meta.scroll[1] + pixel_shift_y,
394
+ )
395
+ return (pixel_shift_x, pixel_shift_y)
396
+
397
+
315
398
  def load_map(path: PathLike, *, extra_search_base: Optional[Path] = None, skip_missing_images: bool = True, nodes_dir: Optional[PathLike] = None) -> TilemapData:
316
399
  return TilemapData.load(path, extra_search_base=extra_search_base, skip_missing_images=skip_missing_images, nodes_dir=nodes_dir)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-parser
3
- Version: 3.1.22
3
+ Version: 3.1.23
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
@@ -114,6 +114,87 @@ class TestTilemapDataLoad:
114
114
  assert td.resolved_paths[0] == png.resolve()
115
115
  assert td.surfaces[0] is not None
116
116
 
117
+ def test_negative_coordinates_normalized_to_zero_origin(self, tmp_project):
118
+ tmp, data_dir, assets_dir = tmp_project
119
+ payload = {
120
+ "meta": {
121
+ "tile_size": "16;16",
122
+ "map_size": "4;4",
123
+ "initial_map_size": "4;4",
124
+ "render_scale": 2,
125
+ "scroll": "-64;-32",
126
+ "version": "1.1",
127
+ },
128
+ "resources": {"tilesets": []},
129
+ "project_state": {"rules": [], "groups": []},
130
+ "data": {
131
+ "layers": [
132
+ {
133
+ "name": "Terrain",
134
+ "type": "tile",
135
+ "visible": True,
136
+ "locked": False,
137
+ "opacity": 1.0,
138
+ "z_index": 0,
139
+ "tiles": {
140
+ "-2;-1": {"pos": "-2;-1", "ttype": 0, "variant": 1},
141
+ "1;0": {"pos": "1;0", "ttype": 0, "variant": 2},
142
+ },
143
+ },
144
+ {
145
+ "name": "Objects",
146
+ "type": "object",
147
+ "visible": True,
148
+ "locked": False,
149
+ "opacity": 1.0,
150
+ "z_index": 1,
151
+ "tiles": {},
152
+ "objects": {
153
+ "1": {
154
+ "area": {"x": -8, "y": -20, "w": 16, "h": 16},
155
+ "ttype": 0,
156
+ "tileset_type": "object",
157
+ "variant": 0,
158
+ }
159
+ },
160
+ },
161
+ ]
162
+ },
163
+ }
164
+ map_path = data_dir / "test_map.json"
165
+ with open(map_path, "w") as f:
166
+ json.dump(payload, f, indent=2)
167
+ nodes_path = data_dir / "test_map.nodes.json"
168
+ with open(nodes_path, "w") as f:
169
+ json.dump(
170
+ {
171
+ "nodes": [
172
+ {
173
+ "node_id": "pe_neg",
174
+ "name": "Mist",
175
+ "node_type": "particle_emitter",
176
+ "area": {"x": -40, "y": 0, "w": 16, "h": 16},
177
+ "layer_name": "fx",
178
+ "properties": {},
179
+ "group": "",
180
+ }
181
+ ],
182
+ "groups": [],
183
+ },
184
+ f,
185
+ indent=2,
186
+ )
187
+
188
+ td = TilemapData.load(map_path)
189
+
190
+ assert td.origin_offset == (64, 32)
191
+ assert td.map_size == (6, 5)
192
+ assert td.get_tile_at("Terrain", 0, 0).variant == 1
193
+ assert td.get_tile_at("Terrain", 3, 1).variant == 2
194
+ obj = td.get_layer("Objects").objects[1]
195
+ assert (obj.area.x, obj.area.y) == (56, 12)
196
+ assert td.particle_emitters[0].rect == Rect(24, 32, 16, 16)
197
+
117
198
 
118
199
  MINIMAL_NODES = {
119
200
  "nodes": [
File without changes