1
0
mirror of https://github.com/ScrelliCopter/tmx2gba.git synced 2025-02-21 03:29:25 +11:00

32 Commits

Author SHA1 Message Date
5937455000 Linux build fixes 2025-01-23 19:00:16 +11:00
224e1e53e9 fix empty objects list triggering an assertion failure 2024-04-11 16:31:17 +10:00
b8d7d43899 argparse: fix unknown flags not being reported 2024-04-11 16:21:33 +10:00
056612667b update readme 2024-04-11 15:34:53 +10:00
e6053f9472 restore object reading 2024-04-11 10:36:16 +10:00
6b786d36fb try to add pdbs to windows artifacts 2024-04-11 07:41:58 +10:00
708fd13d08 use modern pugixml target 2024-04-11 07:27:43 +10:00
6a6d589817 refactor tmxmap 2024-04-11 07:16:13 +10:00
fcb9eceec3 collect string handling utility functions into strtools 2024-04-11 06:37:09 +10:00
2638bf2667 merge help and version commands 2024-04-11 04:37:49 +10:00
b29c61774c normalise license snippets/copyright & cmake stuff 2024-04-11 04:25:28 +10:00
835b80256f implement remaining encodings 2024-04-10 20:31:55 +10:00
677d59f096 fix tilesets & implement xml tile reader 2024-04-10 19:37:32 +10:00
b6308816ae msvc fixes 2024-04-10 12:46:34 +10:00
0dd9074e27 restore full compression support 2024-04-10 10:21:37 +10:00
5daf57ffe1 remove masm hex generator 2024-04-10 06:47:12 +10:00
6c52897942 fix include 2024-04-10 06:45:45 +10:00
e6bb098e15 replace rapidxml uses with pugixml 2024-04-10 06:42:58 +10:00
c2e9f5c974 break map reader into its own class 2024-04-10 02:49:52 +10:00
385f7b069f restore build & basic functionality 2024-04-10 02:31:43 +10:00
ebe83ffb68 Revert "port to tmxlite"
This reverts commit 35abaf71
2024-04-08 03:28:28 +10:00
f9928df187 argparse: use replace raw char* strings with string views 2024-04-08 02:22:23 +10:00
d69eec8dcf argparse: fix not setting required 2024-04-07 21:24:29 +10:00
e53f988e46 Merge branch 'refs/heads/tmxlite' 2024-04-07 13:05:33 +10:00
bc930b8ca4 Update README.md 2024-04-07 12:59:52 +10:00
8961343558 fix zlib build 2024-04-07 09:53:46 +10:00
f8a6b976c9 gzip support for miniz 2024-04-07 09:41:19 +10:00
a7617f3a3a tmxlite: further cleanup 2024-03-28 22:15:57 +11:00
849ff6bcc8 slightly simplify tmxlite subproject 2024-03-28 21:42:00 +11:00
b06ad7cd79 revamp pugixml find behaviour 2024-03-28 21:14:49 +11:00
320bc19f2d revamp zstd find behaviour 2024-03-28 19:46:21 +11:00
1db1797a27 gitattributes: vendor c sources 2024-03-28 16:45:50 +11:00
61 changed files with 1048 additions and 5030 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.c linguist-vendored

View File

@@ -23,7 +23,7 @@ jobs:
- { name: "Windows MSVC x86", os: windows-latest, artifact: windows-x86, arch: x86 }
- { name: "Windows MSVC x64", os: windows-latest, artifact: windows-x64 }
- { name: "Windows MSVC ARM64", os: windows-latest, artifact: windows-arm64, arch: amd64_arm64 }
- { name: "Ubuntu", artifact: "linux", os: ubuntu-latest, extra: "-DUSE_BUNDLED_ZSTD:BOOL=OFF" }
- { name: "Ubuntu", artifact: "linux", os: ubuntu-latest, extra: "-DUSE_BUNDLED_ZSTD:BOOL=OFF -DUSE_BUNDLED_PUGIXML:BOOL=OFF" }
runs-on: ${{matrix.config.os}}
steps:
@@ -38,7 +38,7 @@ jobs:
- uses: awalsh128/cache-apt-pkgs-action@latest
if: ${{matrix.config.artifact == 'linux'}}
with:
packages: libzstd-dev
packages: libzstd-dev libpugixml-dev
version: 1.0
- name: Configure CMake
@@ -54,4 +54,4 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: ${{env.ARTIFACT_NAME}}-${{matrix.config.artifact}}
path: build/src/${{env.ARTIFACT_NAME}}${{startsWith(matrix.config.os, 'windows') && '.exe' || ''}}
path: build/src/${{env.ARTIFACT_NAME}}${{startsWith(matrix.config.os, 'windows') && '.[ep][xd][eb]' || ''}}

View File

@@ -8,7 +8,6 @@ project(tmx2gba
option(USE_ZLIB "Use zlib instead of bundled miniz" "${UNIX}")
option(USE_BUNDLED_PUGIXML "Use bundled PUGIXML" ON)
option(USE_BUNDLED_ZSTD "Use bundled libzstd" ON)
option(USE_BUNDLED_TMXLITE "Use bundled tmxlite" ON)
option(TMX2GBA_DKP_INSTALL "Install into DEVKITPRO prefix" OFF)
@@ -19,13 +18,13 @@ if (ENABLE_ASAN)
add_link_options(-fsanitize=address -shared-libasan)
endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules/")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
# Libraries
if (USE_BUNDLED_PUGIXML)
add_subdirectory(ext/pugixml)
else()
find_package(PUGIXML REQUIRED)
find_package(pugixml REQUIRED CONFIG)
endif()
if (USE_ZLIB)
@@ -37,17 +36,11 @@ endif()
if (USE_BUNDLED_ZSTD)
add_subdirectory(ext/zstd)
else()
find_package(ZSTD REQUIRED)
find_package(Zstd REQUIRED)
endif()
add_subdirectory(ext/base64)
if (USE_BUNDLED_TMXLITE)
add_subdirectory(ext/tmxlite)
else()
find_package(TMXLITE REQUIRED)
endif()
# Main tmx2gba sources
add_subdirectory(src)

View File

@@ -1,43 +1,38 @@
# tmx2gba #
tmx2gba is a simple command line utility that converts [Tiled](http://www.mapeditor.org/) .tmx maps to Game Boy Advance formatted charmaps.
Originally developed for my own personal use, I've thrown it up in case this is of use to anyone else.
If you find a bug, please open an issue.
Enjoy!
### Features ###
* Exports to raw binary that can be easily memcpy'd into VRAM.
* Export raw charmaps that can be easily memcpy'd into VRAM.
* Preserves tile flipping.
* Supports per-tile palette specification.
* Custom collision layer support.
* Support for objects with id mapping.
### How do I use it? ###
## Usage ##
```
tmx2gba [-h] [-r offset] [-lyc name] [-p 0-15] <-i inpath> <-o outpath>
tmx2gba [-h] [-r offset] [-lyc name] [-p 0-15] [-m name;id] <-i inpath> <-o outpath>
```
| Command | Required | Notes |
|--------------|----------|-----------------------------------------------------------------------|
| -h | N/A | Display help & command info. |
| -v | No | Display version & quit. |
| -l (name) | No | Name of layer to use (default first layer in TMX). |
| -y (name) | No | Layer for palette mappings. |
| -c (name) | No | Output a separate 8bit collision map of the specified layer. |
| -r (offset) | No | Offset tile indices (default 0). |
| -p (0-15) | No | Select which palette to use for 4-bit tilesets. |
| -m (name;id) | No | Map an object name to an ID, will enable object exports. |
| -i (path) | *Yes* | Path to input TMX file. |
| -o (path) | *Yes* | Path to output files. |
| -f <file> | No | Command line instructions list for easy integration with buildscripts |
| Command | Required | Notes |
|--------------|----------|------------------------------------------------------------------------------------|
| -h | N/A | Display program help & command info then quit |
| -l (name) | No | Name of layer to use (default first layer in TMX) |
| -y (name) | No | Layer for palette mappings |
| -c (name) | No | Output a separate 8bit collision map of the specified layer |
| -r (offset) | No | Offset tile indices (default 0) |
| -p (0-15) | No | Select which palette to use for 4-bit tilesets |
| -m (name;id) | No | Map an object name to an ID, will enable object exports |
| -i (path) | *Yes* | Path to input TMX file |
| -o (path) | *Yes* | Path to output files |
| -f <file> | No | Flag file containing command-line arguments for easy integration with buildscripts |
### How do I build it? ###
## Building ##
Dependencies for building are CMake 3.x and a C++11 compliant compiler,
Dependencies for building are CMake 3.15 and a C++20 compliant compiler,
all other dependencies are in-tree so you should be able to build with:
```bash
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
make -C build -j$(nproc --all)
cmake --build build
```
Optionally, you may install it to use it system wide:
@@ -45,10 +40,8 @@ Optionally, you may install it to use it system wide:
sudo cmake --install build
```
Which will copy the tmx2gba executable to /usr/local/bin/tmx2gba by default,
if you prefer to use /usr for some reason you may specify a prefix like so:
```bash
sudo cmake --install build --prefix /usr
```
`--prefix /usr` can be used to override install location.
If you're a devkitPro user and would prefer to keep all your development tools compartmentalised
you may optionally install to the tools directory with the `TMX2GBA_DKP_INSTALL` option (OFF by default).
The build scripts will respect your `DEVKITPRO` environment variable but if not set will install to
@@ -63,32 +56,10 @@ sudo cmake --install build
* Add support for multi-SBB prepared charmaps.
* Check if this works for NDS as well.
* Compression support.
* Support for less common TMX formats.
### License ###
[tmx2gba](https://github.com/ScrelliCopter/tmx2gba) is licensed under the zlib license.
[RapidXML](http://rapidxml.sourceforge.net/) is licensed under the Boost & MIT licenses.
[René Nyffenegger's base64.cpp](https://github.com/ReneNyffenegger/cpp-base64) is licensed under the zlib license.
[miniz](https://github.com/richgel999/miniz) is public domain software.
[ultragetopt](https://github.com/kevinoid/ultragetopt) is licensed under the MIT license.
```
Copyright (C) 2015-2023 a dinosaur
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
```
## License ##
[tmx2gba](https://github.com/ScrelliCopter/tmx2gba) is licensed under the [Zlib license](COPYING.txt).
- [pugixml](https://pugixml.org/) is licensed under the [MIT license](ext/pugixml/LICENSE.md).
- [René Nyffenegger's base64.cpp](https://github.com/ReneNyffenegger/cpp-base64) is licensed under the [Zlib license](ext/base64/LICENSE).
- [miniz](https://github.com/richgel999/miniz) is licensed under the [MIT license](ext/miniz/LICENSE).
- [ZStandard](https://facebook.github.io/zstd/) is licensed under the [BSD 3-clause license](ext/zstd/LICENSE).

View File

@@ -1,10 +0,0 @@
find_path(PUGIXML_INCLUDE_DIR NAMES pugixml.hpp)
find_library(PUGIXML_LIBRARY NAMES pugixml)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PUGIXML DEFAULT_MSG
PUGIXML_LIBRARY PUGIXML_INCLUDE_DIR)
mark_as_advanced(PUGIXML_INCLUDE_DIR
PUGIXML_LIBRARY)

View File

@@ -1,10 +0,0 @@
include(FindPackageHandleStandardArgs)
# Search for the header file
find_path(TMXLITE_INCLUDE_DIR NAMES tmxlite/Config.hpp PATH_SUFFIXES include)
# Search for the library
find_library(TMXLITE_LIBRARIES NAMES tmxlite PATH_SUFFIXES lib)
# Did we find everything we need?
FIND_PACKAGE_HANDLE_STANDARD_ARGS(tmxlite DEFAULT_MSG TMXLITE_LIBRARIES TMXLITE_INCLUDE_DIR)

View File

@@ -1,41 +0,0 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# - Try to find Facebook zstd library
# This will define
# ZSTD_FOUND
# ZSTD_INCLUDE_DIR
# ZSTD_LIBRARY
#
find_path(ZSTD_INCLUDE_DIR NAMES zstd.h)
find_library(ZSTD_LIBRARY_DEBUG NAMES zstdd zstd_staticd)
find_library(ZSTD_LIBRARY_RELEASE NAMES zstd zstd_static)
include(SelectLibraryConfigurations)
SELECT_LIBRARY_CONFIGURATIONS(ZSTD)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(
ZSTD DEFAULT_MSG
ZSTD_LIBRARY ZSTD_INCLUDE_DIR
)
if (ZSTD_FOUND)
message(STATUS "Found Zstd: ${ZSTD_LIBRARY}")
endif()
mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)

View File

@@ -0,0 +1,171 @@
# SPDX-License-Identifier: Zlib
# SPDX-FileCopyrightText: 2024 a dinosaur
#[=======================================================================[.rst:
FindZstd
--------
Find the Facebook Zstd library.
Imported Targets
^^^^^^^^^^^^^^^^
.. variable:: Zstd::Zstd
:prop_tgt:`IMPORTED` target for using Zstd, if Zstd is found.
Result Variables
^^^^^^^^^^^^^^^^
This module defines the following variables:
.. variable:: Zstd_FOUND
True if Zstd was found.
.. variable:: Zstd_INCLUDE_DIRS
Path to the directory containing the Zstd headers (zstd.h, etc.)
.. variable:: Zstd_LIBRARIES
Location of the Zstd library.
.. variable:: Zstd_VERSION
The version of Zstd found.
Legacy Variables
^^^^^^^^^^^^^^^^
The following variables are defined by the official Zstd CMakeLists.txt:
.. variable:: zstd_VERSION_MAJOR
The major version of Zstd.
.. variable:: zstd_VERSION_MINOR
The minor version of Zstd.
.. variable:: zstd_VERSION_PATCH
The patch/release version of Zstd.
The following variables are provided for compatibility with old find modules:
.. variable:: ZSTD_INCLUDE_DIR
Directory containing the Zstd header. (use ``Zstd_INCLUDE_DIRS`` instead)
.. variable:: ZSTD_LIBRARY
The Zstd library. (use ``Zstd_LIBRARIES`` instead)
Hints
^^^^^
.. variable:: Zstd_PREFER_STATIC_LIBS
Set to ``ON`` to prefer static libraries. Defaults to ``OFF``
Cache Variables
^^^^^^^^^^^^^^^
The following cache variables may also be set,
these are transitory and should not be relied upon:
.. variable:: Zstd_INCLUDE_DIR
Directory containing the Zstd header.
.. variable:: Zstd_LIBRARY_DEBUG
The Zstd debug library if found.
.. variable:: Zstd_LIBRARY_RELEASE
The Zstd release library if found.
.. variable:: Zstd_LIBRARY
The Zstd library.
#]=======================================================================]
#TODO: define Zstd::static & Zstd::shared and alias Zstd::Zstd based on preference
find_path(Zstd_INCLUDE_DIR NAMES zstd.h)
mark_as_advanced(Zstd_INCLUDE_DIR)
if (Zstd_PREFER_STATIC_LIBS)
find_library(Zstd_LIBRARY_DEBUG NAMES zstd_staticd zstdd)
find_library(Zstd_LIBRARY_RELEASE NAMES zstd_static zstd)
else()
find_library(Zstd_LIBRARY_DEBUG NAMES zstdd zstd_staticd)
find_library(Zstd_LIBRARY_RELEASE NAMES zstd zstd_static)
endif()
include(SelectLibraryConfigurations)
select_library_configurations(Zstd)
mark_as_advanced(Zstd_LIBRARY Zstd_LIBRARY_DEBUG Zstd_LIBRARY_RELEASE)
if (Zstd_INCLUDE_DIR AND EXISTS "${Zstd_INCLUDE_DIR}/zstd.h")
function (_zstd_read_define _variable _define)
set(_file "${Zstd_INCLUDE_DIR}/zstd.h")
set(_regex "#define[ \t]+${_define}[ \t]+([0-9]+)")
file(STRINGS "${_file}" _line LIMIT_COUNT 1 REGEX "${_regex}")
if (CMAKE_VERSION VERSION_LESS "3.29")
string(REGEX MATCH "${_regex}" _line "${_line}")
endif()
set(${_variable} ${CMAKE_MATCH_1} PARENT_SCOPE)
endfunction()
_zstd_read_define(zstd_VERSION_MAJOR "ZSTD_VERSION_MAJOR")
_zstd_read_define(zstd_VERSION_MINOR "ZSTD_VERSION_MINOR")
_zstd_read_define(zstd_VERSION_PATCH "ZSTD_VERSION_RELEASE")
set(Zstd_VERSION "${zstd_VERSION_MAJOR}.${zstd_VERSION_MINOR}.${zstd_VERSION_PATCH}")
mark_as_advanced(zstd_VERSION_MAJOR zstd_VERSION_MINOR zstd_VERSION_PATCH Zstd_VERSION)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Zstd
REQUIRED_VARS Zstd_LIBRARY Zstd_INCLUDE_DIR
VERSION_VAR Zstd_VERSION)
mark_as_advanced(Zstd_FOUND)
if (Zstd_FOUND)
set(Zstd_INCLUDE_DIRS ${Zstd_INCLUDE_DIR})
set(Zstd_LIBRARIES ${Zstd_LIBRARY})
# Legacy variables
set(ZSTD_INCLUDE_DIR ${Zstd_INCLUDE_DIR})
set(ZSTD_LIBRARY ${Zstd_LIBRARY})
mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY)
if (NOT TARGET Zstd::Zstd)
add_library(Zstd::Zstd UNKNOWN IMPORTED)
set_property(TARGET Zstd::Zstd PROPERTY
INTERFACE_INCLUDE_DIRECTORIES "${Zstd_INCLUDE_DIR}")
endif()
if (NOT Zstd_LIBRARY_DEBUG AND NOT Zstd_LIBRARY_RELEASE)
set_property(TARGET Zstd::Zstd PROPERTY
IMPORTED_LOCATION "${Zstd_LIBRARY}")
endif()
if (Zstd_LIBRARY_DEBUG)
set_property(TARGET Zstd::Zstd APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET Zstd::Zstd PROPERTY
IMPORTED_LOCATION_DEBUG "${Zstd_LIBRARY_DEBUG}")
endif()
if (Zstd_LIBRARY_RELEASE)
set_property(TARGET Zstd::Zstd APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET Zstd::Zstd PROPERTY
IMPORTED_LOCATION_RELEASE "${Zstd_LIBRARY_RELEASE}")
endif()
endif()

View File

@@ -1,5 +1,7 @@
add_library(base64
base64.cpp base64.h)
add_library(base64::base64 ALIAS base64)
set_target_properties(base64 PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON)
target_compile_options(base64 PUBLIC $<$<CXX_COMPILER_ID:MSVC>:/Zc:__cplusplus>)
target_include_directories(base64
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

19
ext/base64/LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright © 2004-2017 by René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -22,6 +22,7 @@ add_library(pugixml STATIC
src/pugixml.hpp
src/pugixml.cpp)
add_library(pugixml::static ALIAS pugixml)
add_library(pugixml::pugixml ALIAS pugixml)
set_target_properties(pugixml PROPERTIES
CXX_STANDARD_REQUIRED ON

View File

@@ -1,54 +0,0 @@
project(tmxlite VERSION 1.3.1)
set(USE_RTTI TRUE CACHE BOOL "Use run time type information?")
# includes the list of source files in the src directory
set(PROJECT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
file(GLOB PROJECT_SRC ${PROJECT_DIR}/*.cpp)
file(GLOB PROJECT_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp)
file(GLOB PROJECT_HEADERS_INL ${CMAKE_CURRENT_SOURCE_DIR}/include/*.inl)
file(GLOB PROJECT_HEADERS_DETAIL ${CMAKE_CURRENT_SOURCE_DIR}/include/detail/*.hpp)
set(PROJECT_SRC ${PROJECT_SRC} ${PROJECT_HEADERS} ${PROJECT_HEADERS_INL} ${PROJECT_HEADERS_DETAIL})
add_library(tmxlite STATIC ${PROJECT_SRC})
set_target_properties(tmxlite PROPERTIES
CXX_STANDARD 14
CXX_STANDARD_REQUIRED ON)
target_compile_definitions(tmxlite PRIVATE $<$<CONFIG:Debug>:_DEBUG_> TMXLITE_STATIC)
target_compile_options(tmxlite PRIVATE -Wall)
if (NOT USE_RTTI AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
target_compile_options(tmxlite PRIVATE -fno-rtti)
endif()
# disable msvc warning
if (MSVC)
target_compile_definitions(tmxlite PRIVATE _CRT_SECURE_NO_WARNINGS)
endif()
target_link_libraries(tmxlite base64::base64)
if (USE_BUNDLED_PUGIXML)
target_link_libraries(tmxlite pugixml::static)
else()
target_include_directories(tmxlite PRIVATE ${PUGIXML_INCLUDE_DIR})
target_link_libraries(tmxlite ZLIB::ZLIB ${PUGIXML_LIBRARY})
endif()
if (USE_ZLIB)
target_compile_definitions(tmxlite PRIVATE USE_ZLIB)
target_link_libraries(tmxlite ZLIB::ZLIB)
else()
target_link_libraries(tmxlite miniz::miniz)
endif()
target_compile_definitions(tmxlite PRIVATE USE_ZSTD)
if (USE_BUNDLED_ZSTD)
target_link_libraries(tmxlite zstd::static)
else()
target_include_directories(tmxlite PRIVATE ${ZSTD_INCLUDE_DIR})
target_link_libraries(tmxlite ZLIB::ZLIB ${ZSTD_LIBRARY})
endif()
target_include_directories(tmxlite PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)

View File

@@ -1,64 +0,0 @@
/*********************************************************************
(c) Matt Marchant 2016 - 2021
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
//check which platform we're on and create export macros as necessary
#if !defined(TMXLITE_STATIC)
#if defined(_WIN32)
//windows compilers need specific (and different) keywords for export
#define TMXLITE_EXPORT_API __declspec(dllexport)
//for vc compilers we also need to turn off this annoying C4251 warning
#ifdef _MSC_VER
#pragma warning(disable: 4251)
#endif //_MSC_VER
#else //linux, FreeBSD, Mac OS X
#if __GNUC__ >= 4
//gcc 4 has special keywords for showing/hiding symbols,
//the same keyword is used for both importing and exporting
#define TMXLITE_EXPORT_API __attribute__ ((__visibility__ ("default")))
#else
//gcc < 4 has no mechanism to explicitly hide symbols, everything's exported
#define TMXLITE_EXPORT_API
#endif //__GNUC__
#endif //_WIN32
#else
//static build doesn't need import/export macros
#define TMXLITE_EXPORT_API
#endif //TMXLITE_STATIC

View File

@@ -1,136 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2021
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/detail/Android.hpp>
#include <tmxlite/detail/Log.hpp>
#include <tmxlite/Types.hpp>
#include <string>
#include <sstream>
#include <vector>
#include <functional>
#include <algorithm>
namespace tmx
{
//using inline here just to supress unused warnings on gcc
bool decompress(const char* source, std::vector<unsigned char>& dest, std::size_t inSize, std::size_t expectedSize);
static inline Colour colourFromString(std::string str)
{
//removes preceding #
auto result = str.find_last_of('#');
if (result != std::string::npos)
{
str = str.substr(result + 1);
}
if (str.size() == 6 || str.size() == 8)
{
unsigned int value, r, g, b;
unsigned int a = 255;
std::stringstream input(str);
input >> std::hex >> value;
r = (value >> 16) & 0xff;
g = (value >> 8) & 0xff;
b = value & 0xff;
if (str.size() == 8)
{
a = (value >> 24) & 0xff;
}
return{ std::uint8_t(r), std::uint8_t(g), std::uint8_t(b), std::uint8_t(a) };
}
Logger::log(str + ": not a valid colour string", Logger::Type::Error);
return{};
}
static inline std::string resolveFilePath(std::string path, const std::string& workingDir)
{
static const std::string match("../");
std::size_t result = path.find(match);
std::size_t count = 0;
while (result != std::string::npos)
{
count++;
path = path.substr(result + match.size());
result = path.find(match);
}
if (workingDir.empty()) return path;
std::string outPath = workingDir;
for (auto i = 0u; i < count; ++i)
{
result = outPath.find_last_of('/');
if (result != std::string::npos)
{
outPath = outPath.substr(0, result);
}
}
// this does only work on windows
#ifndef __ANDROID__
return outPath + '/' + path;
#endif
// todo: make resolveFilePath work with subfolders on
// android - currently only the root folder is working
#ifdef __ANDROID__
return path;
#endif
}
static inline std::string getFilePath(const std::string& path)
{
//TODO this doesn't actually check that there is a file at the
//end of the path, or that it's even a valid path...
static auto searchFunc = [](const char separator, const std::string& path)->std::string
{
std::size_t i = path.rfind(separator, path.length());
if (i != std::string::npos)
{
return(path.substr(0, i + 1));
}
return "";
};
#ifdef _WIN32 //try windows formatted paths first
std::string retVal = searchFunc('\\', path);
if (!retVal.empty()) return retVal;
#endif
return searchFunc('/', path);
}
} //namespacec tmx

View File

@@ -1,107 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2022
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Config.hpp>
#include <tmxlite/Layer.hpp>
#include <tmxlite/Types.hpp>
namespace tmx
{
/*!
\brief Image layers contain a single image which make up that
layer. The parser contains the fully resolved path to the image
relative to the working directory.
*/
class TMXLITE_EXPORT_API ImageLayer final : public Layer
{
public:
explicit ImageLayer(const std::string&);
Type getType() const override { return Layer::Type::Image; }
void parse(const pugi::xml_node&, Map*) override;
/*!
\brief Returns the path, relative to the working directory,
of the image used by the image layer.
*/
const std::string& getImagePath() const { return m_filePath; }
/*!
\brief Returns the colour used by the image to represent transparent
pixels. By default this is (0, 0, 0, 0)
*/
const Colour& getTransparencyColour() const { return m_transparencyColour; }
/*!
\brief Returns true if the image used by this layer specifically states a
colour to use as transparency
*/
bool hasTransparency() const { return m_hasTransparency; }
/*!
\brief Returns the size of the image of the image layer in pixels.
*/
const Vector2u& getImageSize() const { return m_imageSize; }
/*!
\brief Returns true if the image drawn by this layer is repeated along
the X axis.
*/
bool hasRepeatX() const { return m_hasRepeatX; }
/*!
\brief Returns true if the image drawn by this layer is repeated along
the Y axis.
*/
bool hasRepeatY() const { return m_hasRepeatY; }
private:
std::string m_workingDir;
std::string m_filePath;
Colour m_transparencyColour;
bool m_hasTransparency;
Vector2u m_imageSize;
bool m_hasRepeatX;
bool m_hasRepeatY;
};
template <>
inline ImageLayer& Layer::getLayerAs<ImageLayer>()
{
assert(getType() == Type::Image);
return *static_cast<ImageLayer*>(this);
}
template <>
inline const ImageLayer& Layer::getLayerAs<ImageLayer>() const
{
assert(getType() == Type::Image);
return *static_cast<const ImageLayer*>(this);
}
}

View File

@@ -1,175 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Config.hpp>
#include <tmxlite/Property.hpp>
#include <tmxlite/Types.hpp>
#include <string>
#include <memory>
#include <vector>
namespace pugi
{
class xml_node;
}
namespace tmx
{
class Map;
class TileLayer;
class ObjectGroup;
class ImageLayer;
class LayerGroup;
/*!
\brief Represents a layer of a tmx format tile map.
This is an abstract base class from which all layer
types are derived.
*/
class TMXLITE_EXPORT_API Layer
{
public:
using Ptr = std::unique_ptr<Layer>;
Layer() : m_opacity(1.f), m_visible(true) {};
virtual ~Layer() = default;
/*!
\brief Layer type as returned by getType()
Tile: this layer is a TileLayer type
Object: This layer is an ObjectGroup type
Image: This layer is an ImageLayer type
Group: This layer is a LayerGroup type
*/
enum class Type
{
Tile,
Object,
Image,
Group
};
/*!
\brief Returns a Type value representing the concrete type.
Use this when deciding which conrete layer type to use when
calling the templated function getLayerAs<T>()
*/
virtual Type getType() const = 0;
/*!
\brief Returns the class of the Layer, as defined in the editor Tiled 1.9+
*/
const std::string& getClass() const { return m_class; }
/*!
\brief Use this to get a reference to the concrete layer type
which this layer points to.
Use getType() to return the type value of this layer and determine
if the concrete type is TileLayer, ObjectGroup, ImageLayer, or LayerGroup
*/
template <typename T>
T& getLayerAs();
template <typename T>
const T& getLayerAs() const;
/*!
\brief Attempts to parse the specific node layer type
*/
virtual void parse(const pugi::xml_node&, Map* = nullptr) = 0;
/*!
\brief Returns the name of the layer
*/
const std::string& getName() const { return m_name; }
/*!
\brief Returns the opacity value for the layer
*/
float getOpacity() const { return m_opacity; }
/*!
\brief Returns whether this layer is visible or not
*/
bool getVisible() const { return m_visible; }
/*!
\brief Returns the offset from the top left corner
of the layer, in pixels
*/
const Vector2i& getOffset() const { return m_offset; }
/*!
\brief Returns the parallax factor
*/
const Vector2f& getParallaxFactor() const { return m_parallaxFactor; }
/*!
\brief Returns the tint colour of the layer.
Defaults to 0xFFFFFFFF - pure white
*/
Colour getTintColour() const { return m_tintColour; }
/*!
\brief Returns the size of the layer, in pixels.
This will be the same as the map size for fixed size maps.
*/
const Vector2u& getSize() const { return m_size; }
/*!
\brief Returns the list of properties of this layer
*/
const std::vector<Property>& getProperties() const { return m_properties; }
protected:
void setName(const std::string& name) { m_name = name; }
void setClass(const std::string& cls) { m_class = cls; }
void setOpacity(float opacity) { m_opacity = opacity; }
void setVisible(bool visible) { m_visible = visible; }
void setOffset(std::int32_t x, std::int32_t y) { m_offset = Vector2i(x, y); }
void setParallaxFactor(float x, float y) { m_parallaxFactor.x = x; m_parallaxFactor.y = y; }
void setTintColour(Colour c) { m_tintColour = c; }
void setSize(std::uint32_t width, std::uint32_t height) { m_size = Vector2u(width, height); }
void addProperty(const pugi::xml_node& node) { m_properties.emplace_back(); m_properties.back().parse(node); }
private:
std::string m_name;
std::string m_class;
float m_opacity;
bool m_visible;
Vector2i m_offset;
Vector2f m_parallaxFactor;
Colour m_tintColour = { 255,255,255,255 };
Vector2u m_size;
std::vector<Property> m_properties;
};
}

View File

@@ -1,86 +0,0 @@
/*********************************************************************
Grant Gangi 2019 - 2022
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Config.hpp>
#include <tmxlite/Layer.hpp>
#include <tmxlite/Types.hpp>
#include <vector>
namespace tmx
{
/*!
\brief Layer groups are used to organize the layers of
the map in a hierarchy. They can contain all other layer
types including more layer groups to further nest layers.
*/
class TMXLITE_EXPORT_API LayerGroup final : public Layer
{
public:
LayerGroup(const std::string& workDir, const Vector2u& tileCount);
~LayerGroup() = default;
LayerGroup(const LayerGroup&) = delete;
const LayerGroup& operator = (const LayerGroup&) = delete;
LayerGroup(LayerGroup&&) = default;
LayerGroup& operator = (LayerGroup&&) = default;
Type getType() const override { return Layer::Type::Group; }
void parse(const pugi::xml_node&, Map*) override;
/*!
\brief Returns a reference to the vector containing the layer data.
Layers are pointer-to-baseclass, the concrete type of which can be
found via Layer::getType()
\see Layer
*/
const std::vector<Layer::Ptr>& getLayers() const { return m_layers; }
private:
std::vector<Layer::Ptr> m_layers;
std::string m_workingDir;
Vector2u m_tileCount;
};
template <>
inline LayerGroup& Layer::getLayerAs<LayerGroup>()
{
assert(getType() == Type::Group);
return *static_cast<LayerGroup*>(this);
}
template <>
inline const LayerGroup& Layer::getLayerAs<LayerGroup>() const
{
assert(getType() == Type::Group);
return *static_cast<const LayerGroup*>(this);
}
}

View File

@@ -1,282 +0,0 @@
/*********************************************************************
Matt Marchant 2016 -2021
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Tileset.hpp>
#include <tmxlite/Layer.hpp>
#include <tmxlite/Property.hpp>
#include <tmxlite/Types.hpp>
#include <tmxlite/Object.hpp>
#include <string>
#include <vector>
#include <map>
#include <unordered_map>
namespace tmx
{
/*!
\brief Holds the xml version of the loaded map
*/
struct TMXLITE_EXPORT_API Version
{
//major/minor are apparently reserved by gcc
std::uint16_t upper;
std::uint16_t lower;
Version(std::uint16_t maj = 0, std::uint16_t min = 0)
: upper(maj), lower(min) {}
};
enum class Orientation
{
Orthogonal,
Isometric,
Staggered,
Hexagonal,
None
};
enum class RenderOrder
{
RightDown,
RightUp,
LeftDown,
LeftUp,
None
};
enum class StaggerAxis
{
X, Y, None
};
enum class StaggerIndex
{
Even, Odd, None
};
/*!
\brief Parser for TMX format tile maps.
This class can be used to parse the XML format tile maps created
with the Tiled map editor, providing an interface to create drawable and
physics objects. Typical usage would be to create an instance of this
class before calling load() providing a path to the *.tmx file to be
loaded. Then layers or objects can be requested from the Map class
to be interpreted as needed.
\see https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map
*/
class TMXLITE_EXPORT_API Map final
{
public:
Map();
~Map() = default;
Map(const Map&) = delete;
Map& operator = (const Map&) = delete;
Map(Map&&) = default;
Map& operator = (Map&&) = default;
/*!
\brief Attempts to parse the tilemap at the given location.
\param std::string Path to map file to try to parse
\returns true if map was parsed successfully else returns false.
In debug mode this will attempt to log any errors to the console.
*/
bool load(const std::string&);
/*!
\brief Loads a map from a document stored in a string
\param data A std::string containing the map data to load
\param workingDir A std::string containing the working directory
in which to find assets such as tile sets or images
\returns true if successful, else false
*/
bool loadFromString(const std::string& data, const std::string& workingDir);
/*!
\brief Returns the version of the tile map last parsed.
If no tile map has yet been parsed the version will read 0, 0
*/
const Version& getVersion() const { return m_version; }
/*!
\brief Returns the orientation of the map if one is loaded,
else returns None
*/
Orientation getOrientation() const { return m_orientation; }
/*!
\brief Returns the RenderOrder of the map if one is loaded,
else returns None
*/
RenderOrder getRenderOrder() const { return m_renderOrder; }
/*!
\brief Returns the tile count of the map in the X and Y directions
*/
const Vector2u& getTileCount() const { return m_tileCount; }
/*!
\brief Returns the size of the tile grid in this map.
Actual tile sizes may vary and will be extended / shrunk about
the bottom left corner of the tile.
*/
const Vector2u& getTileSize() const { return m_tileSize; }
/*!
\brief Returns the bounds of the map
*/
FloatRect getBounds() const { return FloatRect(0.f, 0.f, static_cast<float>(m_tileCount.x * m_tileSize.x), static_cast<float>(m_tileCount.y * m_tileSize.y)); }
/*!
\brief Returns the length of an edge of a tile if a Hexagonal
map is loaded.
The length returned is in pixels of the straight edge running
along the axis returned by getStaggerAxis(). If no map is loaded
or the loaded map is not of Hexagonal orientation this function
returns 0.f
*/
float getHexSideLength() const { return m_hexSideLength; }
/*!
\brief Stagger axis of the map.
If either a Staggered or Hexagonal tile map is loaded this returns
which axis the map is staggered along, else returns None.
*/
StaggerAxis getStaggerAxis() const { return m_staggerAxis; }
/*!
\brief Stagger Index of the loaded map.
If a Staggered or Hexagonal map is loaded this returns whether
the even or odd rows of tiles are staggered, otherwise it returns None.
*/
StaggerIndex getStaggerIndex() const { return m_staggerIndex; }
/*!
\brief Returns the background colour of the map.
*/
const Colour& getBackgroundColour() const { return m_backgroundColour; }
/*!
\brief Returns a reference to the vector of tile sets used by the map
*/
const std::vector<Tileset>& getTilesets() const { return m_tilesets; }
/*!
\brief Returns a reference to the vector containing the layer data.
Layers are pointer-to-baseclass, the concrete type of which can be
found via Layer::getType()
\see Layer
*/
const std::vector<Layer::Ptr>& getLayers() const { return m_layers; }
/*!
\brief Returns the class of the Map, as defined in the editor Tiled 1.9+
*/
const std::string& getClass() const { return m_class; }
/*!
\brief Returns a vector of Property objects loaded by the map
*/
const std::vector<Property>& getProperties() const { return m_properties; }
/*!
\brief Returns a Hashmap of all animated tiles accessible by TileID
*/
const std::map<std::uint32_t, Tileset::Tile>& getAnimatedTiles() const { return m_animTiles; }
/*!
\brief Returns the current working directory of the map. Images and
other resources are loaded relative to this.
*/
const std::string& getWorkingDirectory() const { return m_workingDirectory; }
/*!
\brief Returns an unordered_map of template objects indexed by file name
*/
std::unordered_map<std::string, Object>& getTemplateObjects() { return m_templateObjects; }
const std::unordered_map<std::string, Object>& getTemplateObjects() const { return m_templateObjects; }
/*!
\brief Returns an unordered_map of tilesets used by templated objects.
If Object::getTilesetName() is not empty it can be used to retreive a tileset
from this map. Otherwise the object's tileset can be found from in the map's
global tilesets returned by getTilesets().
*/
std::unordered_map<std::string, Tileset>& getTemplateTilesets() { return m_templateTilesets; }
const std::unordered_map<std::string, Tileset>& getTemplateTilesets() const { return m_templateTilesets; }
/*!
\brief Returns true if this is in infinite tile map.
Infinite maps store their tile data in for tile layers in chunks. If
this is an infinite map use TileLayer::getChunks() to get tile IDs
rather than TileLayer::getTiles().
\see TileLayer
*/
bool isInfinite() const { return m_infinite; }
/*
\brief Returns the origin of each layer's parallax offset value
*/
Vector2f getParallaxOrigin() const { return m_parallaxOrigin; }
private:
Version m_version;
std::string m_class;
Orientation m_orientation;
RenderOrder m_renderOrder;
bool m_infinite;
Vector2u m_tileCount;
Vector2u m_tileSize;
float m_hexSideLength;
StaggerAxis m_staggerAxis;
StaggerIndex m_staggerIndex;
Vector2f m_parallaxOrigin;
Colour m_backgroundColour;
std::string m_workingDirectory;
std::vector<Tileset> m_tilesets;
std::vector<Layer::Ptr> m_layers;
std::vector<Property> m_properties;
std::map<std::uint32_t, Tileset::Tile> m_animTiles;
std::unordered_map<std::string, Object> m_templateObjects;
std::unordered_map<std::string, Tileset> m_templateTilesets;
bool parseMapNode(const pugi::xml_node&);
//always returns false so we can return this
//on load failure
bool reset();
};
}

View File

@@ -1,221 +0,0 @@
/*********************************************************************
(c) Matt Marchant 2016 - 2021
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Config.hpp>
#include <tmxlite/Property.hpp>
#include <tmxlite/Types.hpp>
#include <string>
#include <vector>
namespace pugi
{
class xml_node;
}
namespace tmx
{
class Map;
/*!
\brief Contains the text information stored in a Text object.
*/
struct TMXLITE_EXPORT_API Text final
{
std::string fontFamily;
std::uint32_t pixelSize = 16; //!< pixels, not points
bool wrap = false;
Colour colour;
bool bold = false;
bool italic = false;
bool underline = false;
bool strikethough = false;
bool kerning = true;
enum class HAlign
{
Left, Centre, Right
}hAlign = HAlign::Left;
enum class VAlign
{
Top, Centre, Bottom
}vAlign = VAlign::Top;
std::string content; //!< actual string content
};
/*!
\brief Objects are stored in ObjectGroup layers.
Objects may be rectangular, elliptical, polygonal or
a polyline. Rectangular and elliptical Objects have their
size determined via the AABB, whereas polygon and polyline
shapes are defined by a list of points. Objects are
rectangular by default. Since version 1.0 Objects also
support Text nodes.
*/
class TMXLITE_EXPORT_API Object final
{
public:
enum class Shape
{
Rectangle,
Ellipse,
Point,
Polygon,
Polyline,
Text
};
Object();
/*!
\brief Attempts to parse the given xml node and
read the Object properties if it is valid.
*/
void parse(const pugi::xml_node&, Map*);
/*!
\brief Returns the unique ID of the Object
*/
std::uint32_t getUID() const { return m_UID; }
/*!
\brief Returns the name of the Object
*/
const std::string& getName() const { return m_name; }
/*!
\brief Returns the type (equal to class) of the Object, as defined in the editor Tiled < 1.9
*/
const std::string& getType() const { return m_class; }
/*!
\brief Returns the class (equal to type) of the Object, as defined in the editor Tiled 1.9+
*/
const std::string& getClass() const { return m_class; }
/*!
\brief Returns the position of the Object in pixels
*/
const Vector2f& getPosition() const { return m_position; }
/*!
\brief Returns the global Axis Aligned Bounding Box.
The AABB is positioned via the left and top properties, and
define the Object's width and height. This can be used to derive
the shape of the Object if it is rectangular or elliptical.
*/
const FloatRect& getAABB() const { return m_AABB; }
/*!
\brief Returns the rotation of the Object in degrees clockwise
*/
float getRotation() const { return m_rotation; }
/*!
\brief Returns the global tile ID associated with the Object
if there is one. This is used to draw the Object (and therefore
the Object must be rectangular)
*/
std::uint32_t getTileID() const { return m_tileID; }
/*!
\brief Returns the flip flags if the objects uses a TileID to
draw it.
Returns 0 otherwise.
*/
std::uint8_t getFlipFlags() const { return m_flipFlags; }
/*!
\brief Returns whether or not the Object is visible
*/
bool visible() const { return m_visible; }
/*!
\brief Returns the Shape type of the Object
*/
Shape getShape() const { return m_shape; }
/*!
\brief Returns a reference to the vector of points which
make up the Object. If the Object is rectangular or elliptical
then the vector will be empty. Point coordinates are in pixels,
relative to the object position.
*/
const std::vector<Vector2f>& getPoints() const { return m_points; }
/*!
\brief Returns a reference to the vector of properties belonging to
the Object.
*/
const std::vector<Property>& getProperties() const { return m_properties; }
/*!
\brief Returns a Text struct containing information about any text
this object may have, such as font data and formatting.
If an object does not contain any text information this struct will
be populated with default values. Use getShape() to determine
if this object is in fact a text object.
*/
const Text& getText() const { return m_textData; }
Text& getText() { return m_textData; }
/*!
\brief Returns the tileset name used by this object if it is derived
from a template, else returns an empty string.
If the string is not empty use it to index the unordered_map returned
by Map::getTemplateTilesets()
*/
const std::string& getTilesetName() const { return m_tilesetName; }
private:
std::uint32_t m_UID;
std::string m_name;
std::string m_class;
Vector2f m_position;
FloatRect m_AABB;
float m_rotation;
std::uint32_t m_tileID;
std::uint8_t m_flipFlags;
bool m_visible;
Shape m_shape;
std::vector<Vector2f> m_points;
std::vector<Property> m_properties;
Text m_textData;
std::string m_tilesetName;
void parsePoints(const pugi::xml_node&);
void parseText(const pugi::xml_node&);
void parseTemplate(const std::string&, Map*);
};
}

View File

@@ -1,99 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2022
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Config.hpp>
#include <tmxlite/Layer.hpp>
#include <tmxlite/Object.hpp>
#include <vector>
namespace tmx
{
/*!
\brief ObjectGroup layers contain a series of Objects
which may be made up of shapes or images.
*/
class TMXLITE_EXPORT_API ObjectGroup final : public Layer
{
public:
enum class DrawOrder
{
Index, //< objects should be drawn in the order in which they appear
TopDown //< objects should be drawn sorted by their Y position
};
ObjectGroup();
Type getType() const override { return Layer::Type::Object; }
void parse(const pugi::xml_node&, Map*) override;
/*!
\brief Returns the colour associated with this layer
*/
const Colour& getColour() const { return m_colour; }
/*!
\brief Returns the DrawOrder for the objects in this group.
Defaults to TopDown, where Objects are drawn sorted by Y position
*/
DrawOrder getDrawOrder() const { return m_drawOrder; }
/*!
\brief Returns a reference to the vector of properties for
the ObjectGroup
*/
const std::vector<Property>& getProperties() const { return m_properties; }
/*!
\brief Returns a reference to the vector of Objects which belong to the group
*/
const std::vector<Object>& getObjects() const { return m_objects; }
private:
Colour m_colour;
DrawOrder m_drawOrder;
std::vector<Property> m_properties;
std::vector<Object> m_objects;
};
template <>
inline ObjectGroup& Layer::getLayerAs<ObjectGroup>()
{
assert(getType() == Type::Object);
return *static_cast<ObjectGroup*>(this);
}
template <>
inline const ObjectGroup& Layer::getLayerAs<ObjectGroup>() const
{
assert(getType() == Type::Object);
return *static_cast<const ObjectGroup*>(this);
}
}

View File

@@ -1,86 +0,0 @@
/*********************************************************************
Raphaël Frantz 2021
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Property.hpp>
#include <string>
#include <vector>
namespace tmx
{
/*!
\brief Parser for Tiled object types export format.
Link to the specification: https://doc.mapeditor.org/fr/latest/manual/custom-properties/#predefining-properties.
*/
class TMXLITE_EXPORT_API ObjectTypes final
{
public:
/*!
\brief Types that stores all predefined properties for all objects of this type.
To take less spaces, they are not exported by default into maps.
*/
struct Type
{
std::string name;
Colour colour;
std::vector<Property> properties;
};
/*!
\brief Attempts to parse the object types at the given location.
\param std::string Path to object types file to try to parse
\returns true if object types was parsed successfully else returns false.
In debug mode this will attempt to log any errors to the console.
*/
bool load(const std::string&);
/*!
\brief Loads an object types from a document stored in a string
\param data A std::string containing the object types to load
\param workingDir A std::string containing the working directory
in which to find files.
\returns true if successful, else false
*/
bool loadFromString(const std::string& data, const std::string& workingDir);
/*!
\brief Returns all predefined types and their default values.
*/
const std::vector<Type>& getTypes() const { return m_types; }
private:
std::string m_workingDirectory;
std::vector<Type> m_types;
bool parseObjectTypesNode(const pugi::xml_node&);
//always returns false so we can return this
//on load failure
bool reset();
};
}

View File

@@ -1,144 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2021
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Config.hpp>
#include <tmxlite/Types.hpp>
#include <string>
#include <cassert>
namespace pugi
{
class xml_node;
}
namespace tmx
{
/*!
\brief Represents a custom property.
Tiles, objects and layers of a tmx map may have custom
properties assigned to them. This class represents a
single property and provides access to its value, the
type of which can be determined with getType()
*/
class TMXLITE_EXPORT_API Property final
{
public:
enum class Type
{
Boolean,
Float,
Int,
String,
Colour,
File,
Object,
Undef
};
Property();
static Property fromBoolean(bool value);
static Property fromFloat(float value);
static Property fromInt(int value);
static Property fromString(const std::string& value);
static Property fromColour(const Colour& value);
static Property fromFile(const std::string& value);
static Property fromObject(int value);
/*!
\brief Attempts to parse the given node as a property
\param isObjectTypes Set to true if the parsing is done from an object types files.
*/
void parse(const pugi::xml_node&, bool isObjectTypes = false);
/*!
\brief Returns the type of data stored in the property.
This should generally be called first before trying to
read the proprty value, as reading the incorrect type
will lead to undefined behaviour.
*/
Type getType() const { return m_type; }
/*!
\brief Returns the name of this property
*/
const std::string& getName() const { return m_name; }
/*!
\brief Returns the property's value as a boolean
*/
bool getBoolValue() const { assert(m_type == Type::Boolean); return m_boolValue; }
/*!
\brief Returns the property's value as a float
*/
float getFloatValue() const { assert(m_type == Type::Float); return m_floatValue; }
/*!
\brief Returns the property's value as an integer
*/
int getIntValue() const { assert(m_type == Type::Int || m_type == Type::Object); return m_intValue; }
/*!
\brief Returns the property's value as a string
*/
const std::string& getStringValue() const { assert(m_type == Type::String); return m_stringValue; }
/*!
\brief Returns the property's value as a Colour struct
*/
const Colour& getColourValue() const { assert(m_type == Type::Colour); return m_colourValue; }
/*!
\brief Returns the file path property as a string, relative to the map file
*/
const std::string& getFileValue() const { assert(m_type == Type::File); return m_stringValue; }
/*!
\brief Returns the property's value as an integer object handle
*/
int getObjectValue() const { assert(m_type == Type::Object); return m_intValue; }
private:
union
{
bool m_boolValue;
float m_floatValue;
int m_intValue;
};
std::string m_stringValue;
std::string m_name;
Colour m_colourValue;
Type m_type;
};
}

View File

@@ -1,116 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2022
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Layer.hpp>
#include <tmxlite/Types.hpp>
namespace tmx
{
/*!
\brief A layer made up from a series of tile sets
*/
class TMXLITE_EXPORT_API TileLayer final : public Layer
{
public:
/*!
\brief Tile information for a layer
*/
struct Tile final
{
std::uint32_t ID = 0; //!< Global ID of the tile
std::uint8_t flipFlags = 0; //!< Flags marking if the tile should be flipped when drawn
};
/*!
\brief Represents a chunk of tile data, if this is an infinite map
*/
struct Chunk final
{
Vector2i position; //<! coordinate in tiles, not pixels
Vector2i size; //!< size in tiles, not pixels
std::vector<Tile> tiles;
};
/*!
\brief Flags used to tell if a tile is flipped when drawn
*/
enum FlipFlag
{
Horizontal = 0x8,
Vertical = 0x4,
Diagonal = 0x2
};
explicit TileLayer(std::size_t);
Type getType() const override { return Layer::Type::Tile; }
void parse(const pugi::xml_node&, Map*) override;
/*!
\brief Returns the list of tiles used to make up the layer
If this is empty then the map is most likely infinite, in
which case the tile data is stored in chunks.
\see getChunks()
*/
const std::vector<Tile>& getTiles() const { return m_tiles; }
/*!
\brief Returns a vector of chunks which make up this layer
if the map is set to infinite. This will be empty if the map
is not infinite.
\see getTiles()
*/
const std::vector<Chunk>& getChunks() const { return m_chunks; }
private:
std::vector<Tile> m_tiles;
std::vector<Chunk> m_chunks;
std::size_t m_tileCount;
void parseBase64(const pugi::xml_node&);
void parseCSV(const pugi::xml_node&);
void parseUnencoded(const pugi::xml_node&);
void createTiles(const std::vector<std::uint32_t>&, std::vector<Tile>& destination);
};
template <>
inline TileLayer& Layer::getLayerAs<TileLayer>()
{
assert(getType() == Type::Tile);
return *static_cast<TileLayer*>(this);
}
template <>
inline const TileLayer& Layer::getLayerAs<TileLayer>() const
{
assert(getType() == Type::Tile);
return *static_cast<const TileLayer*>(this);
}
}

View File

@@ -1,296 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Config.hpp>
#include <tmxlite/Property.hpp>
#include <tmxlite/ObjectGroup.hpp>
#include <string>
#include <vector>
#include <array>
namespace pugi
{
class xml_node;
}
namespace tmx
{
class Map;
/*!
\brief Represents a Tileset node as loaded
from a *.tmx format tile map via the tmx::Map
class.
*/
class TMXLITE_EXPORT_API Tileset final
{
public:
explicit Tileset(const std::string& workingDir);
/*!
\brief Any tiles within a tile set which have special
data associated with them such as animation or terrain
information will have one of these stored in the tile set.
*/
struct Tile final
{
std::uint32_t ID = 0;
std::array<std::int32_t, 4u> terrainIndices{};
std::uint32_t probability = 100;
/*!
\brief a group of frames which make up an animation
*/
struct Animation final
{
/*!
\brief A frame within an animation
*/
struct Frame final
{
std::uint32_t tileID = 0;
std::uint32_t duration = 0;
bool operator == (const Frame& other) const
{
return (this == &other) ||
(tileID == other.tileID && duration == other.duration);
}
bool operator != (const Frame& other) const
{
return !(*this == other);
}
};
std::vector<Frame> frames;
}animation;
std::vector<Property> properties;
ObjectGroup objectGroup;
std::string imagePath;
Vector2u imageSize;
/*!
\brief The position of the tile within the image.
*/
Vector2u imagePosition;
std::string className;
};
/*!
\brief Terrain information with which one
or more tiles may be associated.
*/
struct Terrain final
{
std::string name;
std::uint32_t tileID = -1;
std::vector<Property> properties;
};
/*!
\brief Declares the alignment of tile Objects
*/
enum class ObjectAlignment
{
Unspecified,
TopLeft,
Top,
TopRight,
Left,
Center,
Right,
BottomLeft,
Bottom,
BottomRight
};
/*!
\brief Attempts to parse the given xml node.
If node parsing fails an error is printed in the console
and the Tileset remains in an uninitialised state.
*/
void parse(pugi::xml_node, Map*);
/*!
\brief Returns the first GID of this tile set.
This the ID of the first tile in the tile set, so that
each tile set guarantees a unique set of IDs
*/
std::uint32_t getFirstGID() const { return m_firstGID; }
/*!
\brief Returns the last GID of this tile set.
This is the ID of the last tile in the tile set.
*/
std::uint32_t getLastGID() const;
/*!
\brief Returns the name of this tile set.
*/
const std::string& getName() const { return m_name; }
/*!
\brief Returns the class of the Tileset, as defined in the editor Tiled 1.9+
*/
const std::string& getClass() const { return m_class; }
/*!
\brief Returns the width and height of a tile in the
tile set, in pixels.
*/
const Vector2u& getTileSize() const { return m_tileSize; }
/*!
\brief Returns the spacing, in pixels, between each tile in the set
*/
std::uint32_t getSpacing() const { return m_spacing; }
/*!
\brief Returns the margin, in pixels, around each tile in the set
*/
std::uint32_t getMargin() const { return m_margin; }
/*!
\brief Returns the number of tiles in the tile set
*/
std::uint32_t getTileCount() const { return m_tileCount; }
/*!
\brief Returns the number of columns which make up the tile set.
This is used when rendering collection of images sets
*/
std::uint32_t getColumnCount() const { return m_columnCount; }
/*!
\brief Returns the alignment of tile objects.
The default value is ObjectAlignment::Unspecified for compatibility.
When the alignment is Unspecified tile objects use BottomLeft in
orthogonal mode and Bottom in isometric mode.
\see ObjectAlignment
*/
ObjectAlignment getObjectAlignment() const { return m_objectAlignment; }
/*!
\brief Returns the tile offset in pixels.
Tile will draw tiles offset from the top left using this value.
*/
const Vector2u& getTileOffset() const { return m_tileOffset; }
/*!
\brief Returns a reference to the list of Property objects for this
tile set
*/
const std::vector<Property>& getProperties() const { return m_properties; }
/*!
\brief Returns the file path to the tile set image, relative to the
working directory. Use this to load the texture required by whichever
method you choose to render the map.
*/
const std::string& getImagePath() const { return m_imagePath; }
/*!
\brief Returns the size of the tile set image in pixels.
*/
const Vector2u& getImageSize() const { return m_imageSize; }
/*!
\brief Returns the colour used by the tile map image to represent transparency.
By default this is a transparent colour (0, 0, 0, 0)
*/
const Colour& getTransparencyColour() const { return m_transparencyColour; }
/*!
\brief Returns true if the image used by this tileset specifically requests
a colour to use as transparency.
*/
bool hasTransparency() const { return m_hasTransparency; }
/*!
\brief Returns a vector of Terrain types associated with one
or more tiles within this tile set
*/
const std::vector<Terrain>& getTerrainTypes() const { return m_terrainTypes; }
/*!
\brief Returns a reference to the vector of tile data used by
tiles which make up this tile set.
*/
const std::vector<Tile>& getTiles() const { return m_tiles; }
/*!
\brief Checks if a tiled ID is in the range of the first ID and the last ID
\param id Tile ID
\return
*/
bool hasTile(std::uint32_t id) const { return id >= m_firstGID && id <= getLastGID(); };
/*!
\brief queries tiles and returns a tile with the given ID. Checks if the TileID is part of the Tileset with `hasTile(id)`
\param id Tile ID. The Tile ID will be corrected internally.
\return In case of a success it returns the correct tile. In terms of failure it will return a nullptr.
*/
const Tile* getTile(std::uint32_t id) const;
private:
std::string m_workingDir;
std::uint32_t m_firstGID;
std::string m_source;
std::string m_name;
std::string m_class;
Vector2u m_tileSize;
std::uint32_t m_spacing;
std::uint32_t m_margin;
std::uint32_t m_tileCount;
std::uint32_t m_columnCount;
ObjectAlignment m_objectAlignment;
Vector2u m_tileOffset;
std::vector<Property> m_properties;
std::string m_imagePath;
Vector2u m_imageSize;
Colour m_transparencyColour;
bool m_hasTransparency;
std::vector<Terrain> m_terrainTypes;
std::vector<std::uint32_t> m_tileIndex;
std::vector<Tile> m_tiles;
void reset();
void parseOffsetNode(const pugi::xml_node&);
void parsePropertyNode(const pugi::xml_node&);
void parseTerrainNode(const pugi::xml_node&);
Tile& newTile(std::uint32_t ID);
void parseTileNode(const pugi::xml_node&, Map*);
void createMissingTile(std::uint32_t ID);
};
}

View File

@@ -1,150 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#pragma once
#include <tmxlite/Config.hpp>
#include <cstdint>
#include <ostream>
namespace tmx
{
/*!
\brief Two dimensional vector used to store points and positions
*/
template <class T>
struct Vector2 final
{
Vector2() : x(0), y(0) {}
Vector2(T x, T y) :x(x), y(y) {}
T x, y;
};
using Vector2f = Vector2<float>;
using Vector2i = Vector2<int>;
using Vector2u = Vector2<unsigned>;
template <typename T>
Vector2<T> operator + (const Vector2<T>& l, const Vector2<T>& r);
template <typename T>
Vector2<T>& operator += (Vector2<T>& l, const Vector2<T>& r);
template <typename T>
Vector2<T> operator - (const Vector2<T>& l, const Vector2<T>& r);
template <typename T>
Vector2<T>& operator -= (Vector2<T>& l, const Vector2<T>& r);
template <typename T>
Vector2<T> operator * (const Vector2<T>& l, const Vector2<T>& r);
template <typename T>
Vector2<T>& operator *= (Vector2<T>& l, const Vector2<T>& r);
template <typename T>
Vector2<T> operator * (const Vector2<T>& l, T r);
template <typename T>
Vector2<T>& operator *= (Vector2<T>& l, T r);
template <typename T>
Vector2<T> operator / (const Vector2<T>& l, const Vector2<T>& r);
template <typename T>
Vector2<T>& operator /= (Vector2<T>& l, const Vector2<T>& r);
template <typename T>
Vector2<T> operator / (const Vector2<T>& l, T r);
template <typename T>
Vector2<T>& operator /= (Vector2<T>& l, T r);
#include "Types.inl"
/*!
\brief Describes a rectangular area, such as an AABB (axis aligned bounding box)
*/
template <class T>
struct Rectangle final
{
Rectangle() : left(0), top(0), width(0), height(0) {}
Rectangle(T l, T t, T w, T h) : left(l), top(t), width(w), height(h) {}
Rectangle(Vector2<T> position, Vector2<T> size) : left(position.x), top(position.y), width(size.x), height(size.y) {}
T left, top, width, height;
};
using FloatRect = Rectangle<float>;
using IntRect = Rectangle<int>;
/*!
\brief Contains the red, green, blue and alpha values of a colour
in the range 0 - 255.
*/
struct TMXLITE_EXPORT_API Colour final
{
Colour(std::uint8_t red = 0, std::uint8_t green = 0, std::uint8_t blue = 0, std::uint8_t alpha = 255)
: r(red), g(green), b(blue), a(alpha) {}
std::uint8_t r, g, b, a;
bool operator == (const Colour& other)
{
return other.r == r
&& other.g == g
&& other.b == b
&& other.a == a;
}
bool operator != (const Colour& other)
{
return !(*this == other);
}
explicit operator std::uint32_t() const
{
return (r << 24) | (g << 16) | (b << 8) | a;
}
};
}
template <typename T>
std::ostream& operator << (std::ostream& os, const tmx::Vector2<T>& t)
{
os << "{" << t.x << ", " << t.y << "}";
return os;
}
template <typename T>
std::ostream& operator << (std::ostream& os, const tmx::Rectangle<T>& t)
{
os << "{" << t.left << ", " << t.top << ", " << t.width << ", " << t.height << "}";
return os;
}
std::ostream& operator << (std::ostream& os, const tmx::Colour& c);

View File

@@ -1,110 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
template <typename T>
Vector2<T> operator + (const Vector2<T>& l, const Vector2<T>& r)
{
return { l.x + r.x, l.y + r.y };
}
template <typename T>
Vector2<T>& operator += (Vector2<T>& l, const Vector2<T>& r)
{
l.x += r.x;
l.y += r.y;
return l;
}
template <typename T>
Vector2<T> operator - (const Vector2<T>& l, const Vector2<T>& r)
{
return { l.x - r.x, l.y - r.y };
}
template <typename T>
Vector2<T>& operator -= (Vector2<T>& l, const Vector2<T>& r)
{
l.x -= r.x;
l.y -= r.y;
return l;
}
template <typename T>
Vector2<T> operator * (const Vector2<T>& l, const Vector2<T>& r)
{
return { l.x * r.x, l.y * r.y };
}
template <typename T>
Vector2<T>& operator *= (Vector2<T>& l, const Vector2<T>& r)
{
l.x *= r.x;
l.y *= r.y;
return l;
}
template <typename T>
Vector2<T> operator * (const Vector2<T>& l, T r)
{
return { l.x * r, l.y * r };
}
template <typename T>
Vector2<T>& operator *= (Vector2<T>& l, T r)
{
l.x *= r;
l.y *= r;
return l;
}
template <typename T>
Vector2<T> operator / (const Vector2<T>& l, const Vector2<T>& r)
{
return { l.x / r.x, l.y / r.y };
}
template <typename T>
Vector2<T>& operator /= (Vector2<T>& l, const Vector2<T>& r)
{
l.x /= r.x;
l.y /= r.y;
return l;
}
template <typename T>
Vector2<T> operator / (const Vector2<T>& l, T r)
{
return { l.x / r, l.y / r };
}
template <typename T>
Vector2<T>& operator /= (Vector2<T>& l, T r)
{
l.x /= r;
l.y /= r;
return l;
}

View File

@@ -1,53 +0,0 @@
/*********************************************************************
Matt Marchant 2016
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#ifndef ANDROID_INC_HPP_
#define ANDROID_INC_HPP_
#ifdef __ANDROID__
#include <string>
#include <sstream>
#include <cstdlib>
namespace std
{
template <typename T>
std::string to_string(T value)
{
std::ostringstream os;
os << value;
return os.str();
}
}
#define STOI(str) std::strtol(str.c_str(), 0, 10)
#else
#define STOI(str) std::stoi(str)
#endif // __ANDROID__
#endif // ANDROID_INC_HPP_

View File

@@ -1,190 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2021
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
//flexible logging class, based on code at https://github.com/fallahn/xygine
#ifndef TMXLITE_LOGGER_HPP_
#define TMXLITE_LOGGER_HPP_
#include <string>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <list>
#include <ctime>
#ifdef _MSC_VER
#define NOMINMAX
#include <windows.h>
#endif //_MSC_VER
#ifdef __ANDROID__
#include <android/log.h>
#include <cstring>
#define LOG_TAG "TMXlite-Debug"
//#define ALOG(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#endif // __ANDROID__
namespace tmx
{
/*!
\brief Class allowing messages to be logged to a combination
of one or more destinations such as the console, log file or
output window in Visual Studio
*/
class Logger final
{
public:
enum class Output
{
Console,
File,
All
};
enum class Type
{
Info,
Warning,
Error
};
/*!
\brief Logs a message to a given destination.
\param message Message to log
\param type Whether this message gets tagged as information, a warning or an error
\param output Destination for the message. Can be the console via cout, a log file on disk, or both
*/
static void log(const std::string& message, Type type = Type::Info, Output output = Output::Console)
{
std::string outstring;
switch (type)
{
case Type::Info:
default:
outstring = "INFO: " + message;
break;
case Type::Error:
outstring = "ERROR: " + message;
break;
case Type::Warning:
outstring = "WARNING: " + message;
break;
}
if (output == Output::Console || output == Output::All)
{
if (type == Type::Error)
{
#ifdef __ANDROID__
int outstringLength = outstring.length();
char outstring_chararray[outstringLength+1];
std::strcpy(outstring_chararray, outstring.c_str());
LOGE("%s",outstring_chararray);
#endif
std::cerr << outstring << std::endl;
}
else
{
#ifdef __ANDROID__
int outstringLength = outstring.length();
char outstring_chararray[outstringLength+1];
std::strcpy(outstring_chararray, outstring.c_str());
LOGI("%s", outstring_chararray);
#endif
std::cout << outstring << std::endl;
}
const std::size_t maxBuffer = 30;
buffer().push_back(outstring);
if (buffer().size() > maxBuffer)buffer().pop_front(); //no majick here pl0x
updateOutString(maxBuffer);
#ifdef _MSC_VER
outstring += "\n";
OutputDebugStringA(outstring.c_str());
#endif //_MSC_VER
}
if (output == Output::File || output == Output::All)
{
//output to a log file
std::ofstream file("output.log", std::ios::app);
if (file.good())
{
#ifndef __ANDROID__
std::time_t time = std::time(nullptr);
auto tm = *std::localtime(&time);
//put_time isn't implemented by the ndk versions of the stl
file.imbue(std::locale());
file << std::put_time(&tm, "%d/%m/%y-%H:%M:%S: ");
#endif //__ANDROID__
file << outstring << std::endl;
file.close();
}
else
{
log(message, type, Output::Console);
log("Above message was intended for log file. Opening file probably failed.", Type::Warning, Output::Console);
}
}
}
static const std::string& bufferString(){ return stringOutput(); }
private:
static std::list<std::string>& buffer(){ static std::list<std::string> buffer; return buffer; }
static std::string& stringOutput() { static std::string output; return output; }
static void updateOutString(std::size_t maxBuffer)
{
static size_t count = 0;
stringOutput().append(buffer().back());
stringOutput().append("\n");
count++;
if (count > maxBuffer)
{
stringOutput() = stringOutput().substr(stringOutput().find_first_of('\n') + 1, stringOutput().size());
count--;
}
}
};
}
#ifndef _DEBUG_
#define LOG(message, type)
#else
#define LOG(message, type) {\
std::stringstream ss; \
ss << message << " (" << __FILE__ << ", " << __LINE__ << ")"; \
tmx::Logger::log(ss.str(), type);}
#endif //_DEBUG_
#endif //TMXLITE_LOGGER_HPP_

View File

@@ -1,133 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#ifndef USE_ZLIB
#include "miniz.h"
#else
#include <zlib.h>
#endif
#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/Types.hpp>
#include <tmxlite/detail/Log.hpp>
#include <cstring>
bool tmx::decompress(const char* source, std::vector<unsigned char>& dest, std::size_t inSize, std::size_t expectedSize)
{
if (!source)
{
LOG("Input string is empty, decompression failed.", Logger::Type::Error);
return false;
}
//#ifdef USE_EXTLIBS
//#else
int currentSize = static_cast<int>(expectedSize);
std::vector<unsigned char> byteArray(expectedSize / sizeof(unsigned char));
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.next_in = (Bytef*)source;
stream.avail_in = static_cast<unsigned int>(inSize);
stream.next_out = (Bytef*)byteArray.data();
stream.avail_out = static_cast<unsigned int>(expectedSize);
//we'd prefer to use inflateInit2 but it appears
//to be incorrect in miniz. This is fine for zlib
//compressed data, but gzip compressed streams
//will fail to inflate.
#ifdef USE_ZLIB
if (inflateInit2(&stream, 15 + 32) != Z_OK)
#else
if (inflateInit(&stream) != Z_OK)
#endif
{
LOG("inflate init failed", Logger::Type::Error);
return false;
}
int result = 0;
do
{
result = inflate(&stream, Z_SYNC_FLUSH);
switch (result)
{
default: break;
case Z_NEED_DICT:
case Z_STREAM_ERROR:
result = Z_DATA_ERROR;
case Z_DATA_ERROR:
Logger::log("If using gzip or zstd compression try using zlib instead", Logger::Type::Info);
case Z_MEM_ERROR:
inflateEnd(&stream);
Logger::log("inflate() returned " + std::to_string(result), Logger::Type::Error);
return false;
}
if (result != Z_STREAM_END)
{
int oldSize = currentSize;
currentSize *= 2;
std::vector<unsigned char> newArray(currentSize / sizeof(unsigned char));
std::memcpy(newArray.data(), byteArray.data(), currentSize / 2);
byteArray = std::move(newArray);
stream.next_out = (Bytef*)(byteArray.data() + oldSize);
stream.avail_out = oldSize;
}
} while (result != Z_STREAM_END);
if (stream.avail_in != 0)
{
LOG("stream.avail_in is 0", Logger::Type::Error);
LOG("zlib decompression failed.", Logger::Type::Error);
return false;
}
const int outSize = currentSize - stream.avail_out;
inflateEnd(&stream);
std::vector<unsigned char> newArray(outSize / sizeof(unsigned char));
std::memcpy(newArray.data(), byteArray.data(), outSize);
byteArray = std::move(newArray);
//copy bytes to vector
dest.insert(dest.begin(), byteArray.begin(), byteArray.end());
//#endif
return true;
}
std::ostream& operator << (std::ostream& os, const tmx::Colour& c)
{
os << "RGBA: " << (int)c.r << ", " << (int)c.g << ", " << (int)c.b << ", " << (int)c.a;
return os;
}

View File

@@ -1,106 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#include <tmxlite/ImageLayer.hpp>
#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/detail/Log.hpp>
using namespace tmx;
ImageLayer::ImageLayer(const std::string& workingDir)
: m_workingDir (workingDir),
m_hasTransparency (false),
m_hasRepeatX (false),
m_hasRepeatY (false)
{
}
//public
void ImageLayer::parse(const pugi::xml_node& node, Map*)
{
std::string attribName = node.name();
if (attribName != "imagelayer")
{
Logger::log("Node not an image layer, node skipped", Logger::Type::Error);
return;
}
//TODO this gets repeated foreach layer type and could all be moved to base class...
setName(node.attribute("name").as_string());
setClass(node.attribute("class").as_string());
setOpacity(node.attribute("opacity").as_float(1.f));
setVisible(node.attribute("visible").as_bool(true));
setOffset(node.attribute("offsetx").as_int(0), node.attribute("offsety").as_int(0));
setSize(node.attribute("width").as_uint(0), node.attribute("height").as_uint(0));
setParallaxFactor(node.attribute("parallaxx").as_float(1.f), node.attribute("parallaxy").as_float(1.f));
std::string tintColour = node.attribute("tintcolor").as_string();
if (!tintColour.empty())
{
setTintColour(colourFromString(tintColour));
}
m_hasRepeatX = node.attribute("repeatx").as_bool(false);
m_hasRepeatY = node.attribute("repeaty").as_bool(false);
for (const auto& child : node.children())
{
attribName = child.name();
if (attribName == "image")
{
attribName = child.attribute("source").as_string();
if (attribName.empty())
{
Logger::log("Image Layer has missing source property", Logger::Type::Warning);
return;
}
if (child.attribute("width") && child.attribute("height"))
{
m_imageSize.x = child.attribute("width").as_uint();
m_imageSize.y = child.attribute("height").as_uint();
}
m_filePath = resolveFilePath(attribName, m_workingDir);
if (child.attribute("trans"))
{
attribName = child.attribute("trans").as_string();
m_transparencyColour = colourFromString(attribName);
m_hasTransparency = true;
}
}
else if (attribName == "properties")
{
for (const auto& p : child.children())
{
addProperty(p);
}
}
}
}

View File

@@ -1,105 +0,0 @@
/*********************************************************************
Grant Gangi 2019
Matt Marchant 2023
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#include <tmxlite/LayerGroup.hpp>
#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/ObjectGroup.hpp>
#include <tmxlite/ImageLayer.hpp>
#include <tmxlite/TileLayer.hpp>
#include <tmxlite/detail/Log.hpp>
using namespace tmx;
LayerGroup::LayerGroup(const std::string& workingDir, const Vector2u& tileCount)
: m_workingDir(workingDir),
m_tileCount(tileCount)
{
}
//public
void LayerGroup::parse(const pugi::xml_node& node, Map* map)
{
assert(map);
std::string attribString = node.name();
if (attribString != "group")
{
Logger::log("Node was not a group layer, node will be skipped.", Logger::Type::Error);
return;
}
setName(node.attribute("name").as_string());
setClass(node.attribute("class").as_string());
setOpacity(node.attribute("opacity").as_float(1.f));
setVisible(node.attribute("visible").as_bool(true));
setOffset(node.attribute("offsetx").as_int(0), node.attribute("offsety").as_int(0));
setSize(node.attribute("width").as_uint(0), node.attribute("height").as_uint(0));
setParallaxFactor(node.attribute("parallaxx").as_float(1.f), node.attribute("parallaxy").as_float(1.f));
std::string tintColour = node.attribute("tintcolor").as_string();
if (!tintColour.empty())
{
setTintColour(colourFromString(tintColour));
}
// parse children
for (const auto& child : node.children())
{
attribString = child.name();
if (attribString == "properties")
{
for (const auto& p : child.children())
{
addProperty(p);
}
}
else if (attribString == "layer")
{
m_layers.emplace_back(std::make_unique<TileLayer>(m_tileCount.x * m_tileCount.y));
m_layers.back()->parse(child, map);
}
else if (attribString == "objectgroup")
{
m_layers.emplace_back(std::make_unique<ObjectGroup>());
m_layers.back()->parse(child, map);
}
else if (attribString == "imagelayer")
{
m_layers.emplace_back(std::make_unique<ImageLayer>(m_workingDir));
m_layers.back()->parse(child, map);
}
else if (attribString == "group")
{
m_layers.emplace_back(std::make_unique<LayerGroup>(m_workingDir, m_tileCount));
m_layers.back()->parse(child, map);
}
else
{
LOG("Unidentified name " + attribString + ": node skipped", Logger::Type::Warning);
}
}
}

View File

@@ -1,363 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#include <tmxlite/Map.hpp>
#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/ObjectGroup.hpp>
#include <tmxlite/ImageLayer.hpp>
#include <tmxlite/TileLayer.hpp>
#include <tmxlite/LayerGroup.hpp>
#include <tmxlite/detail/Log.hpp>
#include <tmxlite/detail/Android.hpp>
#include <queue>
using namespace tmx;
Map::Map()
: m_orientation (Orientation::None),
m_renderOrder (RenderOrder::None),
m_infinite (false),
m_hexSideLength (0.f),
m_staggerAxis (StaggerAxis::None),
m_staggerIndex (StaggerIndex::None)
{
}
//public
bool Map::load(const std::string& path)
{
reset();
//open the doc
pugi::xml_document doc;
auto result = doc.load_file(path.c_str());
if (!result)
{
Logger::log("Failed opening " + path, Logger::Type::Error);
Logger::log("Reason: " + std::string(result.description()), Logger::Type::Error);
return false;
}
//make sure we have consistent path separators
m_workingDirectory = path;
std::replace(m_workingDirectory.begin(), m_workingDirectory.end(), '\\', '/');
m_workingDirectory = getFilePath(m_workingDirectory);
if (!m_workingDirectory.empty() &&
m_workingDirectory.back() == '/')
{
m_workingDirectory.pop_back();
}
//find the map node and bail if it doesn't exist
auto mapNode = doc.child("map");
if (!mapNode)
{
Logger::log("Failed opening map: " + path + ", no map node found", Logger::Type::Error);
return reset();
}
return parseMapNode(mapNode);
}
bool Map::loadFromString(const std::string& data, const std::string& workingDir)
{
reset();
//open the doc
pugi::xml_document doc;
auto result = doc.load_string(data.c_str());
if (!result)
{
Logger::log("Failed opening map", Logger::Type::Error);
Logger::log("Reason: " + std::string(result.description()), Logger::Type::Error);
return false;
}
//make sure we have consistent path separators
m_workingDirectory = workingDir;
std::replace(m_workingDirectory.begin(), m_workingDirectory.end(), '\\', '/');
m_workingDirectory = getFilePath(m_workingDirectory);
if (!m_workingDirectory.empty() &&
m_workingDirectory.back() == '/')
{
m_workingDirectory.pop_back();
}
//find the map node and bail if it doesn't exist
auto mapNode = doc.child("map");
if (!mapNode)
{
Logger::log("Failed opening map: no map node found", Logger::Type::Error);
return reset();
}
return parseMapNode(mapNode);
}
//private
bool Map::parseMapNode(const pugi::xml_node& mapNode)
{
//parse map attributes
std::size_t pointPos = 0;
std::string attribString = mapNode.attribute("version").as_string();
if (attribString.empty() || (pointPos = attribString.find('.')) == std::string::npos)
{
Logger::log("Invalid map version value, map not loaded.", Logger::Type::Error);
return reset();
}
m_version.upper = STOI(attribString.substr(0, pointPos));
m_version.lower = STOI(attribString.substr(pointPos + 1));
m_class = mapNode.attribute("class").as_string();
attribString = mapNode.attribute("orientation").as_string();
if (attribString.empty())
{
Logger::log("Missing map orientation attribute, map not loaded.", Logger::Type::Error);
return reset();
}
if (attribString == "orthogonal")
{
m_orientation = Orientation::Orthogonal;
}
else if (attribString == "isometric")
{
m_orientation = Orientation::Isometric;
}
else if (attribString == "staggered")
{
m_orientation = Orientation::Staggered;
}
else if (attribString == "hexagonal")
{
m_orientation = Orientation::Hexagonal;
}
else
{
Logger::log(attribString + " format maps aren't supported yet, sorry! Map not loaded", Logger::Type::Error);
return reset();
}
attribString = mapNode.attribute("renderorder").as_string();
//this property is optional for older version of map files
if (!attribString.empty())
{
if (attribString == "right-down")
{
m_renderOrder = RenderOrder::RightDown;
}
else if (attribString == "right-up")
{
m_renderOrder = RenderOrder::RightUp;
}
else if (attribString == "left-down")
{
m_renderOrder = RenderOrder::LeftDown;
}
else if (attribString == "left-up")
{
m_renderOrder = RenderOrder::LeftUp;
}
else
{
Logger::log(attribString + ": invalid render order. Map not loaded.", Logger::Type::Error);
return reset();
}
}
if (mapNode.attribute("infinite"))
{
m_infinite = mapNode.attribute("infinite").as_int() != 0;
}
unsigned width = mapNode.attribute("width").as_int();
unsigned height = mapNode.attribute("height").as_int();
if (width && height)
{
m_tileCount = { width, height };
}
else
{
Logger::log("Invalid map tile count, map not loaded", Logger::Type::Error);
return reset();
}
width = mapNode.attribute("tilewidth").as_int();
height = mapNode.attribute("tileheight").as_int();
if (width && height)
{
m_tileSize = { width, height };
}
else
{
Logger::log("Invalid tile size, map not loaded", Logger::Type::Error);
return reset();
}
m_hexSideLength = mapNode.attribute("hexsidelength").as_float();
if (m_orientation == Orientation::Hexagonal && m_hexSideLength <= 0)
{
Logger::log("Invalid he side length found, map not loaded", Logger::Type::Error);
return reset();
}
attribString = mapNode.attribute("staggeraxis").as_string();
if (attribString == "x")
{
m_staggerAxis = StaggerAxis::X;
}
else if (attribString == "y")
{
m_staggerAxis = StaggerAxis::Y;
}
if ((m_orientation == Orientation::Staggered || m_orientation == Orientation::Hexagonal)
&& m_staggerAxis == StaggerAxis::None)
{
Logger::log("Map missing stagger axis property. Map not loaded.", Logger::Type::Error);
return reset();
}
attribString = mapNode.attribute("staggerindex").as_string();
if (attribString == "odd")
{
m_staggerIndex = StaggerIndex::Odd;
}
else if (attribString == "even")
{
m_staggerIndex = StaggerIndex::Even;
}
if ((m_orientation == Orientation::Staggered || m_orientation == Orientation::Hexagonal)
&& m_staggerIndex == StaggerIndex::None)
{
Logger::log("Map missing stagger index property. Map not loaded.", Logger::Type::Error);
return reset();
}
m_parallaxOrigin =
{
mapNode.attribute("parallaxoriginx").as_float(0.f),
mapNode.attribute("parallaxoriginy").as_float(0.f)
};
//colour property is optional
attribString = mapNode.attribute("backgroundcolor").as_string();
if (!attribString.empty())
{
m_backgroundColour = colourFromString(attribString);
}
//TODO do we need next object ID
//parse all child nodes
for (const auto& node : mapNode.children())
{
std::string name = node.name();
if (name == "tileset")
{
m_tilesets.emplace_back(m_workingDirectory);
m_tilesets.back().parse(node, this);
}
else if (name == "layer")
{
m_layers.emplace_back(std::make_unique<TileLayer>(m_tileCount.x * m_tileCount.y));
m_layers.back()->parse(node);
}
else if (name == "objectgroup")
{
m_layers.emplace_back(std::make_unique<ObjectGroup>());
m_layers.back()->parse(node, this);
}
else if (name == "imagelayer")
{
m_layers.emplace_back(std::make_unique<ImageLayer>(m_workingDirectory));
m_layers.back()->parse(node, this);
}
else if (name == "properties")
{
const auto& children = node.children();
for (const auto& child : children)
{
m_properties.emplace_back();
m_properties.back().parse(child);
}
}
else if (name == "group")
{
m_layers.emplace_back(std::make_unique<LayerGroup>(m_workingDirectory, m_tileCount));
m_layers.back()->parse(node, this);
}
else
{
LOG("Unidentified name " + name + ": node skipped", Logger::Type::Warning);
}
}
// fill animated tiles for easier lookup into map
for(const auto& ts : m_tilesets)
{
for(const auto& tile : ts.getTiles())
{
if (!tile.animation.frames.empty())
{
m_animTiles[tile.ID + ts.getFirstGID()] = tile;
}
}
}
return true;
}
bool Map::reset()
{
m_orientation = Orientation::None;
m_renderOrder = RenderOrder::None;
m_tileCount = { 0u, 0u };
m_tileSize = { 0u, 0u };
m_hexSideLength = 0.f;
m_staggerAxis = StaggerAxis::None;
m_staggerIndex = StaggerIndex::None;
m_backgroundColour = {};
m_workingDirectory = "";
m_tilesets.clear();
m_layers.clear();
m_properties.clear();
m_templateObjects.clear();
m_templateTilesets.clear();
m_animTiles.clear();
return false;
}

View File

@@ -1,399 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2021
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#include <tmxlite/Object.hpp>
#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/Map.hpp>
#include <tmxlite/Tileset.hpp>
#include <tmxlite/detail/Log.hpp>
#include <sstream>
using namespace tmx;
Object::Object()
: m_UID (0),
m_rotation (0.f),
m_tileID (0),
m_flipFlags (0),
m_visible (true),
m_shape (Shape::Rectangle)
{
}
//public
void Object::parse(const pugi::xml_node& node, Map* map)
{
std::string attribString = node.name();
if (attribString != "object")
{
Logger::log("This not an Object node, parsing skipped.", Logger::Type::Error);
return;
}
m_UID = node.attribute("id").as_int();
m_name = node.attribute("name").as_string();
m_class = node.attribute("type").as_string();
if (m_class.empty())
{
m_class = node.attribute("class").as_string();
}
m_position.x = node.attribute("x").as_float();
m_AABB.left = m_position.x;
m_position.y = node.attribute("y").as_float();
m_AABB.top = m_position.y;
m_AABB.width = node.attribute("width").as_float();
m_AABB.height = node.attribute("height").as_float();
m_rotation = node.attribute("rotation").as_float();
m_visible = node.attribute("visible").as_bool(true);
m_tileID = node.attribute("gid").as_uint();
static const std::uint32_t mask = 0xf0000000;
m_flipFlags = ((m_tileID & mask) >> 28);
m_tileID = m_tileID & ~mask;
for (const auto& child : node.children())
{
attribString = child.name();
if (attribString == "properties")
{
for (const auto& p : child.children())
{
m_properties.emplace_back();
m_properties.back().parse(p);
}
}
else if (attribString == "ellipse")
{
m_shape = Shape::Ellipse;
}
else if (attribString == "point")
{
m_shape = Shape::Point;
}
else if (attribString == "polygon")
{
m_shape = Shape::Polygon;
parsePoints(child);
}
else if (attribString == "polyline")
{
m_shape = Shape::Polyline;
parsePoints(child);
}
else if (attribString == "text")
{
m_shape = Shape::Text;
parseText(child);
}
}
//parse templates last so we know which properties
//ought to be overridden
std::string templateStr = node.attribute("template").as_string();
if (!templateStr.empty() && map)
{
parseTemplate(templateStr, map);
}
}
//private
void Object::parsePoints(const pugi::xml_node& node)
{
if (node.attribute("points"))
{
std::string pointlist = node.attribute("points").as_string();
std::stringstream stream(pointlist);
std::vector<std::string> points;
std::string pointstring;
while (std::getline(stream, pointstring, ' '))
{
points.push_back(pointstring);
}
//parse each pair into sf::vector2f
for (unsigned int i = 0; i < points.size(); i++)
{
std::vector<float> coords;
std::stringstream coordstream(points[i]);
float j;
while (coordstream >> j)
{
coords.push_back(j);
//TODO this should really ignore anything non-numeric
if (coordstream.peek() == ',')
{
coordstream.ignore();
}
}
m_points.emplace_back(coords[0], coords[1]);
}
}
else
{
Logger::log("Points for polygon or polyline object are missing", Logger::Type::Warning);
}
}
void Object::parseText(const pugi::xml_node& node)
{
m_textData.bold = node.attribute("bold").as_bool(false);
m_textData.colour = colourFromString(node.attribute("color").as_string("#FFFFFFFF"));
m_textData.fontFamily = node.attribute("fontfamily").as_string();
m_textData.italic = node.attribute("italic").as_bool(false);
m_textData.kerning = node.attribute("kerning").as_bool(true);
m_textData.pixelSize = node.attribute("pixelsize").as_uint(16);
m_textData.strikethough = node.attribute("strikeout").as_bool(false);
m_textData.underline = node.attribute("underline").as_bool(false);
m_textData.wrap = node.attribute("wrap").as_bool(false);
std::string alignment = node.attribute("halign").as_string("left");
if (alignment == "left")
{
m_textData.hAlign = Text::HAlign::Left;
}
else if (alignment == "center")
{
m_textData.hAlign = Text::HAlign::Centre;
}
else if (alignment == "right")
{
m_textData.hAlign = Text::HAlign::Right;
}
alignment = node.attribute("valign").as_string("top");
if (alignment == "top")
{
m_textData.vAlign = Text::VAlign::Top;
}
else if (alignment == "center")
{
m_textData.vAlign = Text::VAlign::Centre;
}
else if (alignment == "bottom")
{
m_textData.vAlign = Text::VAlign::Bottom;
}
m_textData.content = node.text().as_string();
}
void Object::parseTemplate(const std::string& path, Map* map)
{
assert(map);
auto& templateObjects = map->getTemplateObjects();
auto& templateTilesets = map->getTemplateTilesets();
//load the template if not already loaded
if (templateObjects.count(path) == 0)
{
auto templatePath = map->getWorkingDirectory() + "/" + path;
pugi::xml_document doc;
if (!doc.load_file(templatePath.c_str()))
{
Logger::log("Failed opening template file " + path, Logger::Type::Error);
return;
}
auto templateNode = doc.child("template");
if (!templateNode)
{
Logger::log("Template node missing from " + path, Logger::Type::Error);
return;
}
//if the template has a tileset load that (if not already loaded)
std::string tilesetName;
auto tileset = templateNode.child("tileset");
if (tileset)
{
tilesetName = tileset.attribute("source").as_string();
if (!tilesetName.empty() &&
templateTilesets.count(tilesetName) == 0)
{
templateTilesets.insert(std::make_pair(tilesetName, Tileset(map->getWorkingDirectory())));
templateTilesets.at(tilesetName).parse(tileset, map);
}
}
//parse the object - don't pass the map pointer here so there's
//no recursion if someone tried to get clever and put a template in a template
auto obj = templateNode.child("object");
if (obj)
{
templateObjects.insert(std::make_pair(path, Object()));
templateObjects[path].parse(obj, nullptr);
templateObjects[path].m_tilesetName = tilesetName;
}
}
//apply any non-overridden object properties from the template
if (templateObjects.count(path) != 0)
{
const auto& obj = templateObjects[path];
if (m_AABB.width == 0)
{
m_AABB.width = obj.m_AABB.width;
}
if (m_AABB.height == 0)
{
m_AABB.height = obj.m_AABB.height;
}
m_tilesetName = obj.m_tilesetName;
if (m_name.empty())
{
m_name = obj.m_name;
}
if (m_class.empty())
{
m_class = obj.m_class;
}
if (m_rotation == 0)
{
m_rotation = obj.m_rotation;
}
if (m_tileID == 0)
{
m_tileID = obj.m_tileID;
}
if (m_flipFlags == 0)
{
m_flipFlags = obj.m_flipFlags;
}
if (m_shape == Shape::Rectangle)
{
m_shape = obj.m_shape;
}
if (m_points.empty())
{
m_points = obj.m_points;
}
//compare properties and only copy ones that don't exist
for (const auto& p : obj.m_properties)
{
auto result = std::find_if(m_properties.begin(), m_properties.end(),
[&p](const Property& a)
{
return a.getName() == p.getName();
});
if (result == m_properties.end())
{
m_properties.push_back(p);
}
}
if (m_shape == Shape::Text)
{
//check each text property and update as necessary
//TODO this makes he assumption we prefer the template
//properties over the default ones - this might not
//actually be the case....
const auto& otherText = obj.m_textData;
if (m_textData.fontFamily.empty())
{
m_textData.fontFamily = otherText.fontFamily;
}
if (m_textData.pixelSize == 16)
{
m_textData.pixelSize = otherText.pixelSize;
}
//TODO this isn't actually right if we *want* to be false
//and the template is set to true...
if (m_textData.wrap == false)
{
m_textData.wrap = otherText.wrap;
}
if (m_textData.colour == Colour())
{
m_textData.colour = otherText.colour;
}
if (m_textData.bold == false)
{
m_textData.bold = otherText.bold;
}
if (m_textData.italic == false)
{
m_textData.italic = otherText.italic;
}
if (m_textData.underline == false)
{
m_textData.underline = otherText.underline;
}
if (m_textData.strikethough == false)
{
m_textData.strikethough = otherText.strikethough;
}
if (m_textData.kerning == true)
{
m_textData.kerning = otherText.kerning;
}
if (m_textData.hAlign == Text::HAlign::Left)
{
m_textData.hAlign = otherText.hAlign;
}
if (m_textData.vAlign == Text::VAlign::Top)
{
m_textData.vAlign = otherText.vAlign;
}
if (m_textData.content.empty())
{
m_textData.content = otherText.content;
}
}
}
}

View File

@@ -1,98 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/ObjectGroup.hpp>
#include <tmxlite/detail/Log.hpp>
using namespace tmx;
ObjectGroup::ObjectGroup()
: m_colour (127, 127, 127, 255),
m_drawOrder (DrawOrder::TopDown)
{
}
//public
void ObjectGroup::parse(const pugi::xml_node& node, Map* map)
{
assert(map);
std::string attribString = node.name();
if (attribString != "objectgroup")
{
Logger::log("Node was not an object group, node will be skipped.", Logger::Type::Error);
return;
}
setName(node.attribute("name").as_string());
setClass(node.attribute("class").as_string());
attribString = node.attribute("color").as_string();
if (!attribString.empty())
{
m_colour = colourFromString(attribString);
}
setOpacity(node.attribute("opacity").as_float(1.f));
setVisible(node.attribute("visible").as_bool(true));
setOffset(node.attribute("offsetx").as_int(0), node.attribute("offsety").as_int(0));
setSize(node.attribute("width").as_uint(0), node.attribute("height").as_uint(0));
setParallaxFactor(node.attribute("parallaxx").as_float(1.f), node.attribute("parallaxy").as_float(1.f));
std::string tintColour = node.attribute("tintcolor").as_string();
if (!tintColour.empty())
{
setTintColour(colourFromString(tintColour));
}
attribString = node.attribute("draworder").as_string();
if (attribString == "index")
{
m_drawOrder = DrawOrder::Index;
}
for (const auto& child : node.children())
{
attribString = child.name();
if (attribString == "properties")
{
for (const auto& p : child)
{
m_properties.emplace_back();
m_properties.back().parse(p);
}
}
else if (attribString == "object")
{
m_objects.emplace_back();
m_objects.back().parse(child, map);
}
}
}

View File

@@ -1,150 +0,0 @@
/*********************************************************************
Raphaël Frantz 2021
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/ObjectTypes.hpp>
#include <tmxlite/detail/Log.hpp>
using namespace tmx;
bool ObjectTypes::load(const std::string &path)
{
reset();
//open the doc
pugi::xml_document doc;
auto result = doc.load_file(path.c_str());
if (!result)
{
Logger::log("Failed opening " + path, Logger::Type::Error);
Logger::log("Reason: " + std::string(result.description()), Logger::Type::Error);
return false;
}
//make sure we have consistent path separators
m_workingDirectory = path;
std::replace(m_workingDirectory.begin(), m_workingDirectory.end(), '\\', '/');
m_workingDirectory = getFilePath(m_workingDirectory);
if (!m_workingDirectory.empty() &&
m_workingDirectory.back() == '/')
{
m_workingDirectory.pop_back();
}
//find the node and bail if it doesn't exist
auto node = doc.child("objecttypes");
if (!node)
{
Logger::log("Failed opening object types: " + path + ", no objecttype node found", Logger::Type::Error);
return reset();
}
return parseObjectTypesNode(node);
}
bool ObjectTypes::loadFromString(const std::string &data, const std::string &workingDir)
{
reset();
//open the doc
pugi::xml_document doc;
auto result = doc.load_string(data.c_str());
if (!result)
{
Logger::log("Failed opening object types", Logger::Type::Error);
Logger::log("Reason: " + std::string(result.description()), Logger::Type::Error);
return false;
}
//make sure we have consistent path separators
m_workingDirectory = workingDir;
std::replace(m_workingDirectory.begin(), m_workingDirectory.end(), '\\', '/');
m_workingDirectory = getFilePath(m_workingDirectory);
if (!m_workingDirectory.empty() &&
m_workingDirectory.back() == '/')
{
m_workingDirectory.pop_back();
}
//find the node and bail if it doesn't exist
auto node = doc.child("objecttypes");
if (!node)
{
Logger::log("Failed object types: no objecttypes node found", Logger::Type::Error);
return reset();
}
return parseObjectTypesNode(node);
}
bool ObjectTypes::parseObjectTypesNode(const pugi::xml_node &node)
{
//<objecttypes> <-- node
// <objecttype name="Character" color="#1e47ff">
// <property>...
//parse types
for(const auto& child : node.children())
{
std::string attribString = child.name();
if (attribString == "objecttype")
{
Type type;
//parse the metadata of the type
type.name = child.attribute("name").as_string();
type.colour = colourFromString(child.attribute("color").as_string("#FFFFFFFF"));;
//parse the default properties of the type
for (const auto& p : child.children())
{
Property prop;
prop.parse(p, true);
type.properties.push_back(prop);
}
m_types.push_back(type);
}
else
{
LOG("Unidentified name " + attribString + ": node skipped", Logger::Type::Warning);
}
}
return true;
}
bool ObjectTypes::reset()
{
m_workingDirectory.clear();
m_types.clear();
return false;
}

View File

@@ -1,163 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2021
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#include <tmxlite/Property.hpp>
#include <tmxlite/detail/Log.hpp>
#include <tmxlite/FreeFuncs.hpp>
using namespace tmx;
Property::Property()
: m_type(Type::Undef)
{
}
Property Property::fromBoolean(bool value)
{
Property p;
p.m_type = Type::Boolean;
p.m_boolValue = value;
return p;
}
Property Property::fromFloat(float value)
{
Property p;
p.m_type = Type::Float;
p.m_floatValue = value;
return p;
}
Property Property::fromInt(int value)
{
Property p;
p.m_type = Type::Int;
p.m_intValue = value;
return p;
}
Property Property::fromString(const std::string& value)
{
Property p;
p.m_type = Type::String;
p.m_stringValue = value;
return p;
}
Property Property::fromColour(const Colour& value)
{
Property p;
p.m_type = Type::Colour;
p.m_colourValue = value;
return p;
}
Property Property::fromFile(const std::string& value)
{
Property p;
p.m_type = Type::File;
p.m_stringValue = value;
return p;
}
Property Property::fromObject(int value)
{
Property p;
p.m_type = Type::Object;
p.m_intValue = value;
return p;
}
//public
void Property::parse(const pugi::xml_node& node, bool isObjectTypes)
{
// The value attribute name is different in object types
const char *const valueAttribute = isObjectTypes ? "default" : "value";
std::string attribData = node.name();
if (attribData != "property")
{
Logger::log("Node was not a valid property, node will be skipped", Logger::Type::Error);
return;
}
m_name = node.attribute("name").as_string();
attribData = node.attribute("type").as_string("string");
if (attribData == "bool")
{
attribData = node.attribute(valueAttribute).as_string("false");
m_boolValue = (attribData == "true");
m_type = Type::Boolean;
return;
}
else if (attribData == "int")
{
m_intValue = node.attribute(valueAttribute).as_int(0);
m_type = Type::Int;
return;
}
else if (attribData == "float")
{
m_floatValue = node.attribute(valueAttribute).as_float(0.f);
m_type = Type::Float;
return;
}
else if (attribData == "string")
{
m_stringValue = node.attribute(valueAttribute).as_string();
//if value is empty, try getting the child value instead
//as this is how multiline string properties are stored.
if(m_stringValue.empty())
{
m_stringValue = node.child_value();
}
m_type = Type::String;
return;
}
else if (attribData == "color")
{
m_colourValue = colourFromString(node.attribute(valueAttribute).as_string("#FFFFFFFF"));
m_type = Type::Colour;
return;
}
else if (attribData == "file")
{
m_stringValue = node.attribute(valueAttribute).as_string();
m_type = Type::File;
return;
}
else if (attribData == "object")
{
m_intValue = node.attribute(valueAttribute).as_int(0);
m_type = Type::Object;
return;
}
}

View File

@@ -1,334 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#ifdef USE_ZSTD
# include <zstd.h>
#endif
#include "base64.h"
#include "tmxlite/FreeFuncs.hpp"
#include "tmxlite/TileLayer.hpp"
#include "tmxlite/detail/Log.hpp"
#include <sstream>
using namespace tmx;
namespace
{
struct CompressionType final
{
enum
{
Zlib, GZip, Zstd, None
};
};
}
TileLayer::TileLayer(std::size_t tileCount)
: m_tileCount (tileCount)
{
m_tiles.reserve(tileCount);
}
//public
void TileLayer::parse(const pugi::xml_node& node, Map*)
{
std::string attribName = node.name();
if (attribName != "layer")
{
Logger::log("node not a layer node, skipped parsing", Logger::Type::Error);
return;
}
setName(node.attribute("name").as_string());
setClass(node.attribute("class").as_string());
setOpacity(node.attribute("opacity").as_float(1.f));
setVisible(node.attribute("visible").as_bool(true));
setOffset(node.attribute("offsetx").as_int(0), node.attribute("offsety").as_int(0));
setSize(node.attribute("width").as_uint(0), node.attribute("height").as_uint(0));
setParallaxFactor(node.attribute("parallaxx").as_float(1.f), node.attribute("parallaxy").as_float(1.f));
std::string tintColour = node.attribute("tintcolor").as_string();
if (!tintColour.empty())
{
setTintColour(colourFromString(tintColour));
}
for (const auto& child : node.children())
{
attribName = child.name();
if (attribName == "data")
{
attribName = child.attribute("encoding").as_string();
if (attribName == "base64")
{
parseBase64(child);
}
else if (attribName == "csv")
{
parseCSV(child);
}
else
{
parseUnencoded(child);
}
}
else if (attribName == "properties")
{
for (const auto& p : child.children())
{
addProperty(p);
}
}
}
}
//private
void TileLayer::parseBase64(const pugi::xml_node& node)
{
auto processDataString = [](std::string dataString, std::size_t tileCount, std::int32_t compressionType)->std::vector<std::uint32_t>
{
std::stringstream ss;
ss << dataString;
ss >> dataString;
dataString = base64_decode(dataString);
std::size_t expectedSize = tileCount * 4; //4 bytes per tile
std::vector<unsigned char> byteData;
byteData.reserve(expectedSize);
switch (compressionType)
{
default:
byteData.insert(byteData.end(), dataString.begin(), dataString.end());
break;
case CompressionType::Zstd:
#if defined USE_ZSTD
{
std::size_t dataSize = dataString.length() * sizeof(unsigned char);
std::size_t result = ZSTD_decompress(byteData.data(), expectedSize, &dataString[0], dataSize);
if (ZSTD_isError(result))
{
std::string err = ZSTD_getErrorName(result);
LOG("Failed to decompress layer data, node skipped.\nError: " + err, Logger::Type::Error);
}
}
break;
#else
Logger::log("Library must be built with USE_ZSTD for Zstd compression", Logger::Type::Error);
return {};
#endif
case CompressionType::GZip:
#ifndef USE_ZLIB
Logger::log("Library must be built with USE_ZLIB for GZip compression", Logger::Type::Error);
return {};
#endif
//[[fallthrough]];
case CompressionType::Zlib:
{
//unzip
std::size_t dataSize = dataString.length() * sizeof(unsigned char);
if (!decompress(dataString.c_str(), byteData, dataSize, expectedSize))
{
LOG("Failed to decompress layer data, node skipped.", Logger::Type::Error);
return {};
}
}
break;
}
//data stream is in bytes so we need to OR into 32 bit values
std::vector<std::uint32_t> IDs;
IDs.reserve(tileCount);
for (auto i = 0u; i < expectedSize - 3u; i += 4u)
{
std::uint32_t id = byteData[i] | byteData[i + 1] << 8 | byteData[i + 2] << 16 | byteData[i + 3] << 24;
IDs.push_back(id);
}
return IDs;
};
std::int32_t compressionType = CompressionType::None;
std::string compression = node.attribute("compression").as_string();
if (compression == "gzip")
{
compressionType = CompressionType::GZip;
}
else if (compression == "zlib")
{
compressionType = CompressionType::Zlib;
}
else if (compression == "zstd")
{
compressionType = CompressionType::Zstd;
}
std::string data = node.text().as_string();
if (data.empty())
{
//check for chunk nodes
auto dataCount = 0;
for (const auto& childNode : node.children())
{
std::string childName = childNode.name();
if (childName == "chunk")
{
std::string dataString = childNode.text().as_string();
if (!dataString.empty())
{
Chunk chunk;
chunk.position.x = childNode.attribute("x").as_int();
chunk.position.y = childNode.attribute("y").as_int();
chunk.size.x = childNode.attribute("width").as_int();
chunk.size.y = childNode.attribute("height").as_int();
auto IDs = processDataString(dataString, (chunk.size.x * chunk.size.y), compressionType);
if (!IDs.empty())
{
createTiles(IDs, chunk.tiles);
m_chunks.push_back(chunk);
dataCount++;
}
}
}
}
if (dataCount == 0)
{
Logger::log("Layer " + getName() + " has no layer data. Layer skipped.", Logger::Type::Error);
return;
}
}
else
{
auto IDs = processDataString(data, m_tileCount, compressionType);
createTiles(IDs, m_tiles);
}
}
void TileLayer::parseCSV(const pugi::xml_node& node)
{
auto processDataString = [](const std::string dataString, std::size_t tileCount)->std::vector<std::uint32_t>
{
std::vector<std::uint32_t> IDs;
IDs.reserve(tileCount);
const char* ptr = dataString.c_str();
while (true)
{
char* end;
auto res = std::strtoul(ptr, &end, 10);
if (end == ptr) break;
ptr = end;
IDs.push_back(res);
if (*ptr == ',') ++ptr;
}
return IDs;
};
std::string data = node.text().as_string();
if (data.empty())
{
//check for chunk nodes
auto dataCount = 0;
for (const auto& childNode : node.children())
{
std::string childName = childNode.name();
if (childName == "chunk")
{
std::string dataString = childNode.text().as_string();
if (!dataString.empty())
{
Chunk chunk;
chunk.position.x = childNode.attribute("x").as_int();
chunk.position.y = childNode.attribute("y").as_int();
chunk.size.x = childNode.attribute("width").as_int();
chunk.size.y = childNode.attribute("height").as_int();
auto IDs = processDataString(dataString, chunk.size.x * chunk.size.y);
if (!IDs.empty())
{
createTiles(IDs, chunk.tiles);
m_chunks.push_back(chunk);
dataCount++;
}
}
}
}
if (dataCount == 0)
{
Logger::log("Layer " + getName() + " has no layer data. Layer skipped.", Logger::Type::Error);
return;
}
}
else
{
createTiles(processDataString(data, m_tileCount), m_tiles);
}
}
void TileLayer::parseUnencoded(const pugi::xml_node& node)
{
std::string attribName;
std::vector<std::uint32_t> IDs;
IDs.reserve(m_tileCount);
for (const auto& child : node.children())
{
attribName = child.name();
if (attribName == "tile")
{
IDs.push_back(child.attribute("gid").as_uint());
}
}
createTiles(IDs, m_tiles);
}
void TileLayer::createTiles(const std::vector<std::uint32_t>& IDs, std::vector<Tile>& destination)
{
//LOG(IDs.size() != m_tileCount, "Layer tile count does not match expected size. Found: "
// + std::to_string(IDs.size()) + ", expected: " + std::to_string(m_tileCount));
static const std::uint32_t mask = 0xf0000000;
for (const auto& id : IDs)
{
destination.emplace_back();
destination.back().flipFlags = ((id & mask) >> 28);
destination.back().ID = id & ~mask;
}
}

View File

@@ -1,456 +0,0 @@
/*********************************************************************
Matt Marchant 2016 - 2023
http://trederia.blogspot.com
tmxlite - Zlib license.
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
*********************************************************************/
#include <pugixml.hpp>
#include <tmxlite/Tileset.hpp>
#include <tmxlite/FreeFuncs.hpp>
#include <tmxlite/detail/Log.hpp>
#include <ctype.h>
using namespace tmx;
Tileset::Tileset(const std::string& workingDir)
: m_workingDir (workingDir),
m_firstGID (0),
m_spacing (0),
m_margin (0),
m_tileCount (0),
m_columnCount (0),
m_objectAlignment (ObjectAlignment::Unspecified),
m_transparencyColour (0, 0, 0, 0),
m_hasTransparency (false)
{
}
//public
void Tileset::parse(pugi::xml_node node, Map* map)
{
assert(map);
std::string attribString = node.name();
if (attribString != "tileset")
{
Logger::log(attribString + ": not a tileset node! Node will be skipped.", Logger::Type::Warning);
return;
}
m_firstGID = node.attribute("firstgid").as_int();
if (m_firstGID == 0)
{
Logger::log("Invalid first GID in tileset. Tileset node skipped.", Logger::Type::Warning);
return;
}
pugi::xml_document tsxDoc; //need to keep this in scope
if (node.attribute("source"))
{
//parse TSX doc
std::string path = node.attribute("source").as_string();
path = resolveFilePath(path, m_workingDir);
//as the TSX file now dictates the image path, the working
//directory is now that of the tsx file
auto position = path.find_last_of('/');
if (position != std::string::npos)
{
m_workingDir = path.substr(0, position);
}
else
{
m_workingDir = "";
}
//see if doc can be opened
auto result = tsxDoc.load_file(path.c_str());
if (!result)
{
Logger::log(path + ": Failed opening tsx file for tile set, tile set will be skipped", Logger::Type::Error);
return reset();
}
//if it can then replace the current node with tsx node
node = tsxDoc.child("tileset");
if (!node)
{
Logger::log("tsx file does not contain a tile set node, tile set will be skipped", Logger::Type::Error);
return reset();
}
}
m_name = node.attribute("name").as_string();
LOG("found tile set " + m_name, Logger::Type::Info);
m_class = node.attribute("class").as_string();
m_tileSize.x = node.attribute("tilewidth").as_int();
m_tileSize.y = node.attribute("tileheight").as_int();
if (m_tileSize.x == 0 || m_tileSize.y == 0)
{
Logger::log("Invalid tile size found in tile set node. Node will be skipped.", Logger::Type::Error);
return reset();
}
m_spacing = node.attribute("spacing").as_int();
m_margin = node.attribute("margin").as_int();
m_tileCount = node.attribute("tilecount").as_int();
m_columnCount = node.attribute("columns").as_int();
m_tileIndex.reserve(m_tileCount);
m_tiles.reserve(m_tileCount);
std::string objectAlignment = node.attribute("objectalignment").as_string();
if (!objectAlignment.empty())
{
if (objectAlignment == "unspecified")
{
m_objectAlignment = ObjectAlignment::Unspecified;
}
else if (objectAlignment == "topleft")
{
m_objectAlignment = ObjectAlignment::TopLeft;
}
else if (objectAlignment == "top")
{
m_objectAlignment = ObjectAlignment::Top;
}
else if (objectAlignment == "topright")
{
m_objectAlignment = ObjectAlignment::TopRight;
}
else if (objectAlignment == "left")
{
m_objectAlignment = ObjectAlignment::Left;
}
else if (objectAlignment == "center")
{
m_objectAlignment = ObjectAlignment::Center;
}
else if (objectAlignment == "right")
{
m_objectAlignment = ObjectAlignment::Right;
}
else if (objectAlignment == "bottomleft")
{
m_objectAlignment = ObjectAlignment::BottomLeft;
}
else if (objectAlignment == "bottom")
{
m_objectAlignment = ObjectAlignment::Bottom;
}
else if (objectAlignment == "bottomright")
{
m_objectAlignment = ObjectAlignment::BottomRight;
}
}
const auto& children = node.children();
for (const auto& node : children)
{
std::string name = node.name();
if (name == "image")
{
//TODO this currently doesn't cover embedded images
//mostly because I can't figure out how to export them
//from the Tiled editor... but also resource handling
//should be handled by the renderer, not the parser.
attribString = node.attribute("source").as_string();
if (attribString.empty())
{
Logger::log("Tileset image node has missing source property, tile set not loaded", Logger::Type::Error);
return reset();
}
m_imagePath = resolveFilePath(attribString, m_workingDir);
if (node.attribute("trans"))
{
attribString = node.attribute("trans").as_string();
m_transparencyColour = colourFromString(attribString);
m_hasTransparency = true;
}
if (node.attribute("width") && node.attribute("height"))
{
m_imageSize.x = node.attribute("width").as_int();
m_imageSize.y = node.attribute("height").as_int();
}
}
else if (name == "tileoffset")
{
parseOffsetNode(node);
}
else if (name == "properties")
{
parsePropertyNode(node);
}
else if (name == "terraintypes")
{
parseTerrainNode(node);
}
else if (name == "tile")
{
parseTileNode(node, map);
}
}
//if the tsx file does not declare every tile, we create the missing ones
if (m_tiles.size() != getTileCount())
{
for (std::uint32_t ID = 0; ID < getTileCount(); ID++)
{
createMissingTile(ID);
}
}
}
std::uint32_t Tileset::getLastGID() const
{
assert(!m_tileIndex.empty());
return m_firstGID + static_cast<std::uint32_t>(m_tileIndex.size()) - 1;
}
const Tileset::Tile* Tileset::getTile(std::uint32_t id) const
{
if (!hasTile(id))
{
return nullptr;
}
//corrects the ID. Indices and IDs are different.
id -= m_firstGID;
id = m_tileIndex[id];
return id ? &m_tiles[id - 1] : nullptr;
}
//private
void Tileset::reset()
{
m_firstGID = 0;
m_source = "";
m_name = "";
m_class = "";
m_tileSize = { 0,0 };
m_spacing = 0;
m_margin = 0;
m_tileCount = 0;
m_columnCount = 0;
m_objectAlignment = ObjectAlignment::Unspecified;
m_tileOffset = { 0,0 };
m_properties.clear();
m_imagePath = "";
m_transparencyColour = { 0, 0, 0, 0 };
m_hasTransparency = false;
m_terrainTypes.clear();
m_tileIndex.clear();
m_tiles.clear();
}
void Tileset::parseOffsetNode(const pugi::xml_node& node)
{
m_tileOffset.x = node.attribute("x").as_int();
m_tileOffset.y = node.attribute("y").as_int();
}
void Tileset::parsePropertyNode(const pugi::xml_node& node)
{
const auto& children = node.children();
for (const auto& child : children)
{
m_properties.emplace_back();
m_properties.back().parse(child);
}
}
void Tileset::parseTerrainNode(const pugi::xml_node& node)
{
const auto& children = node.children();
for (const auto& child : children)
{
std::string name = child.name();
if (name == "terrain")
{
m_terrainTypes.emplace_back();
auto& terrain = m_terrainTypes.back();
terrain.name = child.attribute("name").as_string();
terrain.tileID = child.attribute("tile").as_int();
auto properties = child.child("properties");
if (properties)
{
for (const auto& p : properties)
{
name = p.name();
if (name == "property")
{
terrain.properties.emplace_back();
terrain.properties.back().parse(p);
}
}
}
}
}
}
Tileset::Tile& Tileset::newTile(std::uint32_t ID)
{
Tile& tile = (m_tiles.emplace_back(), m_tiles.back());
if (m_tileIndex.size() <= ID)
{
m_tileIndex.resize(ID + 1, 0);
}
m_tileIndex[ID] = static_cast<std::uint32_t>(m_tiles.size());
tile.ID = ID;
return tile;
}
void Tileset::parseTileNode(const pugi::xml_node& node, Map* map)
{
assert(map);
Tile& tile = newTile(node.attribute("id").as_int());
if (node.attribute("terrain"))
{
std::string data = node.attribute("terrain").as_string();
bool lastWasChar = true;
std::size_t idx = 0u;
for (auto i = 0u; i < data.size() && idx < tile.terrainIndices.size(); ++i)
{
if (isdigit(data[i]))
{
tile.terrainIndices[idx++] = std::atoi(&data[i]);
lastWasChar = false;
}
else
{
if (!lastWasChar)
{
lastWasChar = true;
}
else
{
tile.terrainIndices[idx++] = -1;
lastWasChar = false;
}
}
}
if (lastWasChar)
{
tile.terrainIndices[idx] = -1;
}
}
tile.probability = node.attribute("probability").as_int(100);
tile.className = node.attribute("type").as_string();
if (tile.className.empty())
{
tile.className = node.attribute("class").as_string();
}
//by default we set the tile's values as in an Image tileset
tile.imagePath = m_imagePath;
tile.imageSize = m_tileSize;
if (m_columnCount != 0)
{
std::uint32_t rowIndex = tile.ID % m_columnCount;
std::uint32_t columnIndex = tile.ID / m_columnCount;
tile.imagePosition.x = m_margin + rowIndex * (m_tileSize.x + m_spacing);
tile.imagePosition.y = m_margin + columnIndex * (m_tileSize.y + m_spacing);
}
const auto& children = node.children();
for (const auto& child : children)
{
std::string name = child.name();
if (name == "properties")
{
for (const auto& prop : child.children())
{
tile.properties.emplace_back();
tile.properties.back().parse(prop);
}
}
else if (name == "objectgroup")
{
tile.objectGroup.parse(child, map);
}
else if (name == "image")
{
std::string attribString = child.attribute("source").as_string();
if (attribString.empty())
{
Logger::log("Tile image path missing", Logger::Type::Warning);
continue;
}
tile.imagePath = resolveFilePath(attribString, m_workingDir);
tile.imagePosition = tmx::Vector2u(0, 0);
if (child.attribute("trans"))
{
attribString = child.attribute("trans").as_string();
m_transparencyColour = colourFromString(attribString);
m_hasTransparency = true;
}
if (child.attribute("width"))
{
tile.imageSize.x = child.attribute("width").as_uint();
}
if (child.attribute("height"))
{
tile.imageSize.y = child.attribute("height").as_uint();
}
}
else if (name == "animation")
{
for (const auto& frameNode : child.children())
{
Tile::Animation::Frame frame;
frame.duration = frameNode.attribute("duration").as_int();
frame.tileID = frameNode.attribute("tileid").as_int() + m_firstGID;
tile.animation.frames.push_back(frame);
}
}
}
}
void Tileset::createMissingTile(std::uint32_t ID)
{
//first, we check if the tile does not yet exist
if (m_tileIndex.size() > ID && m_tileIndex[ID])
{
return;
}
Tile& tile = newTile(ID);
tile.imagePath = m_imagePath;
tile.imageSize = m_tileSize;
std::uint32_t rowIndex = ID % m_columnCount;
std::uint32_t columnIndex = ID / m_columnCount;
tile.imagePosition.x = m_margin + rowIndex * (m_tileSize.x + m_spacing);
tile.imagePosition.y = m_margin + columnIndex * (m_tileSize.y + m_spacing);
}

View File

@@ -108,7 +108,8 @@ endif()
set_source_files_properties(${Sources} PROPERTIES LANGUAGE C)
add_library(zstd STATIC ${Sources} ${Headers})
add_library(zstd::static ALIAS zstd)
add_library(Zstd::static ALIAS zstd)
add_library(Zstd::Zstd ALIAS zstd)
add_zstd_compilation_flags(zstd)

View File

@@ -1,5 +1,11 @@
add_executable(tmx2gba
strtools.hpp strtools.cpp
argparse.hpp argparse.cpp
$<$<NOT:$<TARGET_EXISTS:ZLIB::ZLIB>>:gzip.hpp gzip.cpp>
tmxlayer.hpp
tmxobject.hpp
tmxtileset.hpp
tmxmap.hpp tmxmap.cpp
tmxreader.hpp tmxreader.cpp
convert.hpp convert.cpp
headerwriter.hpp headerwriter.cpp
@@ -10,7 +16,12 @@ configure_file(config.h.in config.h @ONLY)
target_sources(tmx2gba PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/config.h)
target_include_directories(tmx2gba PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
set_target_properties(tmx2gba PROPERTIES CXX_STANDARD 20)
set_target_properties(tmx2gba PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON)
target_compile_definitions(${PROJECT_NAME} PRIVATE
$<$<BOOL:${MSVC}>:_CRT_SECURE_NO_WARNINGS>) # Disable msvc warning
# Enable strong warnings
target_compile_options(tmx2gba PRIVATE
@@ -18,7 +29,10 @@ target_compile_options(tmx2gba PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -pedantic>
$<$<CXX_COMPILER_ID:Clang,AppleClang>:-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded>)
target_link_libraries(tmx2gba tmxlite)
target_link_libraries(tmx2gba
pugixml::pugixml base64::base64 Zstd::Zstd
$<$<TARGET_EXISTS:ZLIB::ZLIB>:ZLIB::ZLIB>
$<$<TARGET_EXISTS:miniz::miniz>:miniz::miniz>)
if (TMX2GBA_DKP_INSTALL)
if (DEFINED ENV{DEVKITPRO})

View File

@@ -1,4 +1,5 @@
/* argparse.cpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
#include "argparse.hpp"
#include <optional>
@@ -6,6 +7,7 @@
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <algorithm>
ArgParse::ArgParser::ArgParser(
@@ -23,7 +25,7 @@ void ArgParse::Options::ShowShortUsage(const std::string_view name, std::ostream
out << "Usage: " << name;
for (const auto& it : options)
{
if (it.argumentName)
if (!it.argumentName.empty())
{
// Option with argument
it.required
@@ -55,8 +57,8 @@ void ArgParse::Options::ShowHelpUsage(const std::string_view name, std::ostream&
out << ">" << std::endl;
// Determine the alignment width from the longest argument
auto paramLength = [](const Option& p) -> int { return p.argumentName
? static_cast<int>(std::strlen(p.argumentName) + 3)
auto paramLength = [](const Option& p) -> int { return !p.argumentName.empty()
? static_cast<int>(p.argumentName.length() + 3)
: 1; };
auto longestParam = std::max_element(options.begin(), options.end(),
[=](auto a, auto b) -> bool { return paramLength(a) < paramLength(b); });
@@ -67,7 +69,8 @@ void ArgParse::Options::ShowHelpUsage(const std::string_view name, std::ostream&
{
auto decorateArgument = [=] { return " <" + std::string(it.argumentName) + "> "; };
out << " -" << it.flag
<< std::left << std::setw(alignWidth) << std::setfill('-') << (it.argumentName ? decorateArgument() : " ")
<< std::left << std::setw(alignWidth) << std::setfill('-')
<< (!it.argumentName.empty() ? decorateArgument() : " ")
<< " " << it.helpString << std::endl;
}
out << std::flush;
@@ -97,19 +100,18 @@ ArgParse::ParseCtrl ArgParse::ParserState::Next(const std::string_view token)
{
flagChar = flag.value();
const auto opt = getOption(flagChar);
if (opt.has_value())
if (!opt.has_value())
return ParseCtrl::QUIT_ERR_UNKNOWN;
bool expect = !opt.value().get().argumentName.empty();
if (token.length() <= 2)
{
bool expect = opt.value().get().argumentName != nullptr;
if (token.length() <= 2)
{
expectArg = expect;
if (!expectArg)
return handler(flagChar, "");
}
else
{
return handler(flagChar, expect ? token.substr(2) : "");
}
expectArg = expect;
if (!expectArg)
return handler(flagChar, "");
}
else
{
return handler(flagChar, expect ? token.substr(2) : "");
}
}
else if (!token.empty())

View File

@@ -1,4 +1,5 @@
/* argparse.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
#ifndef ARGPARSE_HPP
#define ARGPARSE_HPP
@@ -16,18 +17,18 @@ namespace ArgParse
{
struct Option
{
const char* argumentName;
const char* helpString;
std::string_view argumentName;
std::string_view helpString;
char flag;
bool required;
static constexpr Option Optional(char flag, const char* name, const char* help)
static constexpr Option Optional(char flag, const std::string_view name, const std::string_view help)
{
return { name, help, flag, false };
}
static constexpr Option Required(char flag, const char* name, const char* help)
static constexpr Option Required(char flag, const std::string_view name, const std::string_view help)
{
return { name, help, flag, false };
return { name, help, flag, true };
}
};

View File

@@ -1,6 +1,10 @@
#ifndef CONFIG_H
#define CONFIG_H
#define TMX2GBA_VERSION "@PROJECT_VERSION@"
#define TMX2GBA_VERSION "@PROJECT_VERSION@"
#define TMX2GBA_DESCRIPTION "@PROJECT_DESCRIPTION@"
#define TMX2GBA_HOMEPAGE "@PROJECT_HOMEPAGE_URL@"
#cmakedefine USE_ZLIB
#endif//CONFIG_H

View File

@@ -1,4 +1,5 @@
/* converter.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#include "convert.hpp"
#include "tmxreader.hpp"
@@ -12,8 +13,7 @@ bool convert::ConvertCharmap(std::vector<uint16_t>& out, int idxOffset, uint32_t
const size_t numTiles = tmx.TileCount();
assert(gfxTiles.size() == numTiles);
if (palTiles.has_value())
assert(palTiles.value().size() == numTiles);
assert(!palTiles.has_value() || palTiles.value().size() == numTiles);
out.reserve(numTiles);
for (size_t i = 0; i < numTiles; ++i)

View File

@@ -1,4 +1,5 @@
/* converter.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
#ifndef CONVERT_HPP
#define CONVERT_HPP

125
src/gzip.cpp Normal file
View File

@@ -0,0 +1,125 @@
// gzip.cpp - portable memory miniz based gzip reader
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
#include "gzip.hpp"
#include <string_view>
GZipReader::GZipReader() noexcept :
mSourceLen(0), mBytesRead(0),
mModificationTime(0), mCrc(0), mInputSize(0), mComputedCrc(0),
crc16(0), mFlags(0), mXflags(0), mOsId(0)
{
tinfl_init(&mState);
mComputedCrc = static_cast<uint32_t>(mz_crc32(0, nullptr, 0));
}
bool GZipReader::OpenMemory(const std::span<const uint8_t> source) noexcept
{
if (source.size() < 20)
return false;
auto it = std::cbegin(source), end = std::cend(source);
constexpr uint8_t magic[2] = { 0x1F, 0x8B };
if (*it++ != magic[0] || *it++ != magic[1])
return false;
constexpr uint8_t CM_DEFLATE = 8;
uint8_t compression = *it++;
if (compression != CM_DEFLATE)
return false;
mFlags = *it++;
mModificationTime = *it++;
mModificationTime |= *it++ << 8;
mModificationTime |= *it++ << 16;
mModificationTime |= *it++ << 24;
mXflags = *it++;
mOsId = *it++;
if (mFlags & FEXTRA)
{
// Skip "extra" field
if (it + 2 >= end)
return false;
uint16_t extraLen = *it++;
extraLen = *it++ << 8;
if (it + extraLen >= end)
return false;
it += extraLen;
}
if (mFlags & FNAME)
{
// Skip null-terminated name string
do
{
if (++it == end)
return false;
} while (*it != '\0');
if (++it == end)
return false;
}
if (mFlags & FCOMMENT)
{
// Skip null-terminated comment string
do
{
if (++it == end)
return false;
} while (*it != '\0');
if (++it == end)
return false;
}
if (mFlags & FHCRC)
{
if (it + 2 >= end)
return false;
crc16 = *it++;
crc16 |= *it++;
}
mIt = it;
mSourceLen = end - it - 8;
it += mSourceLen;
mCrc = *it++;
mCrc |= *it++ << 8;
mCrc |= *it++ << 16;
mCrc |= *it++ << 24;
mInputSize = *it++;
mInputSize |= *it++ << 8;
mInputSize |= *it++ << 16;
mInputSize |= *it++ << 24;
return true;
}
bool GZipReader::Read(std::span<uint8_t> out) noexcept
{
size_t outLen = out.size();
auto res = tinfl_decompress(&mState,
static_cast<const mz_uint8*>(&*mIt), &mSourceLen,
static_cast<mz_uint8*>(out.data()), static_cast<mz_uint8*>(out.data()), &outLen,
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
if (res != TINFL_STATUS_DONE)
return false;
mIt += outLen;
mBytesRead += outLen;
mComputedCrc = static_cast<uint32_t>(mz_crc32(static_cast<mz_ulong>(mComputedCrc), out.data(), outLen));
return true;
}
bool GZipReader::Check() const noexcept
{
if (mComputedCrc != mCrc)
return false;
if (static_cast<uint32_t>(mBytesRead & UINT32_MAX) != mInputSize)
return false;
return true;
}

39
src/gzip.hpp Normal file
View File

@@ -0,0 +1,39 @@
// gzip.hpp - portable memory miniz based gzip reader
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
#ifndef GZIP_HPP
#define GZIP_HPP
#include "miniz.h"
#include <cstdint>
#include <span>
class GZipReader
{
static constexpr uint8_t
FTEXT = 1, FHCRC = 1<<1, FEXTRA = 1<<2, FNAME = 1<<3, FCOMMENT = 1<<4;
static constexpr uint8_t XFL_BEST = 2, XFL_FASTEST = 4;
tinfl_decompressor mState;
std::span<const uint8_t>::iterator mIt;
size_t mSourceLen, mBytesRead;
uint32_t mModificationTime, mCrc, mInputSize, mComputedCrc;
uint16_t crc16;
uint8_t mFlags, mXflags, mOsId;
public:
GZipReader() noexcept;
constexpr size_t SourceLength() const noexcept { return mSourceLen; }
constexpr uint32_t OutputLength() const noexcept { return mInputSize; }
bool OpenMemory(const std::span<const uint8_t> source) noexcept;
bool Read(std::span<uint8_t> out) noexcept;
bool Check() const noexcept;
};
#endif//GZIP_HPP

View File

@@ -1,4 +1,5 @@
/* headerwriter.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#include "headerwriter.hpp"
#include <algorithm>
@@ -9,7 +10,7 @@ template <> constexpr std::string_view DatType<uint8_t>() { return "unsigned cha
template <> constexpr std::string_view DatType<uint16_t>() { return "unsigned short"; }
template <> constexpr std::string_view DatType<uint32_t>() { return "unsigned int"; }
void HeaderWriter::WriteSize(unsigned width, unsigned height)
void HeaderWriter::WriteSize(int width, int height)
{
stream << std::endl;
WriteDefine(mName + "Width", width);

View File

@@ -1,4 +1,5 @@
/* headerwriter.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#ifndef HEADERWRITER_HPP
#define HEADERWRITER_HPP
@@ -37,7 +38,7 @@ public:
WriteDefine(name, std::to_string(value));
}
void WriteSize(unsigned width, unsigned height);
void WriteSize(int width, int height);
void WriteCharacterMap(const std::span<uint16_t> charData);
void WriteCollision(const std::span<uint8_t> collisionData);
void WriteObjects(const std::span<uint32_t> objData);

37
src/strtools.cpp Normal file
View File

@@ -0,0 +1,37 @@
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#include "strtools.hpp"
#include <cctype>
#include <algorithm>
const std::string_view TrimWhitespace(const std::string_view str)
{
auto beg = std::find_if_not(str.begin(), str.end(), ::isspace);
if (beg == std::end(str))
{
return {};
}
auto end = std::find_if_not(str.rbegin(), str.rend(), ::isspace);
auto begOff = std::distance(str.begin(), beg);
auto endOff = std::distance(end, str.rend()) - begOff;
using size_type = std::string::size_type;
return str.substr(static_cast<size_type>(begOff), static_cast<size_type>(endOff));
}
std::string SanitiseLabel(const std::string_view ident)
{
std::string out;
out.reserve(ident.length());
int last = '_';
for (int i : ident)
{
if (out.empty() && std::isdigit(i)) { continue; }
if (!std::isalnum(i)) { i = '_'; }
if (i != '_' || last != '_') { out.push_back(i); }
last = i;
}
return out;
}

113
src/strtools.hpp Normal file
View File

@@ -0,0 +1,113 @@
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
#ifndef STRTOOLS_HPP
#define STRTOOLS_HPP
#include <string>
#include <string_view>
#include <cstdint>
// Cut leading & trailing whitespace (including newlines)
[[nodiscard]] const std::string_view TrimWhitespace(const std::string_view str);
// Convert string to valid C identifier
[[nodiscard]] std::string SanitiseLabel(const std::string_view ident);
#include <ostream>
// Template functions for converting unsigned ints to C/GNU style hex
static inline constexpr char CHexU(uint8_t h) { return "0123456789ABCDEF"[h >> 4]; }
static inline constexpr char CHexL(uint8_t l) { return "0123456789ABCDEF"[l & 15]; }
template <typename T> static void CHex(std::ostream& s, T x);
template <> void CHex(std::ostream& s, uint8_t x)
{
if (x > 9) s << "0x";
if (x > 15) s << CHexU(x);
s << CHexL(x);
}
template <> void CHex(std::ostream& s, uint16_t x)
{
if (x > 9) s << "0x";
if (x > 4095) s << CHexU(static_cast<uint8_t>(x >> 8));
if (x > 255) s << CHexL(static_cast<uint8_t>(x >> 8));
if (x > 15) s << CHexU(static_cast<uint8_t>(x));
s << CHexL(static_cast<uint8_t>(x));
}
template <> void CHex(std::ostream& s, uint32_t x)
{
if (x > 9) s << "0x";
if (x > 0xFFFFFFF) s << CHexU(static_cast<uint8_t>(x >> 24));
if (x > 0xFFFFFF) s << CHexL(static_cast<uint8_t>(x >> 24));
if (x > 0xFFFFF) s << CHexU(static_cast<uint8_t>(x >> 16));
if (x > 65535) s << CHexL(static_cast<uint8_t>(x >> 16));
if (x > 4095) s << CHexU(static_cast<uint8_t>(x >> 8));
if (x > 255) s << CHexL(static_cast<uint8_t>(x >> 8));
if (x > 15) s << CHexU(static_cast<uint8_t>(x));
s << CHexL(static_cast<uint8_t>(x));
}
#include <limits>
#include <cstdlib>
#include <optional>
// Templated string to int/float w/ exception-less error handling
template <typename T>
[[nodiscard]] static std::optional<T> IntFromStr(const char* str, int base = 0) noexcept
{
using std::numeric_limits;
errno = 0;
char* end = nullptr;
long res = std::strtol(str, &end, base);
if (errno == ERANGE) { return std::nullopt; }
if (str == end) { return std::nullopt; }
if constexpr (sizeof(long) > sizeof(T))
{
if (res > numeric_limits<T>::max() || res < numeric_limits<T>::min())
return std::nullopt;
}
return static_cast<T>(res);
}
template <typename T>
[[nodiscard]] static std::optional<T> UintFromStr(const char* str, int base = 0) noexcept
{
using std::numeric_limits;
char* end = nullptr;
errno = 0;
unsigned long res = std::strtoul(str, &end, base);
if (errno == ERANGE) { return std::nullopt; }
if (str == end) { return std::nullopt; }
if constexpr (numeric_limits<unsigned long>::max() > numeric_limits<T>::max())
{
if (res > numeric_limits<T>::max()) { return std::nullopt; }
}
return static_cast<T>(res);
}
template <typename T>
[[nodiscard]] static std::optional<T> FloatFromStr(const char* str) noexcept
{
char* end = nullptr;
T res;
errno = 0;
if constexpr (std::is_same_v<T, float>)
res = std::strtof(str, &end);
else
res = static_cast<T>(std::strtod(str, &end));
if (errno == ERANGE) { return std::nullopt; }
if (str == end) { return std::nullopt; }
return res;
}
#endif//STRTOOLS_HPP

View File

@@ -1,82 +1,11 @@
/* swwriter.cpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
#include "swriter.hpp"
#include "strtools.hpp"
#include <type_traits>
#include <limits>
#include <assert.h>
#define GNU_STYLE 0
#define MASM_STYLE 1
#define HEX_STYLE GNU_STYLE
static inline constexpr char HexU(uint8_t h) { return "0123456789ABCDEF"[h >> 4]; }
static inline constexpr char HexL(uint8_t l) { return "0123456789ABCDEF"[l & 15]; }
#if HEX_STYLE == GNU_STYLE
template <typename T> static void CHex(std::ostream& s, T x);
template <> void CHex(std::ostream& s, uint8_t x)
{
if (x > 9) s << "0x";
if (x > 15) s << HexU(x);
s << HexL(x);
}
template <> void CHex(std::ostream& s, uint16_t x)
{
if (x > 9) s << "0x";
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8));
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8));
if (x > 15) s << HexU(static_cast<uint8_t>(x));
s << HexL(static_cast<uint8_t>(x));
}
template <> void CHex(std::ostream& s, uint32_t x)
{
if (x > 9) s << "0x";
if (x > 0xFFFFFFF) s << HexU(static_cast<uint8_t>(x >> 24));
if (x > 0xFFFFFF) s << HexL(static_cast<uint8_t>(x >> 24));
if (x > 0xFFFFF) s << HexU(static_cast<uint8_t>(x >> 16));
if (x > 65535) s << HexL(static_cast<uint8_t>(x >> 16));
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8));
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8));
if (x > 15) s << HexU(static_cast<uint8_t>(x));
s << HexL(static_cast<uint8_t>(x));
}
#elif HEX_STYLE == MASM_STYLE
template <typename T> static void MHex(std::ostream& s, T x);
template <> void MHex(std::ostream& s, uint8_t x)
{
if (x > 159) s << "0";
if (x > 15) s << HexU(x); else if (x > 9) s << "0";
s << HexL(x);
if (x > 9) s << "h";
}
template <> void MHex(std::ostream& s, uint16_t x)
{
if (x > 40959) s << "0";
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8)); else if (x > 2559) s << "0";
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8)); else if (x > 159) s << "0";
if (x > 15) s << HexU(static_cast<uint8_t>(x)); else if (x > 9) s << "0";
s << HexL(static_cast<uint8_t>(x));
if (x > 9) s << "h";
}
template <> void MHex(std::ostream& s, uint32_t x)
{
if (x > 0x9FFFFFFF) s << "0";
if (x > 0xFFFFFFF) s << HexU(static_cast<uint8_t>(x >> 24)); else if (x > 0x9FFFFFF) s << "0";
if (x > 0xFFFFFF) s << HexL(static_cast<uint8_t>(x >> 24)); else if (x > 0x9FFFFF) s << "0";
if (x > 0xFFFFF) s << HexU(static_cast<uint8_t>(x >> 16)); else if (x > 655359) s << "0";
if (x > 65535) s << HexL(static_cast<uint8_t>(x >> 16)); else if (x > 40959) s << "0";
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8)); else if (x > 2559) s << "0";
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8)); else if (x > 159) s << "0";
if (x > 15) s << HexU(static_cast<uint8_t>(x)); else if (x > 9) s << "0";
s << HexL(static_cast<uint8_t>(x));
if (x > 9) s << "h";
}
#else
# error "Unknown hex style"
#endif
template <typename T> static constexpr const std::string_view DataType();
template <> constexpr const std::string_view DataType<uint8_t>() { return ".byte"; }
@@ -95,11 +24,7 @@ static void WriteArrayDetail(std::ostream& s, const I beg, const I end, int perC
s << "\t" << DataType<Element>() << " ";
const Element e = *it;
#if HEX_STYLE == MASM_STYLE
MHex(s, e);
#elif HEX_STYLE == GNU_STYLE
CHex(s, e);
#endif
if (++it == end)
break;

View File

@@ -1,4 +1,5 @@
/* swwriter.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2024 a dinosaur
#ifndef SWRITER_HPP
#define SWRITER_HPP

View File

@@ -1,10 +1,16 @@
/* tmx2gba.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
// tmx2gba.cpp - main entry point
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#include <string_view>
constexpr std::string_view copyrightStr("(c) 2015-2024 a dinosaur");
#include "argparse.hpp"
#include "tmxreader.hpp"
#include "convert.hpp"
#include "headerwriter.hpp"
#include "swriter.hpp"
#include "strtools.hpp"
#include "config.h"
#include <iostream>
#include <map>
@@ -19,15 +25,14 @@ struct Arguments
int offset = 0;
int palette = 0;
std::vector<std::string> objMappings;
bool help = false, showVersion = false;
bool help = false;
};
using ArgParse::Option;
static const ArgParse::Options options =
{
Option::Optional('h', nullptr, "Display this help & command info"),
Option::Optional('v', nullptr, "Display version & quit"),
Option::Optional('h', {}, "Display help & command info"),
Option::Optional('l', "name", "Name of layer to use (default first layer in TMX)"),
Option::Optional('y', "name", "Layer for palette mappings"),
Option::Optional('c', "name", "Output a separate 8bit collision map of the specified layer"),
@@ -51,7 +56,6 @@ static bool ParseArgs(int argc, char** argv, Arguments& params)
switch (opt)
{
case 'h': params.help = true; return ParseCtrl::QUIT_EARLY;
case 'v': params.showVersion = true; return ParseCtrl::QUIT_EARLY;
case 'l': params.layer = arg; return ParseCtrl::CONTINUE;
case 'c': params.collisionlay = arg; return ParseCtrl::CONTINUE;
case 'y': params.paletteLay = arg; return ParseCtrl::CONTINUE;
@@ -69,11 +73,8 @@ static bool ParseArgs(int argc, char** argv, Arguments& params)
catch (std::out_of_range const&) { return ParseCtrl::QUIT_ERR_RANGE; }
});
if (!parser.Parse(std::span(argv + 1, argc - 1)))
return false;
if (params.help || params.showVersion)
return true;
if (!parser.Parse(std::span(argv + 1, argc - 1))) { return false; }
if (params.help) { return true; }
if (!params.flagFile.empty())
{
@@ -115,41 +116,16 @@ static bool ParseArgs(int argc, char** argv, Arguments& params)
return true;
}
static std::string SanitiseLabel(const std::string_view ident)
{
std::string out;
out.reserve(ident.length());
int last = '_';
for (int i : ident)
{
if (out.empty() && std::isdigit(i))
continue;
if (!std::isalnum(i))
i = '_';
if (i != '_' || last != '_')
out.push_back(i);
last = i;
}
return out;
}
int main(int argc, char** argv)
{
Arguments p;
if (!ParseArgs(argc, argv, p))
return 1;
if (!ParseArgs(argc, argv, p)) { return 1; }
if (p.help)
{
std::cout << "tmx2gba v" << TMX2GBA_VERSION << ", " << copyrightStr << std::endl;
options.ShowHelpUsage(argv[0], std::cout);
return 0;
}
if (p.showVersion)
{
std::cout << "tmx2gba version " << TMX2GBA_VERSION << ", (c) 2015-2024 a dinosaur" << std::endl;
return 0;
}
// Object mappings
std::map<std::string, uint32_t> objMapping;
@@ -222,8 +198,7 @@ int main(int argc, char** argv)
// Convert to GBA-friendly charmap data
{
std::vector<uint16_t> charDat;
if (!convert::ConvertCharmap(charDat, p.offset, p.palette, tmx))
return 1;
if (!convert::ConvertCharmap(charDat, p.offset, p.palette, tmx)) { return 1; }
// Write out charmap
outH.WriteSize(tmx.GetSize().width, tmx.GetSize().height);
@@ -235,8 +210,7 @@ int main(int argc, char** argv)
if (tmx.HasCollisionTiles())
{
std::vector<uint8_t> collisionDat;
if (!convert::ConvertCollision(collisionDat, tmx))
return 1;
if (!convert::ConvertCollision(collisionDat, tmx)) { return 1; }
outH.WriteCollision(collisionDat);
outS.WriteArray("Collision", collisionDat, 32);
@@ -245,8 +219,7 @@ int main(int argc, char** argv)
if (tmx.HasObjects())
{
std::vector<uint32_t> objDat;
if (!convert::ConvertObjects(objDat, tmx))
return 1;
if (!convert::ConvertObjects(objDat, tmx)) { return 1; }
outH.WriteObjects(objDat);
outS.WriteArray("Objdat", objDat);

34
src/tmxlayer.hpp Normal file
View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#ifndef TMXLAYER_HPP
#define TMXLAYER_HPP
#include <vector>
#include <span>
#include <string>
#include <string_view>
#include <cstdint>
#include <utility>
class TmxLayer
{
std::string mName;
int mWidth, mHeight;
std::vector<uint32_t> mTileDat;
public:
static constexpr uint32_t FLIP_HORZ = 0x80000000;
static constexpr uint32_t FLIP_VERT = 0x40000000;
static constexpr uint32_t FLIP_DIAG = 0x20000000;
static constexpr uint32_t FLIP_MASK = 0xE0000000;
TmxLayer(int width, int height, const std::string_view name, std::vector<uint32_t>&& tileDat) noexcept
: mName(name), mWidth(width), mHeight(height), mTileDat(std::move(tileDat)) {}
[[nodiscard]] const std::string_view Name() const noexcept { return mName; }
[[nodiscard]] constexpr std::pair<int, int> TileCount() const noexcept { return { mWidth, mHeight }; }
[[nodiscard]] constexpr const std::span<const uint32_t> Tiles() const noexcept { return mTileDat; }
};
#endif//TMXLAYER_HPP

236
src/tmxmap.cpp Normal file
View File

@@ -0,0 +1,236 @@
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#include "tmxmap.hpp"
#include "strtools.hpp"
#include "config.h"
#include <pugixml.hpp>
#include <base64.h>
#ifdef USE_ZLIB
# include <zlib.h>
#else
# include "gzip.hpp"
#endif
#include <zstd.h>
#include <cerrno>
#include <algorithm>
enum class Encoding { XML, BASE64, CSV, INVALID };
enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID };
[[nodiscard]] static Encoding EncodingFromStr(const std::string_view str)
{
if (str.empty()) { return Encoding::XML; }
if (str == "base64") { return Encoding::BASE64; }
if (str == "csv") { return Encoding::CSV; }
return Encoding::INVALID;
}
[[nodiscard]] static Compression CompressionFromStr(const std::string_view str)
{
if (str.empty()) { return Compression::NONE; }
if (str == "gzip") { return Compression::GZIP; }
if (str == "zlib") { return Compression::ZLIB; }
if (str == "zstd") { return Compression::ZSTD; }
return Compression::INVALID;
}
[[nodiscard]] static bool DecodeBase64(
std::vector<uint32_t>& out, size_t numTiles,
const std::string_view base64, Compression compression)
{
auto decoded = base64_decode(TrimWhitespace(base64));
if (decoded.empty()) { return false; }
const std::span source(reinterpret_cast<const uint8_t*>(decoded.data()), decoded.size());
//FIXME: lmao what is big endian
switch (compression)
{
case Compression::GZIP:
#ifndef USE_ZLIB
{
out.resize(numTiles);
GZipReader reader;
if (!reader.OpenMemory(source) ||
!reader.Read({ reinterpret_cast<uint8_t*>(out.data()), sizeof(uint32_t) * numTiles }) ||
!reader.Check())
return false;
return true;
}
#endif
case Compression::ZLIB:
{
out.resize(numTiles);
// Decompress gzip/zlib data with zlib/zlib data miniz
z_stream s =
{
.next_in = const_cast<Bytef*>(source.data()),
.avail_in = static_cast<unsigned int>(source.size()),
.next_out = reinterpret_cast<Bytef*>(out.data()),
.avail_out = static_cast<unsigned int>(sizeof(uint32_t) * numTiles),
.zalloc = nullptr, .zfree = nullptr, .opaque = nullptr
};
#ifdef USE_ZLIB
const int wbits = (compression == Compression::GZIP) ? MAX_WBITS | 16 : MAX_WBITS;
#else
const int wbits = MZ_DEFAULT_WINDOW_BITS;
#endif
if (inflateInit2(&s, wbits) != Z_OK)
return false;
int res = inflate(&s, Z_FINISH);
inflateEnd(&s);
return res == Z_STREAM_END;
}
case Compression::ZSTD:
{
out.resize(numTiles);
auto res = ZSTD_decompress(
reinterpret_cast<void*>(out.data()),
sizeof(uint32_t) * numTiles,
source.data(), source.size());
return !ZSTD_isError(res);
}
case Compression::NONE:
{
out.reserve(numTiles);
const auto end = source.end();
for (auto it = source.begin(); it < end - 3;)
{
uint32_t tile = *it++;
tile |= static_cast<uint32_t>(*it++) << 8u;
tile |= static_cast<uint32_t>(*it++) << 16u;
tile |= static_cast<uint32_t>(*it++) << 24u;
out.emplace_back(tile);
}
return true;
}
case Compression::INVALID:
default: return false;
}
}
void TmxMap::ReadTileset(const pugi::xml_node& xNode)
{
std::string_view name = xNode.attribute("name").value();
std::string_view source = xNode.attribute("source").value();
auto firstGid = UintFromStr<uint32_t>(xNode.attribute("firstgid").value()).value_or(0);
auto numTiles = UintFromStr<uint32_t>(xNode.attribute("tilecount").value()).value_or(0);
if (numTiles == 0)
return; // FIXME: warn about empty tilesets or something
mTilesets.emplace_back(TmxTileset(name, source, firstGid, numTiles));
}
void TmxMap::ReadLayer(const pugi::xml_node& xNode)
{
std::string_view name = xNode.attribute("name").value();
// Read layer size
int width = IntFromStr<int>(xNode.attribute("width").value()).value_or(0);
int height = IntFromStr<int>(xNode.attribute("height").value()).value_or(0);
if (width <= 0 || height <= 0) { return; }
const auto numTiles = static_cast<size_t>(width) * static_cast<size_t>(height);
auto xData = xNode.child("data");
if (xData.empty() || xData.first_child().empty())
return;
// Read data
std::vector<uint32_t> tileDat;
auto encoding = EncodingFromStr(xData.attribute("encoding").value());
if (encoding == Encoding::BASE64)
{
const std::string_view base64(xData.child_value());
if (base64.empty())
return;
const auto compression = CompressionFromStr(xData.attribute("compression").value());
if (compression == Compression::INVALID || !DecodeBase64(tileDat, numTiles, base64, compression))
return;
}
else if (encoding == Encoding::XML)
{
tileDat.reserve(numTiles);
std::ranges::transform(xData.children("tile"), std::back_inserter(tileDat), [](auto it)
-> uint32_t { return UintFromStr<uint32_t>(it.attribute("gid").value()).value_or(0); });
}
else if (encoding == Encoding::CSV)
{
tileDat.reserve(numTiles);
const std::string_view csv(xData.child_value());
std::string::size_type pos = 0;
while (true)
{
// TODO: check if this has a problem on other locales?
auto gid = UintFromStr<uint32_t>(csv.substr(pos).data());
if (gid.has_value())
tileDat.emplace_back(gid.value());
if ((pos = csv.find(',', pos)) == std::string::npos)
break;
++pos;
}
}
else { return; }
mLayers.emplace_back(TmxLayer(width, height, name, std::move(tileDat)));
}
void TmxMap::ReadObjectGroup(const pugi::xml_node& xNode)
{
std::string_view name(xNode.value());
std::vector<TmxObject> objects;
const auto xObjects = xNode.children("object");
//mObjects.reserve(xObjects.size())
for (const auto it : xObjects)
{
int id = IntFromStr<int>(it.attribute("id").value()).value_or(0);
std::string_view name = it.attribute("name").value();
// Read axis-aligned bounding box
auto x = FloatFromStr<float>(it.attribute("x").value()).value_or(0.0f);
auto y = FloatFromStr<float>(it.attribute("y").value()).value_or(0.0f);
auto width = FloatFromStr<float>(it.attribute("width").value()).value_or(0.0f);
auto height = FloatFromStr<float>(it.attribute("height").value()).value_or(0.0f);
objects.emplace_back(TmxObject(id, name, { x, y, width, height }));
}
if (objects.empty())
return; //FIXME: log this
mObjectGroups.emplace_back(TmxObjectGroup(name, std::move(objects)));
}
bool TmxMap::Load(const std::string& inPath)
{
// Parse document
pugi::xml_document xDoc;
auto res = xDoc.load_file(inPath.c_str());
if (res.status != pugi::xml_parse_status::status_ok)
return false;
// Get map node
auto xMap = xDoc.child("map");
if (xMap.empty())
return false;
// Read map attribs
mWidth = IntFromStr<int>(xMap.attribute("width").value()).value_or(0);
mHeight = IntFromStr<int>(xMap.attribute("height").value()).value_or(0);
// Read nodes
for (auto it : xMap.children())
{
std::string_view name(it.name());
if (!name.compare("layer")) { ReadLayer(it); }
else if (!name.compare("tileset")) { ReadTileset(it); }
else if (!name.compare("objectgroup")) { ReadObjectGroup(it); }
}
return true;
}

38
src/tmxmap.hpp Normal file
View File

@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#ifndef TMXMAP_HPP
#define TMXMAP_HPP
#include "tmxtileset.hpp"
#include "tmxobject.hpp"
#include "tmxlayer.hpp"
#include <vector>
#include <span>
#include <string>
#include <string_view>
namespace pugi { class xml_node; }
class TmxMap
{
int mWidth = 0, mHeight = 0;
std::vector<TmxLayer> mLayers;
std::vector<TmxTileset> mTilesets;
std::vector<TmxObjectGroup> mObjectGroups;
void ReadTileset(const pugi::xml_node& xNode);
void ReadLayer(const pugi::xml_node& xNode);
void ReadObjectGroup(const pugi::xml_node& xNode);
public:
[[nodiscard]] bool Load(const std::string& inPath);
[[nodiscard]] constexpr std::pair<int, int> TileCount() const noexcept { return { mWidth, mHeight }; }
[[nodiscard]] constexpr const std::vector<TmxTileset>& Tilesets() const noexcept { return mTilesets; }
[[nodiscard]] constexpr const std::vector<TmxLayer>& Layers() const noexcept { return mLayers; }
[[nodiscard]] constexpr const std::vector<TmxObjectGroup>& ObjectGroups() const noexcept { return mObjectGroups; }
};
#endif//TMXMAP_HPP

43
src/tmxobject.hpp Normal file
View File

@@ -0,0 +1,43 @@
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#ifndef TMXOBJECT_HPP
#define TMXOBJECT_HPP
#include <string>
#include <string_view>
#include <vector>
#include <utility>
class TmxObject
{
public:
template <typename T>
struct AABB { T x, y, w, h; };
TmxObject(int id, std::string_view name, AABB<float>&& box) : mId(id), mName(name), mBox(std::move(box)) {}
constexpr int Id() const noexcept { return mId; }
const std::string_view Name() const noexcept { return mName; }
constexpr const AABB<float>& Box() const noexcept { return mBox; }
private:
int mId;
std::string mName;
AABB<float> mBox;
};
class TmxObjectGroup
{
std::string mName;
std::vector<TmxObject> mObjects;
public:
TmxObjectGroup(std::string_view name, std::vector<TmxObject>&& objects)
: mName(name), mObjects(std::move(objects)) {}
const std::string_view Name() const noexcept { return mName; }
constexpr const std::vector<TmxObject>& Objects() const noexcept { return mObjects; }
};
#endif//TMXOBJECT_HPP

View File

@@ -1,11 +1,11 @@
/* tmxreader.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#include "tmxreader.hpp"
#include "tmxlite/Map.hpp"
#include "tmxlite/TileLayer.hpp"
#include "tmxlite/ObjectGroup.hpp"
#include "tmxmap.hpp"
#include <optional>
#include <algorithm>
#include <ranges>
TmxReader::Error TmxReader::Open(const std::string& inPath,
@@ -14,115 +14,96 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
const std::string_view collisionName,
const std::map<std::string, uint32_t>& objMapping)
{
tmx::Map map;
if (!map.load(inPath))
TmxMap map;
if (!map.Load(inPath))
return Error::LOAD_FAILED;
using tmx::TileLayer;
using tmx::ObjectGroup;
using std::optional;
using std::reference_wrapper;
optional<reference_wrapper<const TileLayer>> layerGfx;
optional<reference_wrapper<const TileLayer>> layerCls;
optional<reference_wrapper<const TileLayer>> layerPal;
std::vector<reference_wrapper<const ObjectGroup>> objGroups;
optional<reference_wrapper<const TmxLayer>> layerGfx, layerCls, layerPal;
// Read layers
for (const auto& layer : map.getLayers())
for (const auto& layer : map.Layers())
{
auto name = layer->getName();
if (layer->getType() == tmx::Layer::Type::Tile)
{
const auto& tileLayer = layer->getLayerAs<TileLayer>();
// tmxlite unfortunately has no error reporting when a layer fails to load,
// empty check will suffice for the time being
if (tileLayer.getTiles().empty())
continue;
auto name = layer.Name();
//FIXME: no error reporting when a layer fails to load
if (layer.Tiles().empty())
continue;
if (layerGfx == std::nullopt && (graphicsName.empty() || name == graphicsName))
layerGfx = tileLayer;
if (!collisionName.empty() && layerCls == std::nullopt && name == collisionName)
layerCls = tileLayer;
if (!paletteName.empty() && layerPal == std::nullopt && name == paletteName)
layerPal = tileLayer;
}
else if (!objMapping.empty() && layer->getType() == tmx::Layer::Type::Object)
{
objGroups.emplace_back(layer->getLayerAs<ObjectGroup>());
}
if (!layerGfx.has_value() && (graphicsName.empty() || name == graphicsName)) { layerGfx = layer; }
if (!collisionName.empty() && !layerCls.has_value() && name == collisionName) { layerCls = layer; }
if (!paletteName.empty() && !layerPal.has_value() && name == paletteName) { layerPal = layer; }
}
// Check layers
if (layerGfx == std::nullopt)
{
if (graphicsName.empty())
return Error::NO_LAYERS;
else
return Error::GRAPHICS_NOTFOUND;
}
if (layerCls == std::nullopt && !collisionName.empty())
if (!layerGfx.has_value())
return graphicsName.empty()
? Error::NO_LAYERS
: Error::GRAPHICS_NOTFOUND;
if (!layerCls.has_value() && !collisionName.empty())
return Error::GRAPHICS_NOTFOUND;
if (layerPal == std::nullopt && !paletteName.empty())
if (!layerPal.has_value() && !paletteName.empty())
return Error::PALETTE_NOTFOUND;
// Read TMX map
mSize = Size { map.getTileCount().x, map.getTileCount().y };
mSize = Size{ map.TileCount().first, map.TileCount().second };
size_t numTiles = static_cast<size_t>(mSize.width) * static_cast<size_t>(mSize.height);
// Read graphics layer
mGraphics.reserve(numTiles);
for (auto tmxTile : layerGfx.value().get().getTiles())
mGraphics.emplace_back(Tile { tmxTile.ID, tmxTile.flipFlags });
for (auto tmxTile : layerGfx.value().get().Tiles())
mGraphics.emplace_back(Tile{ tmxTile & ~TmxLayer::FLIP_MASK, static_cast<uint8_t>((tmxTile & TmxLayer::FLIP_MASK) >> 28) });
// Read optional layers
if (layerPal.has_value())
{
std::vector<uint32_t> v;
v.reserve(numTiles);
for (auto tmxTile : layerPal.value().get().getTiles())
v.emplace_back(tmxTile.ID);
for (auto tmxTile : layerPal.value().get().Tiles())
v.emplace_back(tmxTile & ~TmxLayer::FLIP_MASK);
mPalette.emplace(v);
}
if (layerCls.has_value())
{
std::vector<uint32_t> v;
v.reserve(numTiles);
for (auto tmxTile : layerCls.value().get().getTiles())
v.emplace_back(tmxTile.ID);
for (auto tmxTile : layerCls.value().get().Tiles())
v.emplace_back(tmxTile & ~TmxLayer::FLIP_MASK);
mCollision.emplace(v);
}
// Read tilesets
const auto& tilesets = map.getTilesets();
const auto& tilesets = map.Tilesets();
mGidTable.reserve(tilesets.size());
for (const auto& set : tilesets)
mGidTable.emplace_back(std::make_pair(set.getFirstGID(), set.getLastGID()));
std::ranges::transform(tilesets, std::back_inserter(mGidTable),
[](const auto& it) { return it.GidRange(); });
// Read objects
if (!objMapping.empty())
if (!map.ObjectGroups().empty())
{
std::vector<Object> v;
for (const auto& group : objGroups)
std::vector<Object> objs;
for (const auto& group : map.ObjectGroups())
{
const auto& tmxObjects = group.get().getObjects();
v.reserve(v.size() + tmxObjects.size());
const auto& tmxObjects = group.Objects();
objs.reserve(objs.size() + tmxObjects.size());
for (const auto& tmxObj : tmxObjects)
{
auto it = objMapping.find(tmxObj.getName());
auto it = objMapping.find(std::string(tmxObj.Name()));
if (it == objMapping.end())
continue;
const auto& aabb = tmxObj.getAABB();
Object obj;
obj.id = it->second;
obj.x = aabb.left;
obj.y = aabb.top;
v.emplace_back(obj);
const auto& aabb = tmxObj.Box();
objs.emplace_back(Object
{
.id = it->second,
.x = aabb.x,
.y = aabb.y
});
}
}
mObjects.emplace(v);
if (!objs.empty())
mObjects.emplace(objs);
}
return Error::OK;

View File

@@ -1,4 +1,5 @@
/* tmxreader.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#ifndef TMXREADER_HPP
#define TMXREADER_HPP
@@ -34,7 +35,7 @@ public:
const std::string_view paletteName,
const std::string_view collisionName,
const std::map<std::string, uint32_t>& objMapping);
struct Size { unsigned width, height; };
struct Size { int width, height; };
[[nodiscard]] constexpr Size GetSize() const { return mSize; }
[[nodiscard]] constexpr size_t TileCount() const { return

26
src/tmxtileset.hpp Normal file
View File

@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#ifndef TMXTILESET_HPP
#define TMXTILESET_HPP
#include <string>
#include <string_view>
#include <cstdint>
class TmxTileset
{
std::string mName, mSource;
uint32_t mFirstGid = 0, mTileCount = 0;
public:
TmxTileset(const std::string_view name, const std::string_view source, uint32_t firstGid, uint32_t tileCount)
: mName(name), mSource(source), mFirstGid(firstGid), mTileCount(tileCount) {}
[[nodiscard]] const std::string_view Name() const noexcept { return mName; }
[[nodiscard]] const std::string_view Source() const noexcept { return mSource; }
[[nodiscard]] constexpr const std::pair<uint32_t, uint32_t> GidRange() const noexcept
{ return { mFirstGid, mFirstGid + mTileCount - 1 }; }
};
#endif//TMXTILESET_HPP