tilemap-parser 3.1.10__tar.gz → 3.1.12__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-3.1.10 → tilemap_parser-3.1.12}/PKG-INFO +1 -1
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/pyproject.toml +1 -1
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/__init__.py +9 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/parser/__init__.py +14 -0
- tilemap_parser-3.1.12/src/tilemap_parser/parser/particle.py +154 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/runtime/__init__.py +16 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/runtime/map_loader.py +23 -9
- tilemap_parser-3.1.12/src/tilemap_parser/runtime/particles.py +570 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser.egg-info/PKG-INFO +1 -1
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser.egg-info/SOURCES.txt +2 -0
- tilemap_parser-3.1.12/tests/test_map_loader.py +275 -0
- tilemap_parser-3.1.10/tests/test_map_loader.py +0 -122
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/LICENSE +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/README.md +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/setup.cfg +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/parser/animation.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/parser/collision.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/parser/collision_loader.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/parser/map_parse.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/parser/node_parse.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/runtime/animation_player.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/runtime/area_node.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/runtime/collision_cache.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/runtime/object_collision.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/runtime/renderer.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/runtime/tile_collision.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/utils/__init__.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser/utils/geometry.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser.egg-info/dependency_links.txt +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser.egg-info/requires.txt +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/src/tilemap_parser.egg-info/top_level.txt +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/tests/test_collision.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/tests/test_geometry.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/tests/test_object_collision.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/tests/test_render_scale.py +0 -0
- {tilemap_parser-3.1.10 → tilemap_parser-3.1.12}/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.
|
|
3
|
+
Version: 3.1.12
|
|
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.
|
|
7
|
+
version = "3.1.12"
|
|
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"
|
|
@@ -45,6 +45,15 @@ __all__ = [
|
|
|
45
45
|
"TileLayerRenderer",
|
|
46
46
|
"TilesetCollision",
|
|
47
47
|
"TilemapData",
|
|
48
|
+
"Particle",
|
|
49
|
+
"ParticleEmitter",
|
|
50
|
+
"ParticleRenderer",
|
|
51
|
+
"ParticleSystem",
|
|
52
|
+
"ParticleSystemConfig",
|
|
53
|
+
"SpriteBatchRenderer",
|
|
54
|
+
"clear_texture_caches",
|
|
55
|
+
"parse_particle_dict",
|
|
56
|
+
"parse_particle_file",
|
|
48
57
|
"aabb_overlap",
|
|
49
58
|
"check_collision",
|
|
50
59
|
"circle_vs_circle",
|
|
@@ -46,6 +46,14 @@ from .map_parse import (
|
|
|
46
46
|
parse_map_json,
|
|
47
47
|
)
|
|
48
48
|
from .node_parse import ParsedNode, parse_nodes_dict, parse_nodes_file
|
|
49
|
+
from .particle import (
|
|
50
|
+
ALPHA_FADE_MODES,
|
|
51
|
+
EMISSION_SHAPES,
|
|
52
|
+
PARTICLE_SHAPES,
|
|
53
|
+
ParticleSystemConfig,
|
|
54
|
+
parse_particle_dict,
|
|
55
|
+
parse_particle_file,
|
|
56
|
+
)
|
|
49
57
|
|
|
50
58
|
__all__ = [
|
|
51
59
|
"AnimationClip",
|
|
@@ -90,4 +98,10 @@ __all__ = [
|
|
|
90
98
|
"parse_nodes_file",
|
|
91
99
|
"parse_object_collision",
|
|
92
100
|
"parse_tileset_collision",
|
|
101
|
+
"ALPHA_FADE_MODES",
|
|
102
|
+
"EMISSION_SHAPES",
|
|
103
|
+
"PARTICLE_SHAPES",
|
|
104
|
+
"ParticleSystemConfig",
|
|
105
|
+
"parse_particle_dict",
|
|
106
|
+
"parse_particle_file",
|
|
93
107
|
]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
7
|
+
|
|
8
|
+
from .map_parse import (
|
|
9
|
+
JsonDict,
|
|
10
|
+
MapParseError,
|
|
11
|
+
_coerce_float,
|
|
12
|
+
_coerce_int,
|
|
13
|
+
_optional_dict,
|
|
14
|
+
_require_dict,
|
|
15
|
+
_require_list,
|
|
16
|
+
_require_str,
|
|
17
|
+
_ctx,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
EMISSION_SHAPES = ["point", "rect", "circle", "line"]
|
|
22
|
+
PARTICLE_SHAPES = ["circle", "square", "diamond", "star", "sparkle", "smoke", "heart", "line"]
|
|
23
|
+
ALPHA_FADE_MODES = ["none", "fade_out", "fade_in", "fade_both"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ParticleSystemConfig:
|
|
28
|
+
name: str
|
|
29
|
+
emission_shape: str = "point"
|
|
30
|
+
particle_shape: str = "circle"
|
|
31
|
+
particle_size_min: int = 2
|
|
32
|
+
particle_size_max: int = 6
|
|
33
|
+
spawn_rate: int = 20
|
|
34
|
+
max_particles: int = 100
|
|
35
|
+
lifetime_min: float = 0.5
|
|
36
|
+
lifetime_max: float = 2.0
|
|
37
|
+
speed_min: float = 20.0
|
|
38
|
+
speed_max: float = 60.0
|
|
39
|
+
direction: float = -1.0
|
|
40
|
+
spread: float = 45.0
|
|
41
|
+
gravity_x: float = 0.0
|
|
42
|
+
gravity_y: float = 30.0
|
|
43
|
+
start_color_r: int = 255
|
|
44
|
+
start_color_g: int = 200
|
|
45
|
+
start_color_b: int = 100
|
|
46
|
+
start_color_a: int = 255
|
|
47
|
+
end_color_r: int = 255
|
|
48
|
+
end_color_g: int = 100
|
|
49
|
+
end_color_b: int = 50
|
|
50
|
+
end_color_a: int = 0
|
|
51
|
+
start_scale: float = 1.0
|
|
52
|
+
end_scale: float = 0.3
|
|
53
|
+
rotation_speed: float = 0.0
|
|
54
|
+
alpha_fade: str = "fade_out"
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> JsonDict:
|
|
57
|
+
return {
|
|
58
|
+
"emission_shape": self.emission_shape,
|
|
59
|
+
"particle_shape": self.particle_shape,
|
|
60
|
+
"particle_size_min": self.particle_size_min,
|
|
61
|
+
"particle_size_max": self.particle_size_max,
|
|
62
|
+
"spawn_rate": self.spawn_rate,
|
|
63
|
+
"max_particles": self.max_particles,
|
|
64
|
+
"lifetime_min": self.lifetime_min,
|
|
65
|
+
"lifetime_max": self.lifetime_max,
|
|
66
|
+
"speed_min": self.speed_min,
|
|
67
|
+
"speed_max": self.speed_max,
|
|
68
|
+
"direction": self.direction,
|
|
69
|
+
"spread": self.spread,
|
|
70
|
+
"gravity_x": self.gravity_x,
|
|
71
|
+
"gravity_y": self.gravity_y,
|
|
72
|
+
"start_color_r": self.start_color_r,
|
|
73
|
+
"start_color_g": self.start_color_g,
|
|
74
|
+
"start_color_b": self.start_color_b,
|
|
75
|
+
"start_color_a": self.start_color_a,
|
|
76
|
+
"end_color_r": self.end_color_r,
|
|
77
|
+
"end_color_g": self.end_color_g,
|
|
78
|
+
"end_color_b": self.end_color_b,
|
|
79
|
+
"end_color_a": self.end_color_a,
|
|
80
|
+
"start_scale": self.start_scale,
|
|
81
|
+
"end_scale": self.end_scale,
|
|
82
|
+
"rotation_speed": self.rotation_speed,
|
|
83
|
+
"alpha_fade": self.alpha_fade,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def from_dict(cls, d: JsonDict, name: str = "") -> "ParticleSystemConfig":
|
|
88
|
+
def g(key: str, default: Any = 0) -> Any:
|
|
89
|
+
return d.get(key, default)
|
|
90
|
+
|
|
91
|
+
cfg = cls(name=name)
|
|
92
|
+
cfg.emission_shape = str(g("emission_shape", "point"))
|
|
93
|
+
if cfg.emission_shape not in EMISSION_SHAPES:
|
|
94
|
+
cfg.emission_shape = "point"
|
|
95
|
+
cfg.particle_shape = str(g("particle_shape", "circle"))
|
|
96
|
+
if cfg.particle_shape not in PARTICLE_SHAPES:
|
|
97
|
+
cfg.particle_shape = "circle"
|
|
98
|
+
cfg.alpha_fade = str(g("alpha_fade", "fade_out"))
|
|
99
|
+
if cfg.alpha_fade not in ALPHA_FADE_MODES:
|
|
100
|
+
cfg.alpha_fade = "fade_out"
|
|
101
|
+
cfg.particle_size_min = max(1, int(g("particle_size_min", 2)))
|
|
102
|
+
cfg.particle_size_max = max(cfg.particle_size_min, int(g("particle_size_max", 6)))
|
|
103
|
+
cfg.spawn_rate = max(1, int(g("spawn_rate", 20)))
|
|
104
|
+
cfg.max_particles = max(1, int(g("max_particles", 100)))
|
|
105
|
+
cfg.lifetime_min = max(0.1, float(g("lifetime_min", 0.5)))
|
|
106
|
+
cfg.lifetime_max = max(cfg.lifetime_min, float(g("lifetime_max", 2.0)))
|
|
107
|
+
cfg.speed_min = max(0.0, float(g("speed_min", 20)))
|
|
108
|
+
cfg.speed_max = max(cfg.speed_min, float(g("speed_max", 60)))
|
|
109
|
+
cfg.direction = float(g("direction", -1))
|
|
110
|
+
cfg.spread = max(0.0, min(360.0, float(g("spread", 45))))
|
|
111
|
+
cfg.gravity_x = float(g("gravity_x", 0))
|
|
112
|
+
cfg.gravity_y = float(g("gravity_y", 30))
|
|
113
|
+
cfg.start_color_r = max(0, min(255, int(g("start_color_r", 255))))
|
|
114
|
+
cfg.start_color_g = max(0, min(255, int(g("start_color_g", 200))))
|
|
115
|
+
cfg.start_color_b = max(0, min(255, int(g("start_color_b", 100))))
|
|
116
|
+
cfg.start_color_a = max(0, min(255, int(g("start_color_a", 255))))
|
|
117
|
+
cfg.end_color_r = max(0, min(255, int(g("end_color_r", 255))))
|
|
118
|
+
cfg.end_color_g = max(0, min(255, int(g("end_color_g", 100))))
|
|
119
|
+
cfg.end_color_b = max(0, min(255, int(g("end_color_b", 50))))
|
|
120
|
+
cfg.end_color_a = max(0, min(255, int(g("end_color_a", 0))))
|
|
121
|
+
cfg.start_scale = max(0.1, float(g("start_scale", 1.0)))
|
|
122
|
+
cfg.end_scale = max(0.1, float(g("end_scale", 0.3)))
|
|
123
|
+
cfg.rotation_speed = float(g("rotation_speed", 0))
|
|
124
|
+
return cfg
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _parse_system(raw: Any, ctx: str) -> ParticleSystemConfig:
|
|
128
|
+
d = _require_dict(raw, ctx)
|
|
129
|
+
name = str(d.get("name", ""))
|
|
130
|
+
cfg_raw = _optional_dict(d.get("config"), f"{ctx}.config")
|
|
131
|
+
if cfg_raw is None:
|
|
132
|
+
cfg_raw = {}
|
|
133
|
+
return ParticleSystemConfig.from_dict(cfg_raw, name=name)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def parse_particle_dict(root: JsonDict) -> List[ParticleSystemConfig]:
|
|
137
|
+
root = _require_dict(root, "root")
|
|
138
|
+
raw_systems = _require_list(root.get("particle_systems", []), "root.particle_systems")
|
|
139
|
+
return [_parse_system(item, f"root.particle_systems[{i}]") for i, item in enumerate(raw_systems)]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def parse_particle_file(path: Union[str, Path]) -> List[ParticleSystemConfig]:
|
|
143
|
+
p = Path(path)
|
|
144
|
+
if not p.is_file():
|
|
145
|
+
raise MapParseError(f"Not a file: {p}")
|
|
146
|
+
try:
|
|
147
|
+
text = p.read_text(encoding="utf-8")
|
|
148
|
+
except OSError as e:
|
|
149
|
+
raise MapParseError(f"Cannot read {p}: {e}") from e
|
|
150
|
+
try:
|
|
151
|
+
payload = json.loads(text)
|
|
152
|
+
except json.JSONDecodeError as e:
|
|
153
|
+
raise MapParseError(f"Invalid JSON in {p}: {e}") from e
|
|
154
|
+
return parse_particle_dict(payload)
|
|
@@ -24,6 +24,15 @@ from .object_collision import (
|
|
|
24
24
|
)
|
|
25
25
|
from .renderer import LayerRenderStats, TileLayerRenderer
|
|
26
26
|
from .area_node import AreaNode
|
|
27
|
+
from .particles import (
|
|
28
|
+
Particle,
|
|
29
|
+
ParticleEmitter,
|
|
30
|
+
ParticleEmitterNode,
|
|
31
|
+
ParticleRenderer,
|
|
32
|
+
ParticleSystem,
|
|
33
|
+
SpriteBatchRenderer,
|
|
34
|
+
clear_texture_caches,
|
|
35
|
+
)
|
|
27
36
|
|
|
28
37
|
__all__ = [
|
|
29
38
|
"AnimationPlayer",
|
|
@@ -49,4 +58,11 @@ __all__ = [
|
|
|
49
58
|
"load_map",
|
|
50
59
|
"load_object_collision",
|
|
51
60
|
"load_tileset_collision",
|
|
61
|
+
"Particle",
|
|
62
|
+
"ParticleEmitter",
|
|
63
|
+
"ParticleEmitterNode",
|
|
64
|
+
"ParticleRenderer",
|
|
65
|
+
"ParticleSystem",
|
|
66
|
+
"SpriteBatchRenderer",
|
|
67
|
+
"clear_texture_caches",
|
|
52
68
|
]
|
|
@@ -10,6 +10,8 @@ from pygame import Rect, Surface
|
|
|
10
10
|
|
|
11
11
|
from ..parser.map_parse import MapParseError, ParsedLayer, ParsedMap, ParsedTile, parse_map_file
|
|
12
12
|
from ..parser.node_parse import parse_nodes_dict
|
|
13
|
+
from .area_node import AreaNode
|
|
14
|
+
from .particles import ParticleEmitterNode
|
|
13
15
|
|
|
14
16
|
PathLike = Union[str, Path]
|
|
15
17
|
|
|
@@ -29,6 +31,8 @@ class TilemapData:
|
|
|
29
31
|
self.resolved_paths = resolved_paths
|
|
30
32
|
self.warnings = warnings
|
|
31
33
|
self.map_path = map_path
|
|
34
|
+
self.area_nodes: List[AreaNode] = []
|
|
35
|
+
self.particle_emitters: List[ParticleEmitterNode] = []
|
|
32
36
|
self._tw, self._th = parsed.meta.tile_size
|
|
33
37
|
self._build_path_index()
|
|
34
38
|
self._normalize_tile_ttypes()
|
|
@@ -40,6 +44,7 @@ class TilemapData:
|
|
|
40
44
|
*,
|
|
41
45
|
extra_search_base: Optional[Path] = None,
|
|
42
46
|
skip_missing_images: bool = True,
|
|
47
|
+
nodes_dir: Optional[PathLike] = None,
|
|
43
48
|
) -> "TilemapData":
|
|
44
49
|
p = Path(path)
|
|
45
50
|
parsed = parse_map_file(p)
|
|
@@ -69,12 +74,16 @@ class TilemapData:
|
|
|
69
74
|
surfaces.append(None)
|
|
70
75
|
|
|
71
76
|
nodes_name = f"{p.stem}.nodes.json"
|
|
72
|
-
nodes_candidates = [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
nodes_candidates: List[Path] = []
|
|
78
|
+
if nodes_dir is not None:
|
|
79
|
+
nodes_candidates.append(Path(nodes_dir) / nodes_name)
|
|
80
|
+
else:
|
|
81
|
+
nodes_candidates = [
|
|
82
|
+
map_dir / nodes_name,
|
|
83
|
+
map_dir.parent / "nodes" / nodes_name,
|
|
84
|
+
]
|
|
85
|
+
if extra_search_base is not None:
|
|
86
|
+
nodes_candidates.append(extra_search_base / "nodes" / nodes_name)
|
|
78
87
|
for nodes_path in nodes_candidates:
|
|
79
88
|
if nodes_path.is_file():
|
|
80
89
|
try:
|
|
@@ -89,7 +98,12 @@ class TilemapData:
|
|
|
89
98
|
warnings.append(f"Failed to load nodes: {e}")
|
|
90
99
|
break
|
|
91
100
|
|
|
92
|
-
|
|
101
|
+
result = cls(parsed, surfaces, resolved_paths, warnings, map_path=p)
|
|
102
|
+
result.area_nodes = [AreaNode(n) for n in parsed.nodes if n.node_type == "area"]
|
|
103
|
+
result.particle_emitters = [
|
|
104
|
+
ParticleEmitterNode(n) for n in parsed.nodes if n.node_type == "particle_emitter"
|
|
105
|
+
]
|
|
106
|
+
return result
|
|
93
107
|
|
|
94
108
|
def _build_path_index(self) -> None:
|
|
95
109
|
self._path_to_index: Dict[str, int] = {}
|
|
@@ -246,5 +260,5 @@ def _resolve_resource_path(path_str: str, map_dir: Path, extra_search_base: Opti
|
|
|
246
260
|
return candidate
|
|
247
261
|
|
|
248
262
|
|
|
249
|
-
def load_map(path: PathLike, *, extra_search_base: Optional[Path] = None, skip_missing_images: bool = True) -> TilemapData:
|
|
250
|
-
return TilemapData.load(path, extra_search_base=extra_search_base, skip_missing_images=skip_missing_images)
|
|
263
|
+
def load_map(path: PathLike, *, extra_search_base: Optional[Path] = None, skip_missing_images: bool = True, nodes_dir: Optional[PathLike] = None) -> TilemapData:
|
|
264
|
+
return TilemapData.load(path, extra_search_base=extra_search_base, skip_missing_images=skip_missing_images, nodes_dir=nodes_dir)
|