From 30f52fd7cec64eac9aa10e8fdcf87d0f71a24420 Mon Sep 17 00:00:00 2001 From: Darren Eberly Date: Wed, 28 Jul 2021 22:37:30 -0400 Subject: [PATCH] Fix templates for tile objects. See #41 --- pytiled_parser/layer.py | 60 +++++++++++++++------------------- pytiled_parser/tiled_map.py | 39 ++++++++++++++++++++-- pytiled_parser/tiled_object.py | 44 ++++++++++++++++++++----- pytiled_parser/tileset.py | 14 +++++--- 4 files changed, 109 insertions(+), 48 deletions(-) diff --git a/pytiled_parser/layer.py b/pytiled_parser/layer.py index ede3375..194e3cb 100644 --- a/pytiled_parser/layer.py +++ b/pytiled_parser/layer.py @@ -11,10 +11,9 @@ See: import base64 import gzip import importlib.util -import sys import zlib from pathlib import Path -from typing import Any, Callable, List, Optional, Union +from typing import Any, List, Optional, Union from typing import cast as type_cast import attr @@ -284,7 +283,9 @@ def _decode_tile_layer_data( def _cast_chunk( - raw_chunk: RawChunk, encoding: Optional[str] = None, compression: str = None + raw_chunk: RawChunk, + encoding: Optional[str] = None, + compression: Optional[str] = None, ) -> Chunk: """Cast the raw_chunk to a Chunk. @@ -403,7 +404,8 @@ def _cast_tile_layer(raw_layer: RawLayer) -> TileLayer: def _cast_object_layer( - raw_layer: RawLayer, parent_dir: Optional[Path] = None + raw_layer: RawLayer, + parent_dir: Optional[Path] = None, ) -> ObjectLayer: """Cast the raw_layer to an ObjectLayer. @@ -458,46 +460,38 @@ def _cast_group_layer( layers = [] for layer in raw_layer["layers"]: - layers.append(cast(layer, parent_dir)) + layers.append(cast(layer, parent_dir=parent_dir)) return LayerGroup(layers=layers, **_get_common_attributes(raw_layer).__dict__) -def _get_caster(type_: str) -> Callable[[RawLayer], Layer]: - """Get the caster function for the raw layer. - - Args: - type_: the type of the layer - - Returns: - Callable[[RawLayer], Layer]: The caster function. - """ - casters = { - "tilelayer": _cast_tile_layer, - "objectgroup": _cast_object_layer, - "imagelayer": _cast_image_layer, - "group": _cast_group_layer, - } - return casters[type_] - - -def cast(raw_layer: RawLayer, parent_dir: Optional[Path] = None) -> Layer: +def cast( + raw_layer: RawLayer, + parent_dir: Optional[Path] = None, +) -> Layer: """Cast a raw Tiled layer into a pytiled_parser type. This function will determine the type of layer and cast accordingly. Args: raw_layer: Raw layer to be cast. + parent_dir: The parent directory that the map file is in. Returns: Layer: a properly typed Layer. - """ - caster = _get_caster(raw_layer["type"]) - if ( - caster.__name__ == "_cast_object_layer" - or caster.__name__ == "_cast_group_layer" - ): - return caster(raw_layer, parent_dir) - else: - return caster(raw_layer) + Raises: + RuntimeError: For an invalid layer type being provided + """ + type_ = raw_layer["type"] + + if type_ == "objectgroup": + return _cast_object_layer(raw_layer, parent_dir) + elif type_ == "group": + return _cast_group_layer(raw_layer, parent_dir) + elif type_ == "imagelayer": + return _cast_image_layer(raw_layer) + elif type_ == "tilelayer": + return _cast_tile_layer(raw_layer) + + raise RuntimeError(f"An invalid layer type of {type_} was supplied") diff --git a/pytiled_parser/tiled_map.py b/pytiled_parser/tiled_map.py index ce8975a..1f56885 100644 --- a/pytiled_parser/tiled_map.py +++ b/pytiled_parser/tiled_map.py @@ -132,12 +132,16 @@ def parse_map(file: Path) -> TiledMap: tileset_path = Path(parent_dir / raw_tileset["source"]) with open(tileset_path) as raw_tileset_file: tilesets[raw_tileset["firstgid"]] = tileset.cast( - json.load(raw_tileset_file), external_path=tileset_path.parent + json.load(raw_tileset_file), + raw_tileset["firstgid"], + external_path=tileset_path.parent, ) else: # Is an embedded Tileset raw_tileset = typing_cast(RawTileSet, raw_tileset) - tilesets[raw_tileset["firstgid"]] = tileset.cast(raw_tileset) + tilesets[raw_tileset["firstgid"]] = tileset.cast( + raw_tileset, raw_tileset["firstgid"] + ) if isinstance(raw_tiled_map["version"], float): version = str(raw_tiled_map["version"]) @@ -160,6 +164,37 @@ def parse_map(file: Path) -> TiledMap: version=version, ) + layers = [layer for layer in map_.layers if hasattr(layer, "tiled_objects")] + + for my_layer in layers: + for tiled_object in my_layer.tiled_objects: # type: ignore + if hasattr(tiled_object, "new_tileset"): + if tiled_object.new_tileset: + already_loaded = None + for val in map_.tilesets.values(): + if val.name == tiled_object.new_tileset["name"]: + already_loaded = val + break + + if not already_loaded: + highest_firstgid = max(map_.tilesets.keys()) + last_tileset_count = map_.tilesets[highest_firstgid].tile_count + new_firstgid = highest_firstgid + last_tileset_count + map_.tilesets[new_firstgid] = tileset.cast( + tiled_object.new_tileset, + new_firstgid, + tiled_object.new_tileset_path, + ) + tiled_object.gid = tiled_object.gid + (new_firstgid - 1) + + else: + tiled_object.gid = tiled_object.gid + ( + already_loaded.firstgid - 1 + ) + + tiled_object.new_tileset = None + tiled_object.new_tileset_path = None + if raw_tiled_map.get("backgroundcolor") is not None: map_.background_color = parse_color(raw_tiled_map["backgroundcolor"]) diff --git a/pytiled_parser/tiled_object.py b/pytiled_parser/tiled_object.py index 4c4c649..3d167d0 100644 --- a/pytiled_parser/tiled_object.py +++ b/pytiled_parser/tiled_object.py @@ -1,7 +1,7 @@ # pylint: disable=too-few-public-methods import json from pathlib import Path -from typing import Callable, Dict, List, Optional, Union +from typing import Any, Callable, Dict, List, Optional, Union import attr from typing_extensions import TypedDict @@ -148,6 +148,8 @@ class Tile(TiledObject): """ gid: int + new_tileset: Optional[Dict[str, Any]] = None + new_tileset_path: Optional[Path] = None class RawTextDict(TypedDict): @@ -257,7 +259,11 @@ def _cast_point(raw_tiled_object: RawTiledObject) -> Point: return Point(**_get_common_attributes(raw_tiled_object).__dict__) -def _cast_tile(raw_tiled_object: RawTiledObject) -> Tile: +def _cast_tile( + raw_tiled_object: RawTiledObject, + new_tileset: Optional[Dict[str, Any]] = None, + new_tileset_path: Optional[Path] = None, +) -> Tile: """Cast the raw_tiled_object to a Tile object. Args: @@ -268,7 +274,12 @@ def _cast_tile(raw_tiled_object: RawTiledObject) -> Tile: """ gid = raw_tiled_object["gid"] - return Tile(gid=gid, **_get_common_attributes(raw_tiled_object).__dict__) + return Tile( + gid=gid, + new_tileset=new_tileset, + new_tileset_path=new_tileset_path, + **_get_common_attributes(raw_tiled_object).__dict__ + ) def _cast_polygon(raw_tiled_object: RawTiledObject) -> Polygon: @@ -393,16 +404,24 @@ def _get_caster( def cast( - raw_tiled_object: RawTiledObject, parent_dir: Optional[Path] = None + raw_tiled_object: RawTiledObject, + parent_dir: Optional[Path] = None, ) -> TiledObject: """Cast the raw tiled object into a pytiled_parser type Args: raw_tiled_object: Raw Tiled object that is to be cast. + parent_dir: The parent directory that the map file is in. Returns: TiledObject: a properly typed Tiled object. + + Raises: + RuntimeError: When a required parameter was not sent based on a condition. """ + new_tileset = None + new_tileset_path = None + if raw_tiled_object.get("template"): if not parent_dir: raise RuntimeError( @@ -410,12 +429,21 @@ def cast( ) template_path = Path(parent_dir / raw_tiled_object["template"]) with open(template_path) as raw_template_file: - loaded_template = json.load(raw_template_file)["object"] + template = json.load(raw_template_file) + if "tileset" in template: + tileset_path = Path( + template_path.parent / template["tileset"]["source"] + ) + with open(tileset_path) as raw_tileset_file: + new_tileset = json.load(raw_tileset_file) + new_tileset_path = tileset_path.parent + + loaded_template = template["object"] for key in loaded_template: if key != "id": raw_tiled_object[key] = loaded_template[key] # type: ignore - caster = _get_caster(raw_tiled_object) + if raw_tiled_object.get("gid"): + return _cast_tile(raw_tiled_object, new_tileset, new_tileset_path) - tiled_object = caster(raw_tiled_object) - return tiled_object + return _get_caster(raw_tiled_object)(raw_tiled_object) diff --git a/pytiled_parser/tileset.py b/pytiled_parser/tileset.py index 68fb8bc..25bd190 100644 --- a/pytiled_parser/tileset.py +++ b/pytiled_parser/tileset.py @@ -130,6 +130,8 @@ class Tileset: tile_count: int columns: int + firstgid: int + type: str = "tileset" spacing: int = 0 @@ -144,7 +146,6 @@ class Tileset: transformations: Optional[Transformations] = None - firstgid: Optional[int] = None background_color: Optional[Color] = None tile_offset: Optional[OrderedPair] = None transparent_color: Optional[Color] = None @@ -329,11 +330,16 @@ def _cast_grid(raw_grid: RawGrid) -> Grid: ) -def cast(raw_tileset: RawTileSet, external_path: Optional[Path] = None) -> Tileset: +def cast( + raw_tileset: RawTileSet, + firstgid: int, + external_path: Optional[Path] = None, +) -> Tileset: """Cast the raw tileset into a pytiled_parser type Args: raw_tileset: Raw Tileset to be cast. + firstgid: GID corresponding the first tile in the set. external_path: The path to the tileset if it is not an embedded one. Returns: @@ -348,6 +354,7 @@ def cast(raw_tileset: RawTileSet, external_path: Optional[Path] = None) -> Tiles columns=raw_tileset["columns"], spacing=raw_tileset["spacing"], margin=raw_tileset["margin"], + firstgid=firstgid, ) if raw_tileset.get("version") is not None: @@ -373,9 +380,6 @@ def cast(raw_tileset: RawTileSet, external_path: Optional[Path] = None) -> Tiles if raw_tileset.get("imageheight") is not None: tileset.image_height = raw_tileset["imageheight"] - if raw_tileset.get("firstgid") is not None: - tileset.firstgid = raw_tileset["firstgid"] - if raw_tileset.get("backgroundcolor") is not None: tileset.background_color = parse_color(raw_tileset["backgroundcolor"])