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

14 Commits

25 changed files with 365 additions and 267 deletions

View File

@@ -54,4 +54,4 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
name: ${{env.ARTIFACT_NAME}}-${{matrix.config.artifact}} 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_ZLIB "Use zlib instead of bundled miniz" "${UNIX}")
option(USE_BUNDLED_PUGIXML "Use bundled PUGIXML" ON) option(USE_BUNDLED_PUGIXML "Use bundled PUGIXML" ON)
option(USE_BUNDLED_ZSTD "Use bundled libzstd" 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) option(TMX2GBA_DKP_INSTALL "Install into DEVKITPRO prefix" OFF)

View File

@@ -10,13 +10,12 @@ tmx2gba is a simple command line utility that converts [Tiled](http://www.mapedi
## Usage ## ## Usage ##
``` ```
tmx2gba [-hv] [-r offset] [-lyc name] [-p 0-15] [-m name;id] <-i inpath> <-o outpath> tmx2gba [-h] [-r offset] [-lyc name] [-p 0-15] [-m name;id] <-i inpath> <-o outpath>
``` ```
| Command | Required | Notes | | Command | Required | Notes |
|--------------|----------|------------------------------------------------------------------------------------| |--------------|----------|------------------------------------------------------------------------------------|
| -h | N/A | Display help & command info | | -h | N/A | Display program help & command info then quit |
| -v | No | Display version & quit |
| -l (name) | No | Name of layer to use (default first layer in TMX) | | -l (name) | No | Name of layer to use (default first layer in TMX) |
| -y (name) | No | Layer for palette mappings | | -y (name) | No | Layer for palette mappings |
| -c (name) | No | Output a separate 8bit collision map of the specified layer | | -c (name) | No | Output a separate 8bit collision map of the specified layer |
@@ -60,7 +59,6 @@ sudo cmake --install build
## License ## ## License ##
[tmx2gba](https://github.com/ScrelliCopter/tmx2gba) is licensed under the [Zlib license](COPYING.txt). [tmx2gba](https://github.com/ScrelliCopter/tmx2gba) is licensed under the [Zlib license](COPYING.txt).
- A modified [tmxlite](https://github.com/fallahn/tmxlite) is licensed under the [Zlib license](ext/tmxlite/LICENSE).
- [pugixml](https://pugixml.org/) is licensed under the [MIT license](ext/pugixml/LICENSE.md). - [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). - [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). - [miniz](https://github.com/richgel999/miniz) is licensed under the [MIT license](ext/miniz/LICENSE).

View File

@@ -1,6 +1,7 @@
add_library(base64 add_library(base64
base64.cpp base64.h) base64.cpp base64.h)
add_library(base64::base64 ALIAS base64) add_library(base64::base64 ALIAS base64)
set_target_properties(base64 PROPERTIES CXX_STANDARD 17) 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 target_include_directories(base64
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

View File

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

View File

@@ -1,4 +1,5 @@
add_executable(tmx2gba add_executable(tmx2gba
strtools.hpp strtools.cpp
argparse.hpp argparse.cpp argparse.hpp argparse.cpp
$<$<NOT:$<TARGET_EXISTS:ZLIB::ZLIB>>:gzip.hpp gzip.cpp> $<$<NOT:$<TARGET_EXISTS:ZLIB::ZLIB>>:gzip.hpp gzip.cpp>
tmxlayer.hpp tmxlayer.hpp
@@ -15,11 +16,12 @@ configure_file(config.h.in config.h @ONLY)
target_sources(tmx2gba PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/config.h) target_sources(tmx2gba PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/config.h)
target_include_directories(tmx2gba PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) 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 target_compile_definitions(${PROJECT_NAME} PRIVATE
$<$<BOOL:${MSVC}>:_CRT_SECURE_NO_WARNINGS> # disable msvc warning $<$<BOOL:${MSVC}>:_CRT_SECURE_NO_WARNINGS>) # Disable msvc warning
$<$<TARGET_EXISTS:ZLIB::ZLIB>:USE_ZLIB>)
# Enable strong warnings # Enable strong warnings
target_compile_options(tmx2gba PRIVATE target_compile_options(tmx2gba PRIVATE
@@ -27,7 +29,8 @@ target_compile_options(tmx2gba PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -pedantic> $<$<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>) $<$<CXX_COMPILER_ID:Clang,AppleClang>:-Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded>)
target_link_libraries(tmx2gba pugixml base64::base64 Zstd::Zstd target_link_libraries(tmx2gba
pugixml::pugixml base64::base64 Zstd::Zstd
$<$<TARGET_EXISTS:ZLIB::ZLIB>:ZLIB::ZLIB> $<$<TARGET_EXISTS:ZLIB::ZLIB>:ZLIB::ZLIB>
$<$<TARGET_EXISTS:miniz::miniz>:miniz::miniz>) $<$<TARGET_EXISTS:miniz::miniz>:miniz::miniz>)

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 "argparse.hpp"
#include <optional> #include <optional>
@@ -6,6 +7,7 @@
#include <filesystem> #include <filesystem>
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
#include <algorithm>
ArgParse::ArgParser::ArgParser( ArgParse::ArgParser::ArgParser(
@@ -98,8 +100,8 @@ ArgParse::ParseCtrl ArgParse::ParserState::Next(const std::string_view token)
{ {
flagChar = flag.value(); flagChar = flag.value();
const auto opt = getOption(flagChar); 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(); bool expect = !opt.value().get().argumentName.empty();
if (token.length() <= 2) if (token.length() <= 2)
{ {
@@ -112,7 +114,6 @@ ArgParse::ParseCtrl ArgParse::ParserState::Next(const std::string_view token)
return handler(flagChar, expect ? token.substr(2) : ""); return handler(flagChar, expect ? token.substr(2) : "");
} }
} }
}
else if (!token.empty()) else if (!token.empty())
{ {
return ParseCtrl::QUIT_ERR_UNEXPECTED; return ParseCtrl::QUIT_ERR_UNEXPECTED;

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 #ifndef ARGPARSE_HPP
#define ARGPARSE_HPP #define ARGPARSE_HPP

View File

@@ -2,5 +2,9 @@
#define 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 #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 "convert.hpp"
#include "tmxreader.hpp" #include "tmxreader.hpp"

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 #ifndef CONVERT_HPP
#define CONVERT_HPP #define CONVERT_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 "headerwriter.hpp"
#include <algorithm> #include <algorithm>

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 #ifndef HEADERWRITER_HPP
#define HEADERWRITER_HPP #define HEADERWRITER_HPP

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,43 +1,12 @@
/* 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 "swriter.hpp"
#include "strtools.hpp"
#include <type_traits> #include <type_traits>
#include <limits>
#include <assert.h> #include <assert.h>
static inline constexpr char HexU(uint8_t h) { return "0123456789ABCDEF"[h >> 4]; }
static inline constexpr char HexL(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 << 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));
}
template <typename T> static constexpr const std::string_view DataType(); template <typename T> static constexpr const std::string_view DataType();
template <> constexpr const std::string_view DataType<uint8_t>() { return ".byte"; } template <> constexpr const std::string_view DataType<uint8_t>() { return ".byte"; }
template <> constexpr const std::string_view DataType<uint16_t>() { return ".hword"; } template <> constexpr const std::string_view DataType<uint16_t>() { return ".hword"; }

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

View File

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

View File

@@ -2,6 +2,8 @@
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur // SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#include "tmxmap.hpp" #include "tmxmap.hpp"
#include "strtools.hpp"
#include "config.h"
#include <pugixml.hpp> #include <pugixml.hpp>
#include <base64.h> #include <base64.h>
#ifdef USE_ZLIB #ifdef USE_ZLIB
@@ -10,78 +12,10 @@
# include "gzip.hpp" # include "gzip.hpp"
#endif #endif
#include <zstd.h> #include <zstd.h>
#include <limits>
#include <cerrno> #include <cerrno>
#include <optional> #include <algorithm>
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;
}
[[nodiscard]] static std::optional<std::string> UnBase64(const std::string_view base64)
{
// Cut leading & trailing whitespace (including newlines)
auto beg = std::find_if_not(base64.begin(), base64.end(), ::isspace);
if (beg == std::end(base64)) { return std::nullopt; }
auto end = std::find_if_not(base64.rbegin(), base64.rend(), ::isspace);
std::size_t begOff = std::distance(base64.begin(), beg);
std::size_t endOff = std::distance(end, base64.rend()) - begOff;
const auto trimmed = base64.substr(begOff, endOff);
// Decode base64 string
return base64_decode(trimmed);
}
enum class Encoding { XML, BASE64, CSV, INVALID }; enum class Encoding { XML, BASE64, CSV, INVALID };
enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID }; enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID };
@@ -102,33 +36,40 @@ enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID };
return Compression::INVALID; return Compression::INVALID;
} }
[[nodiscard]] static bool Decompress(Compression compression, std::span<uint32_t> out, const std::string_view decoded)
{
//FIXME: lmao what is big endian
const std::span source(reinterpret_cast<const uint8_t*>(decoded.data()), decoded.size());
std::span destination(reinterpret_cast<uint8_t*>(out.data()), sizeof(uint32_t) * out.size());
[[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) switch (compression)
{ {
case Compression::GZIP: case Compression::GZIP:
#ifndef USE_ZLIB #ifndef USE_ZLIB
{ {
out.resize(numTiles);
GZipReader reader; GZipReader reader;
if (!reader.OpenMemory(source) || !reader.Read(destination) || !reader.Check()) if (!reader.OpenMemory(source) ||
!reader.Read({ reinterpret_cast<uint8_t*>(out.data()), sizeof(uint32_t) * numTiles }) ||
!reader.Check())
return false; return false;
return true; return true;
} }
#endif #endif
case Compression::ZLIB: case Compression::ZLIB:
{ {
out.resize(numTiles);
// Decompress gzip/zlib data with zlib/zlib data miniz // Decompress gzip/zlib data with zlib/zlib data miniz
auto dstSize = static_cast<uLongf>(sizeof(uint32_t) * destination.size());
z_stream s = z_stream s =
{ {
.next_in = const_cast<Bytef*>(source.data()), .next_in = const_cast<Bytef*>(source.data()),
.avail_in = static_cast<unsigned int>(source.size()), .avail_in = static_cast<unsigned int>(source.size()),
.next_out = static_cast<Bytef*>(destination.data()), .next_out = reinterpret_cast<Bytef*>(out.data()),
.avail_out = static_cast<unsigned int>(destination.size()), .avail_out = static_cast<unsigned int>(sizeof(uint32_t) * numTiles),
.zalloc = nullptr, .zfree = nullptr, .opaque = nullptr .zalloc = nullptr, .zfree = nullptr, .opaque = nullptr
}; };
#ifdef USE_ZLIB #ifdef USE_ZLIB
@@ -144,11 +85,28 @@ enum class Compression { NONE, GZIP, ZLIB, ZSTD, INVALID };
} }
case Compression::ZSTD: case Compression::ZSTD:
{ {
out.resize(numTiles);
auto res = ZSTD_decompress( auto res = ZSTD_decompress(
destination.data(), destination.size(), reinterpret_cast<void*>(out.data()),
sizeof(uint32_t) * numTiles,
source.data(), source.size()); source.data(), source.size());
return !ZSTD_isError(res); 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; default: return false;
} }
} }
@@ -159,9 +117,11 @@ void TmxMap::ReadTileset(const pugi::xml_node& xNode)
std::string_view source = xNode.attribute("source").value(); std::string_view source = xNode.attribute("source").value();
auto firstGid = UintFromStr<uint32_t>(xNode.attribute("firstgid").value()).value_or(0); auto firstGid = UintFromStr<uint32_t>(xNode.attribute("firstgid").value()).value_or(0);
auto lastGid = UintFromStr<uint32_t>(xNode.attribute("lastgid").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, lastGid)); mTilesets.emplace_back(TmxTileset(name, source, firstGid, numTiles));
} }
void TmxMap::ReadLayer(const pugi::xml_node& xNode) void TmxMap::ReadLayer(const pugi::xml_node& xNode)
@@ -171,8 +131,8 @@ void TmxMap::ReadLayer(const pugi::xml_node& xNode)
// Read layer size // Read layer size
int width = IntFromStr<int>(xNode.attribute("width").value()).value_or(0); int width = IntFromStr<int>(xNode.attribute("width").value()).value_or(0);
int height = IntFromStr<int>(xNode.attribute("height").value()).value_or(0); int height = IntFromStr<int>(xNode.attribute("height").value()).value_or(0);
if (width <= 0 || height <= 0) if (width <= 0 || height <= 0) { return; }
return; const auto numTiles = static_cast<size_t>(width) * static_cast<size_t>(height);
auto xData = xNode.child("data"); auto xData = xNode.child("data");
if (xData.empty() || xData.first_child().empty()) if (xData.empty() || xData.first_child().empty())
@@ -183,49 +143,67 @@ void TmxMap::ReadLayer(const pugi::xml_node& xNode)
auto encoding = EncodingFromStr(xData.attribute("encoding").value()); auto encoding = EncodingFromStr(xData.attribute("encoding").value());
if (encoding == Encoding::BASE64) if (encoding == Encoding::BASE64)
{ {
// Decode base64 string const std::string_view base64(xData.child_value());
auto decoded = UnBase64(xData.child_value()); if (base64.empty())
if (!decoded.has_value())
return; return;
auto compression = CompressionFromStr(xData.attribute("compression").value()); const auto compression = CompressionFromStr(xData.attribute("compression").value());
if (compression == Compression::GZIP || compression == Compression::ZLIB || compression == Compression::ZSTD) if (compression == Compression::INVALID || !DecodeBase64(tileDat, numTiles, base64, compression))
{
tileDat.resize(width * height);
if (!Decompress(compression, tileDat, decoded.value()))
return; return;
} }
else if (compression == Compression::NONE) else if (encoding == Encoding::XML)
{ {
//TODO tileDat.reserve(numTiles);
return; 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 else if (encoding == Encoding::CSV)
{ {
return; tileDat.reserve(numTiles);
} const std::string_view csv(xData.child_value());
}
else std::string::size_type pos = 0;
while (true)
{ {
//TODO // TODO: check if this has a problem on other locales?
return; 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))); mLayers.emplace_back(TmxLayer(width, height, name, std::move(tileDat)));
} }
void TmxMap::ReadObjects(const pugi::xml_node& xNode) void TmxMap::ReadObjectGroup(const pugi::xml_node& xNode)
{ {
for (const auto it : xNode.children("object")) 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(); std::string_view name = it.attribute("name").value();
// Read position // Read axis-aligned bounding box
auto x = FloatFromStr<float>(it.attribute("x").value()).value_or(0.0f); 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 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);
mObjects.emplace_back(TmxObject(name, x, y)); 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) bool TmxMap::Load(const std::string& inPath)
@@ -251,7 +229,7 @@ bool TmxMap::Load(const std::string& inPath)
std::string_view name(it.name()); std::string_view name(it.name());
if (!name.compare("layer")) { ReadLayer(it); } if (!name.compare("layer")) { ReadLayer(it); }
else if (!name.compare("tileset")) { ReadTileset(it); } else if (!name.compare("tileset")) { ReadTileset(it); }
else if (!name.compare("objectgroup")) { ReadObjects(it); } else if (!name.compare("objectgroup")) { ReadObjectGroup(it); }
} }
return true; return true;

View File

@@ -20,18 +20,19 @@ class TmxMap
std::vector<TmxLayer> mLayers; std::vector<TmxLayer> mLayers;
std::vector<TmxTileset> mTilesets; std::vector<TmxTileset> mTilesets;
std::vector<TmxObject> mObjects; std::vector<TmxObjectGroup> mObjectGroups;
void ReadTileset(const pugi::xml_node& xNode); void ReadTileset(const pugi::xml_node& xNode);
void ReadLayer(const pugi::xml_node& xNode); void ReadLayer(const pugi::xml_node& xNode);
void ReadObjects(const pugi::xml_node& xNode); void ReadObjectGroup(const pugi::xml_node& xNode);
public: public:
[[nodiscard]] bool Load(const std::string& inPath); [[nodiscard]] bool Load(const std::string& inPath);
constexpr std::pair<int, int> TileCount() const noexcept { return { mWidth, mHeight }; } [[nodiscard]] constexpr std::pair<int, int> TileCount() const noexcept { return { mWidth, mHeight }; }
constexpr const std::vector<TmxTileset>& Tilesets() const noexcept { return mTilesets; } [[nodiscard]] constexpr const std::vector<TmxTileset>& Tilesets() const noexcept { return mTilesets; }
constexpr const std::vector<TmxLayer>& Layers() const noexcept { return mLayers; } [[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 #endif//TMXMAP_HPP

View File

@@ -1,25 +1,43 @@
/* tmxobject.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */ // SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#ifndef TMXOBJECT_HPP #ifndef TMXOBJECT_HPP
#define TMXOBJECT_HPP #define TMXOBJECT_HPP
#include <string> #include <string>
#include <string_view>
#include <vector>
#include <utility> #include <utility>
class TmxObject class TmxObject
{ {
public: public:
TmxObject(std::string_view name, float x, float y) : mName(name), mPos{ x, y } {}
template <typename T> template <typename T>
struct Position { T x, y; }; 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; } const std::string_view Name() const noexcept { return mName; }
constexpr Position<float> Pos() const noexcept { return mPos; } constexpr const AABB<float>& Box() const noexcept { return mBox; }
private: private:
int mId;
std::string mName; std::string mName;
Position<float> mPos; 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 #endif//TMXOBJECT_HPP

View File

@@ -1,4 +1,5 @@
/* 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 "tmxreader.hpp"
#include "tmxmap.hpp" #include "tmxmap.hpp"
@@ -20,10 +21,7 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
using std::optional; using std::optional;
using std::reference_wrapper; using std::reference_wrapper;
optional<reference_wrapper<const TmxLayer>> layerGfx; optional<reference_wrapper<const TmxLayer>> layerGfx, layerCls, layerPal;
optional<reference_wrapper<const TmxLayer>> layerCls;
optional<reference_wrapper<const TmxLayer>> layerPal;
optional<reference_wrapper<std::vector<const TmxObject>>> objGroups;
// Read layers // Read layers
for (const auto& layer : map.Layers()) for (const auto& layer : map.Layers())
@@ -36,12 +34,6 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
if (!layerGfx.has_value() && (graphicsName.empty() || name == graphicsName)) { layerGfx = layer; } if (!layerGfx.has_value() && (graphicsName.empty() || name == graphicsName)) { layerGfx = layer; }
if (!collisionName.empty() && !layerCls.has_value() && name == collisionName) { layerCls = layer; } if (!collisionName.empty() && !layerCls.has_value() && name == collisionName) { layerCls = layer; }
if (!paletteName.empty() && !layerPal.has_value() && name == paletteName) { layerPal = layer; } if (!paletteName.empty() && !layerPal.has_value() && name == paletteName) { layerPal = layer; }
/*
else if (!objMapping.empty() && layer->getType() == tmx::Layer::Type::Object)
{
objGroups.emplace_back(layer->getLayerAs<ObjectGroup>());
}
*/
} }
// Check layers // Check layers
@@ -61,7 +53,7 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
// Read graphics layer // Read graphics layer
mGraphics.reserve(numTiles); mGraphics.reserve(numTiles);
for (auto tmxTile : layerGfx.value().get().Tiles()) for (auto tmxTile : layerGfx.value().get().Tiles())
mGraphics.emplace_back(Tile{ tmxTile & ~FLIP_MASK, static_cast<uint8_t>((tmxTile & FLIP_MASK) >> 28) }); mGraphics.emplace_back(Tile{ tmxTile & ~TmxLayer::FLIP_MASK, static_cast<uint8_t>((tmxTile & TmxLayer::FLIP_MASK) >> 28) });
// Read optional layers // Read optional layers
if (layerPal.has_value()) if (layerPal.has_value())
@@ -69,7 +61,7 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
std::vector<uint32_t> v; std::vector<uint32_t> v;
v.reserve(numTiles); v.reserve(numTiles);
for (auto tmxTile : layerPal.value().get().Tiles()) for (auto tmxTile : layerPal.value().get().Tiles())
v.emplace_back(tmxTile & ~FLIP_MASK); v.emplace_back(tmxTile & ~TmxLayer::FLIP_MASK);
mPalette.emplace(v); mPalette.emplace(v);
} }
if (layerCls.has_value()) if (layerCls.has_value())
@@ -77,42 +69,42 @@ TmxReader::Error TmxReader::Open(const std::string& inPath,
std::vector<uint32_t> v; std::vector<uint32_t> v;
v.reserve(numTiles); v.reserve(numTiles);
for (auto tmxTile : layerCls.value().get().Tiles()) for (auto tmxTile : layerCls.value().get().Tiles())
v.emplace_back(tmxTile & ~FLIP_MASK); v.emplace_back(tmxTile & ~TmxLayer::FLIP_MASK);
mCollision.emplace(v); mCollision.emplace(v);
} }
// Read tilesets // Read tilesets
const auto& tilesets = map.Tilesets(); const auto& tilesets = map.Tilesets();
mGidTable.reserve(tilesets.size()); mGidTable.reserve(tilesets.size());
for (const auto& set : tilesets) std::ranges::transform(tilesets, std::back_inserter(mGidTable),
mGidTable.emplace_back(set.GidRange()); [](const auto& it) { return it.GidRange(); });
// Read objects // Read objects
/* if (!map.ObjectGroups().empty())
if (!objMapping.empty())
{ {
for (const auto& group : objGroups) std::vector<Object> objs;
for (const auto& group : map.ObjectGroups())
{ {
const auto& tmxObjects = group.get().Objects(); const auto& tmxObjects = group.Objects();
v.reserve(v.size() + tmxObjects.size()); objs.reserve(objs.size() + tmxObjects.size());
for (const auto& tmxObj : tmxObjects) for (const auto& tmxObj : tmxObjects)
{ {
auto it = objMapping.find(std::string(tmxObj.Name())); auto it = objMapping.find(std::string(tmxObj.Name()));
if (it == objMapping.end()) if (it == objMapping.end())
continue; continue;
const auto& pos = tmxObj.Pos(); const auto& aabb = tmxObj.Box();
Object obj; objs.emplace_back(Object
obj.id = it->second; {
obj.x = pos.x; .id = it->second,
obj.y = pos.y; .x = aabb.x,
.y = aabb.y
v.emplace_back(obj); });
} }
} }
mObjects.emplace(v); if (!objs.empty())
mObjects.emplace(objs);
} }
*/
return Error::OK; 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 #ifndef TMXREADER_HPP
#define TMXREADER_HPP #define TMXREADER_HPP

View File

@@ -1,4 +1,5 @@
/* tmxtileset.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */ // SPDX-License-Identifier: Zlib
// SPDX-FileCopyrightText: (c) 2015-2024 a dinosaur
#ifndef TMXTILESET_HPP #ifndef TMXTILESET_HPP
#define TMXTILESET_HPP #define TMXTILESET_HPP
@@ -10,15 +11,16 @@
class TmxTileset class TmxTileset
{ {
std::string mName, mSource; std::string mName, mSource;
uint32_t mFirstGid = 0, mLastGid = 0; uint32_t mFirstGid = 0, mTileCount = 0;
public: public:
TmxTileset(const std::string_view name, const std::string_view source, uint32_t firstGid, uint32_t lastGid) TmxTileset(const std::string_view name, const std::string_view source, uint32_t firstGid, uint32_t tileCount)
: mName(name), mSource(source), mFirstGid(firstGid), mLastGid(lastGid) {} : mName(name), mSource(source), mFirstGid(firstGid), mTileCount(tileCount) {}
[[nodiscard]] const std::string_view Name() const noexcept { return mName; } [[nodiscard]] const std::string_view Name() const noexcept { return mName; }
[[nodiscard]] const std::string_view Source() const noexcept { return mSource; } [[nodiscard]] const std::string_view Source() const noexcept { return mSource; }
[[nodiscard]] constexpr const std::pair<uint32_t, uint32_t> GidRange() const noexcept { return { mFirstGid, mLastGid }; } [[nodiscard]] constexpr const std::pair<uint32_t, uint32_t> GidRange() const noexcept
{ return { mFirstGid, mFirstGid + mTileCount - 1 }; }
}; };
#endif//TMXTILESET_HPP #endif//TMXTILESET_HPP