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

port to tmxlite

This commit is contained in:
2024-03-24 17:53:40 +11:00
parent 7b0979e020
commit 35abaf7121
63 changed files with 24374 additions and 4123 deletions

View File

@@ -0,0 +1,15 @@
set(PROJECT_SRC
${PROJECT_DIR}/FreeFuncs.cpp
${PROJECT_DIR}/ImageLayer.cpp
${PROJECT_DIR}/Map.cpp
${PROJECT_DIR}/Object.cpp
${PROJECT_DIR}/ObjectGroup.cpp
${PROJECT_DIR}/Property.cpp
${PROJECT_DIR}/TileLayer.cpp
${PROJECT_DIR}/LayerGroup.cpp
${PROJECT_DIR}/Tileset.cpp
${PROJECT_DIR}/ObjectTypes.cpp)
set(LIB_SRC
${PROJECT_DIR}/miniz.c
${PROJECT_DIR}/detail/pugixml.cpp)

View File

@@ -0,0 +1,133 @@
/*********************************************************************
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_EXTLIBS
#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_EXTLIBS
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

@@ -0,0 +1,110 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#else
#include "detail/pugixml.hpp"
#endif
#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

@@ -0,0 +1,109 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#else
#include "detail/pugixml.hpp"
#endif
#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);
}
}
}

367
ext/tmxlite/src/Map.cpp Normal file
View File

@@ -0,0 +1,367 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#else
#include "detail/pugixml.hpp"
#endif
#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;
}

403
ext/tmxlite/src/Object.cpp Normal file
View File

@@ -0,0 +1,403 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#else
#include "detail/pugixml.hpp"
#endif
#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

@@ -0,0 +1,102 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#else
#include "detail/pugixml.hpp"
#endif
#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

@@ -0,0 +1,154 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#else
#include "detail/pugixml.hpp"
#endif
#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

@@ -0,0 +1,167 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#else
#include "detail/pugixml.hpp"
#endif
#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

@@ -0,0 +1,340 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#include <zstd.h>
#else
#include "detail/pugixml.hpp"
#endif
#ifdef USE_ZSTD
#include <zstd.h>
#endif
#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 || defined USE_EXTLIBS
{
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);
}
}
#else
Logger::log("Library must be built with USE_EXTLIBS or USE_ZSTD for Zstd compression", Logger::Type::Error);
return {};
#endif
case CompressionType::GZip:
#ifndef USE_EXTLIBS
Logger::log("Library must be built with USE_EXTLIBS 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;
}
}

460
ext/tmxlite/src/Tileset.cpp Normal file
View File

@@ -0,0 +1,460 @@
/*********************************************************************
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.
*********************************************************************/
#ifdef USE_EXTLIBS
#include <pugixml.hpp>
#else
#include "detail/pugixml.hpp"
#endif
#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

@@ -0,0 +1,75 @@
/**
* pugixml parser - version 1.7
* --------------------------------------------------------
* Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at http://pugixml.org/
*
* This library is distributed under the MIT License. See notice at the end
* of this file.
*
* This work is based on the pugxml parser, which is:
* Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
*/
#ifndef HEADER_PUGICONFIG_HPP
#define HEADER_PUGICONFIG_HPP
// Uncomment this to enable wchar_t mode
// #define PUGIXML_WCHAR_MODE
// Uncomment this to enable compact mode
// #define PUGIXML_COMPACT
// Uncomment this to disable XPath
// #define PUGIXML_NO_XPATH
#ifdef __ANDROID__
// Uncomment this to disable STL
#define PUGIXML_NO_STL
// Uncomment this to disable exceptions
#define PUGIXML_NO_EXCEPTIONS
#endif //__ANDROID__
// Set this to control attributes for public classes/functions, i.e.:
// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL
// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL
// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall
// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead
// Tune these constants to adjust memory-related behavior
// #define PUGIXML_MEMORY_PAGE_SIZE 32768
// #define PUGIXML_MEMORY_OUTPUT_STACK 10240
// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096
// Uncomment this to switch to header-only version
//#define PUGIXML_HEADER_ONLY
// Uncomment this to enable long long support
// #define PUGIXML_HAS_LONG_LONG
#endif
/**
* Copyright (c) 2006-2015 Arseny Kapoulkine
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

View File

@@ -0,0 +1,34 @@
/**
* pugixml parser - version 1.7
* --------------------------------------------------------
* Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at http://pugixml.org/
*
* This library is distributed under the MIT License.
*
* This work is based on the pugxml parser, which is:
* Copyright (C) 2003, by Kristen Wegner (kristen@tima.net)
*
*
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,59 @@
if get_option('use_extlibs')
tmxlite_lib = library(meson.project_name() + binary_postfix,
'FreeFuncs.cpp',
'ImageLayer.cpp',
'Map.cpp',
'Object.cpp',
'ObjectGroup.cpp',
'Property.cpp',
'TileLayer.cpp',
'LayerGroup.cpp',
'Tileset.cpp',
install: true,
include_directories: incdir,
dependencies: [zdep, pugidep, zstddep]
)
else
if get_option('use_zstd')
tmxlite_lib = library(meson.project_name() + binary_postfix,
'detail/pugixml.cpp',
'FreeFuncs.cpp',
'ImageLayer.cpp',
'Map.cpp',
'miniz.c',
'Object.cpp',
'ObjectGroup.cpp',
'Property.cpp',
'TileLayer.cpp',
'LayerGroup.cpp',
'Tileset.cpp',
install: true,
include_directories: incdir,
dependencies: zstddep
)
else
tmxlite_lib = library(meson.project_name() + binary_postfix,
'detail/pugixml.cpp',
'FreeFuncs.cpp',
'ImageLayer.cpp',
'Map.cpp',
'miniz.c',
'Object.cpp',
'ObjectGroup.cpp',
'Property.cpp',
'TileLayer.cpp',
'LayerGroup.cpp',
'Tileset.cpp',
install: true,
include_directories: incdir,
)
endif
endif
tmxlite_dep = declare_dependency(
link_with: tmxlite_lib,
include_directories: incdir,
)

4916
ext/tmxlite/src/miniz.c Normal file

File diff suppressed because it is too large Load Diff

8
ext/tmxlite/src/miniz.h Normal file
View File

@@ -0,0 +1,8 @@
/*
miniz public domain replacement for zlib. See miniz.c
for more information.
*/
#ifndef MINIZ_HEADER_FILE_ONLY
#define MINIZ_HEADER_FILE_ONLY
#include "miniz.c"
#endif //MINIZ_HEADER_FILE_ONLY