tilemap-parser 4.2.6__tar.gz → 4.2.7__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 (50) hide show
  1. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/PKG-INFO +1 -1
  2. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/pyproject.toml +1 -1
  3. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/map_object.py +32 -23
  4. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser.egg-info/PKG-INFO +1 -1
  5. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_map_object.py +90 -35
  6. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/LICENSE +0 -0
  7. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/README.md +0 -0
  8. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/setup.cfg +0 -0
  9. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/__init__.py +0 -0
  10. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/parser/__init__.py +0 -0
  11. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/parser/animation.py +0 -0
  12. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/parser/collision.py +0 -0
  13. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/parser/collision_loader.py +0 -0
  14. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/parser/map_parse.py +0 -0
  15. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/parser/node_parse.py +0 -0
  16. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/parser/particle.py +0 -0
  17. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/parser/tmx_converter.py +0 -0
  18. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/__init__.py +0 -0
  19. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/animation_player.py +0 -0
  20. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/area_node.py +0 -0
  21. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/camera.py +0 -0
  22. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/collision_cache.py +0 -0
  23. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/map_loader.py +0 -0
  24. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/object_collision.py +0 -0
  25. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/particles.py +0 -0
  26. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/protocols.py +0 -0
  27. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/renderer.py +0 -0
  28. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/runtime/tile_collision.py +0 -0
  29. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/utils/__init__.py +0 -0
  30. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser/utils/geometry.py +0 -0
  31. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser.egg-info/SOURCES.txt +0 -0
  32. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
  33. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser.egg-info/requires.txt +0 -0
  34. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/src/tilemap_parser.egg-info/top_level.txt +0 -0
  35. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_camera.py +0 -0
  36. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_collision.py +0 -0
  37. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_geometry.py +0 -0
  38. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_gid_collision.py +0 -0
  39. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_integration_animation.py +0 -0
  40. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_integration_collision.py +0 -0
  41. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_integration_map_loader.py +0 -0
  42. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_map_loader.py +0 -0
  43. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_move_grounded.py +0 -0
  44. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_object_collision.py +0 -0
  45. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_object_surfaces.py +0 -0
  46. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_particle_emitter.py +0 -0
  47. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_render_scale.py +0 -0
  48. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_tile_collision.py +0 -0
  49. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/tests/test_tile_layer_renderer_y_sort.py +0 -0
  50. {tilemap_parser-4.2.6 → tilemap_parser-4.2.7}/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.6
3
+ Version: 4.2.7
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.6"
7
+ version = "4.2.7"
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"
@@ -16,7 +16,7 @@ Usage::
16
16
  from __future__ import annotations
17
17
 
18
18
  from pathlib import Path
19
- from typing import Dict, List, Optional, Union
19
+ from typing import Dict, List, Optional, Union, final
20
20
 
21
21
  import pygame
22
22
  from pygame import Surface
@@ -27,6 +27,7 @@ from .collision_cache import CollisionCache
27
27
  from .map_loader import TilemapData
28
28
 
29
29
 
30
+ @final
30
31
  class MapObject:
31
32
  """
32
33
  A game object loaded from a tilemap, carrying a surface for
@@ -48,28 +49,34 @@ class MapObject:
48
49
  shape pairs when either object has multiple shapes.
49
50
  """
50
51
 
51
- __slots__ = (
52
- "x",
53
- "y",
54
- "surface",
55
- "collision_shape",
56
- "collision_shapes",
57
- "collision_layer",
58
- "collision_mask",
59
- "y_sort_origin",
52
+ __slots__ = tuple(
53
+ sorted(
54
+ (
55
+ "x",
56
+ "y",
57
+ "surface",
58
+ "collision_shape",
59
+ "collision_shapes",
60
+ "collision_layer",
61
+ "collision_mask",
62
+ "y_sort_origin",
63
+ "ttype",
64
+ )
65
+ )
60
66
  )
61
67
 
62
68
  def __init__(
63
69
  self,
64
70
  x: float,
65
71
  y: float,
66
- surface: Optional[Surface] = None,
67
- collision_shape: Optional[CollisionPolygon] = None,
72
+ surface: Surface | None = None,
73
+ collision_shape: CollisionPolygon | None = None,
68
74
  *,
69
- collision_shapes: Optional[List[CollisionPolygon]] = None,
75
+ collision_shapes: list[CollisionPolygon] | None = None,
70
76
  collision_layer: int = 1,
71
77
  collision_mask: int = 0xFFFFFFFF,
72
- y_sort_origin: Optional[int] = None,
78
+ y_sort_origin: int | None = None,
79
+ ttype: int = -1,
73
80
  ) -> None:
74
81
  self.x = x
75
82
  self.y = y
@@ -86,13 +93,14 @@ class MapObject:
86
93
  self.collision_layer = collision_layer
87
94
  self.collision_mask = collision_mask
88
95
  self.y_sort_origin = y_sort_origin
96
+ self.ttype = ttype
89
97
 
90
98
  @property
91
99
  def has_collision(self) -> bool:
92
100
  return len(self.collision_shapes) > 0
93
101
 
94
102
 
95
- def _resolve_object_collision_filename(tileset_path: Union[str, Path]) -> str:
103
+ def _resolve_object_collision_filename(tileset_path: str | Path) -> str:
96
104
  """Extract the object collision filename for a given tileset path.
97
105
 
98
106
  Example: ``"building7.png"`` → ``"building7.object_collision.json"``
@@ -102,11 +110,11 @@ def _resolve_object_collision_filename(tileset_path: Union[str, Path]) -> str:
102
110
 
103
111
  def load_map_objects(
104
112
  tilemap_data: TilemapData,
105
- collision_dir: Union[str, Path],
113
+ collision_dir: str | Path,
106
114
  *,
107
- cache: Optional[CollisionCache] = None,
115
+ cache: CollisionCache | None = None,
108
116
  require_collision: bool = True,
109
- ) -> List[MapObject]:
117
+ ) -> list[MapObject]:
110
118
  """Load objects from a tilemap.
111
119
 
112
120
  Iterates every object layer in *tilemap_data*, resolves the
@@ -149,10 +157,10 @@ def load_map_objects(
149
157
  List of :class:`MapObject` instances.
150
158
  """
151
159
  collision_dir = Path(collision_dir)
152
- objects: List[MapObject] = []
160
+ objects: list[MapObject] = []
153
161
  object_layers = tilemap_data.get_layers(layer_type="object")
154
162
 
155
- loaded_collision: Dict[int, Optional[ObjectCollisionData]] = {}
163
+ loaded_collision: dict[int, ObjectCollisionData | None] = {}
156
164
 
157
165
  for layer in object_layers:
158
166
  for obj_id, obj in layer.objects.items():
@@ -171,9 +179,7 @@ def load_map_objects(
171
179
 
172
180
  ttype = obj.ttype
173
181
  if ttype not in loaded_collision:
174
- collision_data = _load_collision_for_tileset(
175
- tilemap_data, ttype, collision_dir, cache
176
- )
182
+ collision_data = _load_collision_for_tileset(tilemap_data, ttype, collision_dir, cache)
177
183
  loaded_collision[ttype] = collision_data
178
184
 
179
185
  collision_data = loaded_collision[ttype]
@@ -186,6 +192,7 @@ def load_map_objects(
186
192
  surface=surf,
187
193
  collision_shape=None,
188
194
  collision_shapes=[],
195
+ ttype=obj.ttype,
189
196
  )
190
197
  objects.append(game_obj)
191
198
  continue
@@ -209,6 +216,7 @@ def load_map_objects(
209
216
  surface=surf,
210
217
  collision_shape=None,
211
218
  collision_shapes=[],
219
+ ttype=obj.ttype,
212
220
  )
213
221
  objects.append(game_obj)
214
222
  continue
@@ -221,6 +229,7 @@ def load_map_objects(
221
229
  collision_shapes=world_shapes,
222
230
  collision_layer=region_layer,
223
231
  collision_mask=region_mask,
232
+ ttype=obj.ttype,
224
233
  )
225
234
  objects.append(game_obj)
226
235
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tilemap-parser
3
- Version: 4.2.6
3
+ Version: 4.2.7
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
@@ -8,14 +8,13 @@ Covers:
8
8
  """
9
9
 
10
10
  import json
11
+ import sys
11
12
  import tempfile
12
13
  from pathlib import Path
13
14
 
14
15
  import pygame
15
16
  import pytest
16
17
 
17
- import sys
18
-
19
18
  sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
20
19
 
21
20
  from tilemap_parser.parser.collision import CollisionPolygon
@@ -250,14 +249,14 @@ def map_multiple_objects():
250
249
  "ttype": 0,
251
250
  "tileset_type": "object",
252
251
  "variant": 0,
253
- "properties": {},
254
- },
255
- "2": {
256
- "area": {"x": 300, "y": 400, "w": 16, "h": 16},
257
- "ttype": 0,
258
- "tileset_type": "object",
259
- "variant": 1,
260
- "properties": {},
252
+ "properties": {},
253
+ },
254
+ "2": {
255
+ "area": {"x": 300, "y": 400, "w": 16, "h": 16},
256
+ "ttype": 0,
257
+ "tileset_type": "object",
258
+ "variant": 1,
259
+ "properties": {},
261
260
  },
262
261
  },
263
262
  "next_object_id": 3,
@@ -415,6 +414,7 @@ class TestLoadMapObjects:
415
414
  nonlocal call_count
416
415
  call_count += 1
417
416
  from tilemap_parser.parser.collision_loader import load_object_collision
417
+
418
418
  return load_object_collision(path)
419
419
 
420
420
  monkeypatch.setattr(
@@ -438,6 +438,7 @@ class TestLoadMapObjects:
438
438
  nonlocal call_count
439
439
  call_count += 1
440
440
  from tilemap_parser.parser.collision_loader import load_object_collision
441
+
441
442
  return load_object_collision(path)
442
443
 
443
444
  # CollisionCache.get_object_collision calls its own module-level
@@ -506,21 +507,27 @@ class TestLoadMapObjects:
506
507
  "region_id": "region_top",
507
508
  "region_rect": [0, 0, 32, 16],
508
509
  "name": "Top",
509
- "shapes": [{"type": "polygon", "vertices": [[0, 0], [32, 0], [32, 16], [0, 16]], "one_way": False}],
510
+ "shapes": [
511
+ {"type": "polygon", "vertices": [[0, 0], [32, 0], [32, 16], [0, 16]], "one_way": False}
512
+ ],
510
513
  "properties": {},
511
514
  },
512
515
  "region_bot": {
513
516
  "region_id": "region_bot",
514
517
  "region_rect": [0, 16, 32, 16],
515
518
  "name": "Bottom",
516
- "shapes": [{"type": "polygon", "vertices": [[0, 0], [32, 0], [32, 16], [0, 16]], "one_way": False}],
519
+ "shapes": [
520
+ {"type": "polygon", "vertices": [[0, 0], [32, 0], [32, 16], [0, 16]], "one_way": False}
521
+ ],
517
522
  "properties": {},
518
523
  },
519
524
  "region_extra": {
520
525
  "region_id": "region_extra",
521
526
  "region_rect": [0, 0, 16, 32],
522
527
  "name": "Extra",
523
- "shapes": [{"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 32], [0, 32]], "one_way": False}],
528
+ "shapes": [
529
+ {"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 32], [0, 32]], "one_way": False}
530
+ ],
524
531
  "properties": {},
525
532
  },
526
533
  },
@@ -546,7 +553,7 @@ class TestLoadMapObjects:
546
553
  surf = pygame.Surface((16, 16))
547
554
  # shape_a: 0-10, 0-10
548
555
  shape_a = CollisionPolygon(vertices=[(0, 0), (10, 0), (10, 10), (0, 10)])
549
- # shape_b: 20-30, 0-10
556
+ # shape_b: 20-30, 0-10
550
557
  shape_b = CollisionPolygon(vertices=[(20, 0), (30, 0), (30, 10), (20, 10)])
551
558
 
552
559
  obj1 = MapObject(x=0, y=0, surface=surf, collision_shape=shape_a, collision_shapes=[shape_a, shape_b])
@@ -627,7 +634,9 @@ class TestLoadMapObjects:
627
634
  "region_id": "region_r",
628
635
  "region_rect": [5, 10, 16, 16],
629
636
  "name": "R",
630
- "shapes": [{"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 16], [0, 16]], "one_way": False}],
637
+ "shapes": [
638
+ {"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 16], [0, 16]], "one_way": False}
639
+ ],
631
640
  "properties": {},
632
641
  },
633
642
  },
@@ -671,8 +680,8 @@ class TestLoadMapObjects:
671
680
 
672
681
  def test_render_scale_3x(self):
673
682
  """render_scale=3.0 scales positions and polygon vertices."""
674
- from tilemap_parser.runtime.map_object import load_map_objects
675
683
  from tilemap_parser.runtime.map_loader import TilemapData
684
+ from tilemap_parser.runtime.map_object import load_map_objects
676
685
 
677
686
  with tempfile.TemporaryDirectory() as tmp:
678
687
  tmp = Path(tmp)
@@ -693,7 +702,9 @@ class TestLoadMapObjects:
693
702
  "region_id": "region_r",
694
703
  "region_rect": [5, 10, 16, 16],
695
704
  "name": "R",
696
- "shapes": [{"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 16], [0, 16]], "one_way": False}],
705
+ "shapes": [
706
+ {"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 16], [0, 16]], "one_way": False}
707
+ ],
697
708
  "properties": {},
698
709
  },
699
710
  },
@@ -792,7 +803,9 @@ class TestLoadMapObjects:
792
803
  "region_id": "region_r",
793
804
  "region_rect": [0, 0, 16, 16],
794
805
  "name": "R",
795
- "shapes": [{"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 16], [0, 16]], "one_way": False}],
806
+ "shapes": [
807
+ {"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 16], [0, 16]], "one_way": False}
808
+ ],
796
809
  "properties": {},
797
810
  },
798
811
  },
@@ -845,8 +858,9 @@ class TestLoadMapObjects:
845
858
  objects = load_map_objects(td, collision_dir)
846
859
  assert len(objects) == 1
847
860
  obj = objects[0]
848
- assert obj.surface.get_size() == (expected_w, expected_h), \
861
+ assert obj.surface.get_size() == (expected_w, expected_h), (
849
862
  f"rs={rs_val}: expected ({expected_w},{expected_h}) got {obj.surface.get_size()}"
863
+ )
850
864
  assert obj.x == 10 * rs_val, f"rs={rs_val}: x={obj.x} != {10 * rs_val}"
851
865
  assert obj.y == 20 * rs_val, f"rs={rs_val}: y={obj.y} != {20 * rs_val}"
852
866
 
@@ -863,7 +877,9 @@ class TestLoadMapObjects:
863
877
  "region_id": "region_r",
864
878
  "region_rect": [0, 0, 17, 35],
865
879
  "name": "R",
866
- "shapes": [{"type": "polygon", "vertices": [[0, 0], [17, 0], [17, 35], [0, 35]], "one_way": False}],
880
+ "shapes": [
881
+ {"type": "polygon", "vertices": [[0, 0], [17, 0], [17, 35], [0, 35]], "one_way": False}
882
+ ],
867
883
  "properties": {},
868
884
  },
869
885
  },
@@ -917,8 +933,9 @@ class TestLoadMapObjects:
917
933
  obj = objects_odd[0]
918
934
  assert obj.x == 11 * 1.5, f"fractional x: {obj.x} != {11 * 1.5}"
919
935
  assert obj.y == 21 * 1.5, f"fractional y: {obj.y} != {21 * 1.5}"
920
- assert obj.surface.get_size() == (25, 52), \
936
+ assert obj.surface.get_size() == (25, 52), (
921
937
  f"truncated surface: {obj.surface.get_size()} != (25, 52) [int(17*1.5)=25, int(35*1.5)=52]"
938
+ )
922
939
 
923
940
  def test_render_scale_collision_consistency(self):
924
941
  """At render_scale=3.0, a loaded object collides with a manually-placed
@@ -943,7 +960,9 @@ class TestLoadMapObjects:
943
960
  "region_id": "region_r",
944
961
  "region_rect": [0, 0, 16, 16],
945
962
  "name": "R",
946
- "shapes": [{"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 16], [0, 16]], "one_way": False}],
963
+ "shapes": [
964
+ {"type": "polygon", "vertices": [[0, 0], [16, 0], [16, 16], [0, 16]], "one_way": False}
965
+ ],
947
966
  "properties": {},
948
967
  },
949
968
  },
@@ -1115,8 +1134,8 @@ class TestLoadMapObjects:
1115
1134
  def test_render_scale_visual_collision_alignment(self):
1116
1135
  """At render_scale=N, collision AABB is fully inside visual rect
1117
1136
  and vertex positions are pixel-accurate for known values."""
1118
- from tilemap_parser.runtime.map_object import load_map_objects
1119
1137
  from tilemap_parser.runtime.map_loader import TilemapData
1138
+ from tilemap_parser.runtime.map_object import load_map_objects
1120
1139
 
1121
1140
  with tempfile.TemporaryDirectory() as tmp:
1122
1141
  tmp = Path(tmp)
@@ -1144,10 +1163,18 @@ class TestLoadMapObjects:
1144
1163
  "shapes": [
1145
1164
  {
1146
1165
  "type": "polygon",
1147
- "vertices": [[32.0, 2.75], [8.0, 30.0], [10.5, 58.75],
1148
- [22.5, 65.25], [20.0, 88.25], [33.5, 81.75],
1149
- [47.5, 87.75], [41.5, 65.75], [58.5, 48.75],
1150
- [52.0, 25.0]],
1166
+ "vertices": [
1167
+ [32.0, 2.75],
1168
+ [8.0, 30.0],
1169
+ [10.5, 58.75],
1170
+ [22.5, 65.25],
1171
+ [20.0, 88.25],
1172
+ [33.5, 81.75],
1173
+ [47.5, 87.75],
1174
+ [41.5, 65.75],
1175
+ [58.5, 48.75],
1176
+ [52.0, 25.0],
1177
+ ],
1151
1178
  "one_way": False,
1152
1179
  }
1153
1180
  ],
@@ -1207,8 +1234,9 @@ class TestLoadMapObjects:
1207
1234
  # Verify surface size is pixel-accurate at integer render_scale
1208
1235
  expected_w = int(64 * rs_val)
1209
1236
  expected_h = int(96 * rs_val)
1210
- assert obj.surface.get_size() == (expected_w, expected_h), \
1237
+ assert obj.surface.get_size() == (expected_w, expected_h), (
1211
1238
  f"rs={rs_val}: surface {obj.surface.get_size()} != ({expected_w},{expected_h})"
1239
+ )
1212
1240
 
1213
1241
  # Verify position is pixel-accurate (float)
1214
1242
  assert obj.x == 27 * rs_val, f"rs={rs_val}: x={obj.x} != {27 * rs_val}"
@@ -1219,10 +1247,12 @@ class TestLoadMapObjects:
1219
1247
  # First vertex: (32.0, 2.75) + region_rect (0, 3) = world (32*rs, (3+2.75)*rs)
1220
1248
  expected_v0 = (32.0 * rs_val, (3.0 + 2.75) * rs_val)
1221
1249
  actual_v0 = shape.vertices[0]
1222
- assert abs(actual_v0[0] - expected_v0[0]) < 0.001, \
1250
+ assert abs(actual_v0[0] - expected_v0[0]) < 0.001, (
1223
1251
  f"rs={rs_val}: vertex[0].x {actual_v0[0]} != {expected_v0[0]}"
1224
- assert abs(actual_v0[1] - expected_v0[1]) < 0.001, \
1252
+ )
1253
+ assert abs(actual_v0[1] - expected_v0[1]) < 0.001, (
1225
1254
  f"rs={rs_val}: vertex[0].y {actual_v0[1]} != {expected_v0[1]}"
1255
+ )
1226
1256
 
1227
1257
  # Verify collision AABB is inside visual rect
1228
1258
  xs = [v[0] for v in shape.vertices]
@@ -1238,14 +1268,16 @@ class TestLoadMapObjects:
1238
1268
  vis_bottom = obj.y + expected_h
1239
1269
 
1240
1270
  margin = -0.001 # allow 0px floating point tolerance
1241
- assert coll_min_x >= vis_left + margin, \
1271
+ assert coll_min_x >= vis_left + margin, (
1242
1272
  f"rs={rs_val}: collision left {coll_min_x} < visual left {vis_left}"
1243
- assert coll_min_y >= vis_top + margin, \
1244
- f"rs={rs_val}: collision top {coll_min_y} < visual top {vis_top}"
1245
- assert coll_max_x <= vis_right - margin, \
1273
+ )
1274
+ assert coll_min_y >= vis_top + margin, f"rs={rs_val}: collision top {coll_min_y} < visual top {vis_top}"
1275
+ assert coll_max_x <= vis_right - margin, (
1246
1276
  f"rs={rs_val}: collision right {coll_max_x} > visual right {vis_right}"
1247
- assert coll_max_y <= vis_bottom - margin, \
1277
+ )
1278
+ assert coll_max_y <= vis_bottom - margin, (
1248
1279
  f"rs={rs_val}: collision bottom {coll_max_y} > visual bottom {vis_bottom}"
1280
+ )
1249
1281
 
1250
1282
  def test_invalid_tileset_index_skipped(self):
1251
1283
  """Object with ttype out of range is skipped."""
@@ -1320,5 +1352,28 @@ class TestLoadMapObjects:
1320
1352
  assert obj.collision_shapes == []
1321
1353
  assert obj.has_collision is False
1322
1354
 
1355
+ def test_ttype_default_minus_one(self):
1356
+ """Direct MapObject creation without ttype defaults to -1."""
1357
+ surf = pygame.Surface((16, 16))
1358
+ obj = MapObject(x=0, y=0, surface=surf)
1359
+ assert obj.ttype == -1
1323
1360
 
1361
+ def test_ttype_set_on_construction(self):
1362
+ """ttype is stored when passed to MapObject.__init__."""
1363
+ surf = pygame.Surface((16, 16))
1364
+ obj = MapObject(x=0, y=0, surface=surf, ttype=3)
1365
+ assert obj.ttype == 3
1324
1366
 
1367
+ def test_ttype_on_loaded_object(self, basic_map):
1368
+ """load_map_objects sets ttype from ParsedObject.ttype."""
1369
+ td, collision_dir, _ = basic_map
1370
+ objects = load_map_objects(td, collision_dir)
1371
+ assert len(objects) == 1
1372
+ assert objects[0].ttype == 0
1373
+
1374
+ def test_ttype_on_visual_only_object(self, map_no_collision_file):
1375
+ """Visual-only objects (require_collision=False) carry ttype."""
1376
+ td, collision_dir = map_no_collision_file
1377
+ objects = load_map_objects(td, collision_dir, require_collision=False)
1378
+ assert len(objects) == 1
1379
+ assert objects[0].ttype == 0
File without changes
File without changes
File without changes