mirror of
https://github.com/ScrelliCopter/tmx2gba.git
synced 2025-02-21 03:29:25 +11:00
rewrite argument parsing
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
add_executable(tmx2gba
|
||||
tmx2gba.cpp
|
||||
add_executable(tmx2gba
|
||||
argparse.hpp argparse.cpp
|
||||
tmxlayer.hpp
|
||||
tmxobject.hpp
|
||||
tmxreader.hpp tmxreader.cpp
|
||||
tmxtileset.hpp)
|
||||
tmxtileset.hpp
|
||||
tmx2gba.cpp)
|
||||
|
||||
target_link_libraries(tmx2gba
|
||||
External::base64
|
||||
External::miniz
|
||||
External::rapidxml
|
||||
External::ultragetopt)
|
||||
External::rapidxml)
|
||||
|
||||
if (TMX2GBA_DKP_INSTALL)
|
||||
if (DEFINED ENV{DEVKITPRO})
|
||||
|
||||
180
src/argparse.cpp
Normal file
180
src/argparse.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
/* argparse.cpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
|
||||
#include "argparse.hpp"
|
||||
#include <iomanip>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
|
||||
ArgParse::ArgParser::ArgParser(
|
||||
const std::string_view argv0,
|
||||
std::initializer_list<Option> options,
|
||||
HandleOption&& handler
|
||||
) noexcept :
|
||||
name(std::filesystem::path(argv0).filename().string()),
|
||||
options(options),
|
||||
handler(std::forward<HandleOption>(handler)) {}
|
||||
|
||||
|
||||
void ArgParse::ArgParser::ShowShortUsage(std::ostream& out) const
|
||||
{
|
||||
out << "Usage: " << name;
|
||||
for (const auto& it : options)
|
||||
{
|
||||
if (it.argumentName)
|
||||
{
|
||||
// Option with argument
|
||||
it.required
|
||||
? out << " <-" << it.flag << ' ' << it.argumentName << '>'
|
||||
: out << " [-" << it.flag << ' ' << it.argumentName << ']';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Argument-less flag
|
||||
it.required
|
||||
? out << " <-" << it.flag << '>'
|
||||
: out << " [-" << it.flag << ']';
|
||||
}
|
||||
}
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
void ArgParse::ArgParser::ShowHelpUsage(std::ostream& out) const
|
||||
{
|
||||
// Base usage
|
||||
out << "Usage: " << name << " [-";
|
||||
for (const auto& it : options)
|
||||
if (!it.required)
|
||||
out << it.flag;
|
||||
out << "] <-";
|
||||
for (const auto& it : options)
|
||||
if (it.required)
|
||||
out << it.flag;
|
||||
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)
|
||||
: 1; };
|
||||
auto longestParam = std::max(options, [=](auto a, auto b) -> bool { return paramLength(a) < paramLength(b); });
|
||||
auto alignWidth = paramLength(longestParam) + 3;
|
||||
|
||||
// print argument descriptions
|
||||
for (const auto& it : options)
|
||||
{
|
||||
auto decorateArgument = [=] { return " <" + std::string(it.argumentName) + "> "; };
|
||||
out << " -" << it.flag
|
||||
<< std::left << std::setw(alignWidth) << std::setfill('-') << (it.argumentName ? decorateArgument() : " ")
|
||||
<< " " << it.helpString << std::endl;
|
||||
}
|
||||
out << std::flush;
|
||||
}
|
||||
|
||||
|
||||
ArgParse::ParseCtrl ArgParse::ParserState::Next(const std::string_view token)
|
||||
{
|
||||
auto getFlag = [](const std::string_view s) { return s[0] == '-' && s[1] ? std::optional<int>(s[1]) : std::nullopt; };
|
||||
auto getOption = [&](int flag) -> std::optional<std::reference_wrapper<const Option>>
|
||||
{
|
||||
for (auto& opt : options)
|
||||
if (opt.flag == flag)
|
||||
return std::optional(std::cref(opt));
|
||||
return {};
|
||||
};
|
||||
|
||||
if (expectArg)
|
||||
{
|
||||
expectArg = false;
|
||||
return handler(flagChar, token);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto flag = getFlag(token);
|
||||
if (flag.has_value())
|
||||
{
|
||||
flagChar = flag.value();
|
||||
const auto opt = getOption(flagChar);
|
||||
if (opt.has_value())
|
||||
{
|
||||
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) : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!token.empty())
|
||||
{
|
||||
return ParseCtrl::QUIT_ERR_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
return ParseCtrl::CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
bool ReadParamFile(std::vector<std::string>& tokens, std::istream& file)
|
||||
{
|
||||
bool inQuote = false;
|
||||
std::string quoteStr;
|
||||
const auto store = [&](const std::string_view token, bool quote)
|
||||
{
|
||||
if (quote)
|
||||
quoteStr = token;
|
||||
else
|
||||
tokens.emplace_back(token);
|
||||
};
|
||||
|
||||
while (!file.eof())
|
||||
{
|
||||
if (!inQuote)
|
||||
{
|
||||
std::string token;
|
||||
file >> token;
|
||||
if (!token.empty())
|
||||
{
|
||||
std::string::size_type beg = 0, end;
|
||||
while ((end = token.find_first_of('"', beg)) != std::string::npos)
|
||||
{
|
||||
auto size = end - beg;
|
||||
if (size > 0)
|
||||
store(token.substr(beg, size), !inQuote);
|
||||
inQuote = !inQuote;
|
||||
beg = end + 1;
|
||||
}
|
||||
if (beg > 0)
|
||||
{
|
||||
auto size = token.length() - beg;
|
||||
if (size > 0)
|
||||
token = token.substr(beg);
|
||||
else
|
||||
token.clear();
|
||||
}
|
||||
if (!token.empty())
|
||||
store(token, inQuote);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const int c = file.get();
|
||||
if (c == '"')
|
||||
{
|
||||
tokens.emplace_back(quoteStr);
|
||||
quoteStr.clear();
|
||||
inQuote = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
quoteStr.push_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !inQuote;
|
||||
}
|
||||
110
src/argparse.hpp
Normal file
110
src/argparse.hpp
Normal file
@@ -0,0 +1,110 @@
|
||||
/* argparse.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
|
||||
#ifndef ARGPARSE_HPP
|
||||
#define ARGPARSE_HPP
|
||||
|
||||
#include <initializer_list>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
#include <span>
|
||||
#include <ranges>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ArgParse
|
||||
{
|
||||
struct Option
|
||||
{
|
||||
char flag;
|
||||
const char* argumentName;
|
||||
bool required;
|
||||
const char* helpString;
|
||||
};
|
||||
|
||||
enum class ParseCtrl
|
||||
{
|
||||
CONTINUE,
|
||||
QUIT_EARLY,
|
||||
QUIT_ERR_UNKNOWN,
|
||||
QUIT_ERR_UNEXPECTED,
|
||||
QUIT_ERR_EXPECTARG,
|
||||
QUIT_ERR_INVALID,
|
||||
QUIT_ERR_RANGE
|
||||
};
|
||||
|
||||
enum class ParseErr
|
||||
{
|
||||
OK,
|
||||
OPT_UNKNOWN,
|
||||
UNEXPECTED,
|
||||
ARG_EXPECTED,
|
||||
ARG_INVALID,
|
||||
ARG_RANGE
|
||||
};
|
||||
|
||||
using HandleOption = std::function<ParseCtrl(int, const std::string_view)>;
|
||||
|
||||
class ParserState
|
||||
{
|
||||
bool expectArg = false;
|
||||
int flagChar;
|
||||
HandleOption handler;
|
||||
const std::initializer_list<Option>& options;
|
||||
|
||||
public:
|
||||
ParserState(HandleOption handler, const std::initializer_list<Option>& options) noexcept
|
||||
: handler(handler), options(options) {}
|
||||
[[nodiscard]] bool ExpectingArg() const { return expectArg; }
|
||||
[[nodiscard]] ParseCtrl Next(const std::string_view token);
|
||||
};
|
||||
|
||||
class ArgParser
|
||||
{
|
||||
const std::string name;
|
||||
std::initializer_list<Option> options;
|
||||
HandleOption handler;
|
||||
|
||||
public:
|
||||
explicit ArgParser(const std::string_view argv0, std::initializer_list<Option> options, HandleOption&& handler) noexcept;
|
||||
|
||||
[[nodiscard]] const std::string_view GetName() const { return name; }
|
||||
|
||||
void ShowShortUsage(std::ostream& out) const;
|
||||
void ShowHelpUsage(std::ostream& out) const;
|
||||
|
||||
template <typename V>
|
||||
ParseErr Parse(V args)
|
||||
{
|
||||
ParserState state(handler, options);
|
||||
for (auto arg : args)
|
||||
{
|
||||
switch (state.Next(arg))
|
||||
{
|
||||
case ParseCtrl::CONTINUE: continue;
|
||||
case ParseCtrl::QUIT_EARLY: return ParseErr::OK;
|
||||
case ParseCtrl::QUIT_ERR_UNKNOWN: return ParseErr::OPT_UNKNOWN;
|
||||
case ParseCtrl::QUIT_ERR_UNEXPECTED: return ParseErr::UNEXPECTED;
|
||||
case ParseCtrl::QUIT_ERR_EXPECTARG: return ParseErr::ARG_EXPECTED;
|
||||
case ParseCtrl::QUIT_ERR_INVALID: return ParseErr::ARG_INVALID;
|
||||
case ParseCtrl::QUIT_ERR_RANGE: return ParseErr::ARG_RANGE;
|
||||
}
|
||||
}
|
||||
return state.ExpectingArg() ? ParseErr::ARG_EXPECTED : ParseErr::OK;
|
||||
}
|
||||
|
||||
inline ParseErr Parse(std::initializer_list<std::string_view> args)
|
||||
{
|
||||
return Parse<std::initializer_list<std::string_view>>(args);
|
||||
}
|
||||
|
||||
inline ParseErr Parse(std::span<char*> args)
|
||||
{
|
||||
return Parse(args | std::views::transform([](char const* v){ return std::string_view(v); }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extern bool ReadParamFile(std::vector<std::string>& tokens, std::istream& file);
|
||||
|
||||
#endif//ARGPARSE_HPP
|
||||
234
src/tmx2gba.cpp
234
src/tmx2gba.cpp
@@ -1,31 +1,20 @@
|
||||
/* tmx2gba.cpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
|
||||
/* tmx2gba.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
|
||||
|
||||
#include "argparse.hpp"
|
||||
#include "tmxreader.hpp"
|
||||
#include "tmxlayer.hpp"
|
||||
#include "tmxobject.hpp"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <ultragetopt.h>
|
||||
|
||||
|
||||
const std::string helpUsage = "Usage: tmx2gba [-h] [-f file] [-r offset] [-lyc name] [-p 0-15] [-m name;id] <-i inpath> <-o outpath>";
|
||||
const std::string helpShort = "Run 'tmx2gba -h' to view all available options.";
|
||||
const std::string helpFull = R"(
|
||||
-h ------------ Display this help & command info.
|
||||
-l <name> ----- Name of layer to use (default first layer in TMX).
|
||||
-y <name> ----- Layer for palette mappings.
|
||||
-c <name> ----- Output a separate 8bit collision map of the specified layer.
|
||||
-r <offset> --- Offset tile indices (default 0).
|
||||
-p <0-15> ----- Select which palette to use for 4-bit tilesets.
|
||||
-m <name;id> -- Map an object name to an ID, will enable object exports.
|
||||
-i <path> ----- Path to input TMX file.
|
||||
-o <path> ----- Path to output files.
|
||||
-f <file> ----- Specify a file to use for flags, will override any options specified on the command line.)";
|
||||
using ArgParse::ArgParser;
|
||||
using ArgParse::ParseCtrl;
|
||||
using ArgParse::ParseErr;
|
||||
|
||||
|
||||
struct Arguments
|
||||
{
|
||||
@@ -36,68 +25,127 @@ struct Arguments
|
||||
int offset = 0;
|
||||
int palette = 0;
|
||||
std::vector<std::string> objMappings;
|
||||
bool objExport = false;
|
||||
};
|
||||
|
||||
void ParseArgs(int argc, char** argv, Arguments& p)
|
||||
static void DisplayError(const ArgParser& parser, const std::string& message, bool helpPrompt = true)
|
||||
{
|
||||
int opt;
|
||||
optreset = 1;
|
||||
while ((opt = getopt(argc, argv, "hr:l:c:p:y:m:i:o:f:")) > 0)
|
||||
std::cerr << parser.GetName() << ": " << message << std::endl;
|
||||
parser.ShowShortUsage(std::cerr);
|
||||
if (helpPrompt)
|
||||
std::cerr << "Run '" << parser.GetName() << " -h' to view all available options." << std::endl;
|
||||
}
|
||||
|
||||
bool CheckParse(const ArgParser& parser, ParseErr err)
|
||||
{
|
||||
switch (err)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case ('h'):
|
||||
p.help = true;
|
||||
return;
|
||||
|
||||
case ('l'): p.layer = optarg; break;
|
||||
case ('c'): p.collisionlay = optarg; break;
|
||||
case ('y'): p.paletteLay = optarg; break;
|
||||
case ('r'): p.offset = std::stoi(optarg); break;
|
||||
case ('p'): p.palette = std::stoi(optarg); break;
|
||||
|
||||
case ('m'):
|
||||
p.objExport = true;
|
||||
p.objMappings.emplace_back(optarg);
|
||||
break;
|
||||
|
||||
case ('i'): p.inPath = optarg; break;
|
||||
case ('o'): p.outPath = optarg; break;
|
||||
case ('f'): p.flagFile = optarg; break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case ParseErr::OK:
|
||||
return true;
|
||||
case ParseErr::OPT_UNKNOWN:
|
||||
DisplayError(parser, "Unrecognised option.");
|
||||
return false;
|
||||
case ParseErr::UNEXPECTED:
|
||||
DisplayError(parser, "Unexpected token.");
|
||||
return false;
|
||||
case ParseErr::ARG_EXPECTED:
|
||||
DisplayError(parser, "Requires an argument.");
|
||||
return false;
|
||||
case ParseErr::ARG_INVALID:
|
||||
DisplayError(parser, "Invalid argument.", false);
|
||||
return false;
|
||||
case ParseErr::ARG_RANGE:
|
||||
DisplayError(parser, "Argument out of range.", false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckArgs(const Arguments& params)
|
||||
bool ParseArgs(int argc, char** argv, Arguments& params)
|
||||
{
|
||||
auto parser = ArgParser(argv[0], {
|
||||
{ 'h', nullptr, false, "Display this help & command info" },
|
||||
{ 'l', "name", false, "Name of layer to use (default first layer in TMX)" },
|
||||
{ 'y', "name", false, "Layer for palette mappings" },
|
||||
{ 'c', "name", false, "Output a separate 8bit collision map of the specified layer" },
|
||||
{ 'r', "offset", false, "Offset tile indices (default 0)" },
|
||||
{ 'p', "0-15", false, "Select which palette to use for 4-bit tilesets" },
|
||||
{ 'm', "name;id", false, "Map an object name to an ID, will enable object exports" },
|
||||
{ 'i', "inpath", true, "Path to input TMX file" },
|
||||
{ 'o', "outpath", true, "Path to output files" },
|
||||
{ 'f', "file", false, "Specify a file to use for flags, will override any options"
|
||||
" specified on the command line" }
|
||||
}, [&](int opt, const std::string_view arg) -> ParseCtrl
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 'h': params.help = 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;
|
||||
case 'r': params.offset = std::stoi(std::string(arg)); return ParseCtrl::CONTINUE;
|
||||
case 'p': params.palette = std::stoi(std::string(arg)); return ParseCtrl::CONTINUE;
|
||||
case 'm': params.objMappings.emplace_back(arg); return ParseCtrl::CONTINUE;
|
||||
case 'i': params.inPath = arg; return ParseCtrl::CONTINUE;
|
||||
case 'o': params.outPath = arg; return ParseCtrl::CONTINUE;
|
||||
case 'f': params.flagFile = arg; return ParseCtrl::CONTINUE;
|
||||
|
||||
default: return ParseCtrl::QUIT_ERR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
catch (std::invalid_argument const& e) { return ParseCtrl::QUIT_ERR_INVALID; }
|
||||
catch (std::out_of_range const& e) { return ParseCtrl::QUIT_ERR_RANGE; }
|
||||
});
|
||||
|
||||
if (!CheckParse(parser, parser.Parse(std::span(argv + 1, argc - 1))))
|
||||
return false;
|
||||
|
||||
if (params.help)
|
||||
{
|
||||
parser.ShowHelpUsage(std::cout);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!params.flagFile.empty())
|
||||
{
|
||||
std::ifstream paramFile(params.flagFile);
|
||||
if (!paramFile.is_open())
|
||||
{
|
||||
std::cerr << "Failed to open param file." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
if (!ReadParamFile(tokens, paramFile))
|
||||
{
|
||||
std::cerr << "Failed to read param file: Unterminated quote string." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckParse(parser, parser.Parse(tokens)))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check my paranoia
|
||||
if (params.inPath.empty())
|
||||
{
|
||||
std::cerr << "No input file specified." << std::endl;
|
||||
std::cout << helpUsage << std::endl << helpShort << std::endl;
|
||||
DisplayError(parser, "No input file specified.");
|
||||
return false;
|
||||
}
|
||||
if (params.outPath.empty())
|
||||
{
|
||||
std::cerr << "No output file specified." << std::endl;
|
||||
std::cout << helpUsage << std::endl << helpShort << std::endl;
|
||||
DisplayError(parser, "No output file specified.");
|
||||
return false;
|
||||
}
|
||||
if (params.palette < 0 || params.palette > 15)
|
||||
{
|
||||
std::cerr << "Invalid palette index." << std::endl;
|
||||
std::cout << helpUsage << std::endl << helpShort << std::endl;
|
||||
DisplayError(parser, "Invalid palette index.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template <typename T> constexpr const char* DatType();
|
||||
template <> constexpr const char* DatType<uint8_t>() { return ".byte"; }
|
||||
template <> constexpr const char* DatType<uint16_t>() { return ".hword"; }
|
||||
@@ -139,84 +187,14 @@ void WriteArray(std::ofstream& aOut, const std::vector<T>& aDat, int aPerCol = 1
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Arguments p;
|
||||
ParseArgs(argc, argv, p);
|
||||
|
||||
if (!ParseArgs(argc, argv, p))
|
||||
return 1;
|
||||
if (p.help)
|
||||
{
|
||||
std::cout << helpUsage << std::endl << helpFull << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!p.flagFile.empty())
|
||||
{
|
||||
std::ifstream paramFile(p.flagFile);
|
||||
if (!paramFile.is_open())
|
||||
{
|
||||
std::cerr << "Failed to open param file." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<std::string> fileArgTokens;
|
||||
fileArgTokens.push_back("auu~~");
|
||||
bool carry = false;
|
||||
std::string rawToken;
|
||||
while (!paramFile.eof())
|
||||
{
|
||||
if (carry)
|
||||
{
|
||||
std::string tmp;
|
||||
paramFile >> tmp;
|
||||
rawToken += " ";
|
||||
rawToken += tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
rawToken.clear();
|
||||
paramFile >> rawToken;
|
||||
}
|
||||
|
||||
if (rawToken.empty())
|
||||
continue;
|
||||
|
||||
bool qFr = rawToken[0] == '"';
|
||||
bool qBk = rawToken[rawToken.length() - 1] == '"';
|
||||
if (qFr && qBk)
|
||||
{
|
||||
fileArgTokens.push_back(rawToken.substr(1, rawToken.length() - 2));
|
||||
}
|
||||
else
|
||||
if (qFr)
|
||||
{
|
||||
fileArgTokens.push_back(rawToken.substr(1, rawToken.length() - 1));
|
||||
carry = true;
|
||||
}
|
||||
else
|
||||
if (qBk)
|
||||
{
|
||||
fileArgTokens.push_back(rawToken.substr(0, rawToken.length() - 1));
|
||||
carry = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileArgTokens.push_back(rawToken);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<const char*> fileArgs;
|
||||
fileArgs.reserve(fileArgTokens.size());
|
||||
for (const auto& token : fileArgTokens)
|
||||
fileArgs.push_back(token.c_str());
|
||||
fileArgs.push_back(nullptr);
|
||||
|
||||
ParseArgs(static_cast<int>(fileArgs.size()) - 1, (char**)fileArgs.data(), p);
|
||||
}
|
||||
|
||||
if (!CheckArgs(p))
|
||||
return -1;
|
||||
|
||||
// Object mappings
|
||||
std::map<std::string, uint32_t> objMapping;
|
||||
if (p.objExport)
|
||||
if (!p.objMappings.empty())
|
||||
{
|
||||
for (const auto& objToken : p.objMappings)
|
||||
{
|
||||
@@ -375,7 +353,7 @@ int main(int argc, char** argv)
|
||||
foutS << std::endl;
|
||||
}
|
||||
|
||||
if (p.objExport)
|
||||
if (!p.objMappings.empty())
|
||||
{
|
||||
std::vector<uint32_t> objDat;
|
||||
for (size_t i = 0; i < tmx.GetObjectCount(); ++i)
|
||||
|
||||
@@ -75,7 +75,7 @@ void TmxReader::ReadTileset(rapidxml::xml_node<>* aXNode)
|
||||
// Read first global ID
|
||||
xAttrib = aXNode->first_attribute("firstgid");
|
||||
if (xAttrib != nullptr)
|
||||
firstGid = std::stoul(xAttrib->value());
|
||||
firstGid = static_cast<uint32_t>(std::stoul(xAttrib->value()));
|
||||
|
||||
mTilesets.push_back(new TmxTileset(name, source, firstGid));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user