/*
* The Mana Server
* Copyright (C) 2004-2010 The Mana World Development Team
* Copyright (C) 2010-2011 The Mana Development Team
*
* This file is part of The Mana Server.
*
* The Mana Server is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* The Mana Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with The Mana Server. If not, see <http://www.gnu.org/licenses/>.
*/
#include "game-server/mapreader.h"
#include "common/defines.h"
#include "game-server/map.h"
#include "utils/base64.h"
#include "utils/logger.h"
#include "utils/xml.h"
#include "utils/zlib.h"
#include "utils/string.h"
#include <cstring>
static std::vector<unsigned> tilesetFirstGids;
Map *MapReader::readMap(const std::string &filename)
{
XML::Document doc(filename);
xmlNodePtr rootNode = doc.rootNode();
// Parse the inflated map data.
if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "map"))
{
LOG_ERROR("Error: Not a map file (" << filename << ")!");
return false;
}
return readMap(rootNode);
}
Map *MapReader::readMap(xmlNodePtr node)
{
int w = XML::getProperty(node, "width", 0);
int h = XML::getProperty(node, "height", 0);
int tileW = XML::getProperty(node, "tilewidth", DEFAULT_TILE_LENGTH);
int tileH = XML::getProperty(node, "tileheight", DEFAULT_TILE_LENGTH);
Map *map = new Map(w, h, tileW, tileH);
for (node = node->xmlChildrenNode; node != NULL; node = node->next)
{
if (xmlStrEqual(node->name, BAD_CAST "tileset"))
{
if (xmlHasProp(node, BAD_CAST "source"))
{
LOG_WARN("Warning: External tilesets not supported yet.");
}
else
{
::tilesetFirstGids.push_back(XML::getProperty(node, "firstgid",
0));
}
}
else if (xmlStrEqual(node->name, BAD_CAST "properties"))
{
for_each_xml_child_node(propNode, node)
{
if (xmlStrEqual(propNode->name, BAD_CAST "property"))
{
std::string key = XML::getProperty(propNode, "name",
std::string());
std::string val = XML::getProperty(propNode, "value",
std::string());
LOG_DEBUG(" " << key << ": " << val);
map->setProperty(key, val);
}
}
}
else if (xmlStrEqual(node->name, BAD_CAST "layer"))
{
if (utils::compareStrI(XML::getProperty(node, "name", "unnamed"),
"collision") == 0)
{
readLayer(node, map);
}
}
else if (xmlStrEqual(node->name, BAD_CAST "objectgroup"))
{
//readObjectGroup(node, map);
for_each_xml_child_node(objectNode, node)
{
if (!xmlStrEqual(objectNode->name, BAD_CAST "object"))
{
continue;
}
std::string objName = XML::getProperty(objectNode, "name",
std::string());
std::string objType = XML::getProperty(objectNode, "type",
std::string());
objType = utils::toUpper(objType);
int objX = XML::getProperty(objectNode, "x", 0);
int objY = XML::getProperty(objectNode, "y", 0);
int objW = XML::getProperty(objectNode, "width", 0);
int objH = XML::getProperty(objectNode, "height", 0);
Rectangle rect = { objX, objY, objW, objH };
MapObject *newObject = new MapObject(rect, objName, objType);
for_each_xml_child_node(propertiesNode, objectNode)
{
if (!xmlStrEqual(propertiesNode->name, BAD_CAST "properties"))
{
continue;
}
for_each_xml_child_node(propertyNode, propertiesNode)
{
if (xmlStrEqual(propertyNode->name, BAD_CAST "property"))
{
std::string key = XML::getProperty(
propertyNode, "name", std::string());
std::string value = getObjectProperty(propertyNode,
std::string());
newObject->addProperty(key, value);
}
}
}
map->addObject(newObject);
}
}
}
// Clean up tilesets
::tilesetFirstGids.clear();
return map;
}
void MapReader::readLayer(xmlNodePtr node, Map *map)
{
node = node->xmlChildrenNode;
int h = map->getHeight();
int w = map->getWidth();
int x = 0;
int y = 0;
// Layers are assumed to be map size, with (0,0) as origin.
// Find its single "data" element.
while (node)
{
if (xmlStrEqual(node->name, BAD_CAST "data")) break;
node = node->next;
}
if (!node)
{
LOG_WARN("Layer without any 'data' element.");
return;
}
std::string encoding = XML::getProperty(node, "encoding", std::string());
if (encoding == "base64")
{
// Read base64 encoded map file
xmlNodePtr dataChild = node->xmlChildrenNode;
if (!dataChild)
{
LOG_WARN("Corrupted layer.");
return;
}
int len = strlen((const char *) dataChild->content) + 1;
char *charData = new char[len + 1];
const char *charStart = (const char *) dataChild->content;
char *charIndex = charData;
while (*charStart)
{
if (*charStart != ' ' && *charStart != '\t' && *charStart != '\n')
{
*charIndex = *charStart;
++charIndex;
}
++charStart;
}
*charIndex = '\0';
int binLen;
unsigned char *binData =
php_base64_decode((unsigned char *)charData, strlen(charData), &binLen);
delete[] charData;
if (!binData)
{
LOG_WARN("Failed to decode base64-encoded layer.");
return;
}
std::string compression =
XML::getProperty(node, "compression", std::string());
if (compression == "gzip" || compression == "zlib")
{
// Inflate the gzipped layer data
char *inflated;
unsigned inflatedSize;
bool res = inflateMemory((char *)binData, binLen, inflated, inflatedSize);
free(binData);
if (!res)
{
LOG_WARN("Failed to decompress compressed layer");
return;
}
binData = (unsigned char *)inflated;
binLen = inflatedSize;
}
for (int i = 0; i < binLen - 3; i += 4)
{
unsigned gid = binData[i] |
(binData[i + 1] << 8) |
(binData[i + 2] << 16) |
(binData[i + 3] << 24);
setTileWithGid(map, x, y, gid);
if (++x == w)
{
x = 0;
++y;
}
}
free(binData);
return;
}
else if (encoding == "csv")
{
xmlNodePtr dataChild = node->xmlChildrenNode;
if (!dataChild)
return;
const char *data = (const char*) xmlNodeGetContent(dataChild);
std::string csv(data);
size_t pos = 0;
size_t oldPos = 0;
while (oldPos != csv.npos)
{
pos = csv.find_first_of(",", oldPos);
unsigned gid = atol(csv.substr(oldPos, pos - oldPos).c_str());
setTileWithGid(map, x, y, gid);
x++;
if (x == w)
{
x = 0;
++y;
// When we're done, don't crash on too much data
if (y == h)
break;
}
oldPos = pos + 1;
}
}
else
{
// Read plain XML map file
node = node->xmlChildrenNode;
while (node)
{
if (xmlStrEqual(node->name, BAD_CAST "tile") && y < h)
{
unsigned gid = XML::getProperty(node, "gid", 0);
setTileWithGid(map, x, y, gid);
if (++x == w)
{
x = 0;
++y;
}
}
node = node->next;
}
}
}
std::string MapReader::getObjectProperty(xmlNodePtr node,
const std::string &def)
{
if (xmlHasProp(node, BAD_CAST "value"))
{
return XML::getProperty(node, "value", def);
}
else if (const char *prop = (const char *)node->xmlChildrenNode->content)
{
return std::string(prop);
}
return std::string();
}
int MapReader::getObjectProperty(xmlNodePtr node, int def)
{
int val = def;
if (xmlHasProp(node, BAD_CAST "value"))
{
val = XML::getProperty(node, "value", def);
}
else if (const char *prop = (const char *)node->xmlChildrenNode->content)
{
val = atoi(prop);
}
return val;
}
void MapReader::setTileWithGid(Map *map, int x, int y, unsigned gid)
{
// Bits on the far end of the 32-bit global tile ID are used for tile flags
const int FlippedHorizontallyFlag = 0x80000000;
const int FlippedVerticallyFlag = 0x40000000;
const int FlippedAntiDiagonallyFlag = 0x20000000;
// Clear flipping flags, they are not relevant
gid &= ~(FlippedHorizontallyFlag |
FlippedVerticallyFlag |
FlippedAntiDiagonallyFlag);
// Find the tileset with the highest firstGid below/eq to gid
unsigned set = gid;
for (std::vector<unsigned>::const_iterator i = ::tilesetFirstGids.begin(),
i_end = ::tilesetFirstGids.end(); i != i_end; ++i)
{
if (gid < *i)
break;
set = *i;
}
if (gid != set)
map->blockTile(x, y, BLOCKTYPE_WALL);
}