tibia-rme/source/iomap_otbm.cpp
lgrossi 37ad3b4047 Simplify kmap saver to use library interfaces
We want RME code to be de-atached from flatbuffers, kmap and the kmap library. For that we've introduced a interface based library and we've implemented the interfaces in RME side (some of them were just incorporated to RME basic entities, like position).

That way we could remove a lot of boiler plate check, simplify the logics in the save and delegate some validations to the new domain objects.
2022-08-26 21:20:28 +02:00

1842 lines
50 KiB
C++

//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor 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 3 of the License, or
// (at your option) any later version.
//
// Remere's Map Editor 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 this program. If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////
#include "main.h"
#include <wx/wfstream.h>
#include <wx/tarstrm.h>
#include <wx/zstream.h>
#include <wx/mstream.h>
#include <wx/datstrm.h>
#include "settings.h"
#include "gui.h" // Loadbar
#include "monsters.h"
#include "monster.h"
#include "npcs.h"
#include "npc.h"
#include "map.h"
#include "tile.h"
#include "item.h"
#include "complexitem.h"
#include "town.h"
#include "iomap_otbm.h"
#include "save_kmap.hpp"
typedef uint8_t attribute_t;
typedef uint32_t flags_t;
// H4X
void reform(Map* map, Tile* tile, Item* item)
{
/*
int aid = item->getActionID();
int id = item->getID();
int uid = item->getUniqueID();
if(item->isDoor()) {
item->eraseAttribute("aid");
item->setAttribute("keyid", aid);
}
if((item->isDoor()) && tile && tile->getHouseID()) {
Door* self = static_cast<Door*>(item);
House* house = map->houses.getHouse(tile->getHouseID());
self->setDoorID(house->getEmptyDoorID());
}
*/
}
// ============================================================================
// Item
Item* Item::Create_OTBM(const IOMap& maphandle, BinaryNode* stream)
{
uint16_t _id;
if(!stream->getU16(_id)) {
return nullptr;
}
uint8_t _count = 0;
const ItemType& iType = g_items[_id];
if(maphandle.version.otbm == MAP_OTBM_1) {
if(iType.stackable || iType.isSplash() || iType.isFluidContainer()) {
stream->getU8(_count);
}
}
return Item::Create(_id, _count);
}
bool Item::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attr, BinaryNode* stream)
{
switch (attr) {
case OTBM_ATTR_COUNT: {
uint8_t subtype;
if(!stream->getU8(subtype)) {
return false;
}
setSubtype(subtype);
break;
}
case OTBM_ATTR_ACTION_ID: {
uint16_t aid;
if(!stream->getU16(aid)) {
return false;
}
setActionID(aid);
break;
}
case OTBM_ATTR_UNIQUE_ID: {
uint16_t uid;
if(!stream->getU16(uid)) {
return false;
}
setUniqueID(uid);
break;
}
case OTBM_ATTR_CHARGES: {
uint16_t charges;
if(!stream->getU16(charges)) {
return false;
}
setSubtype(charges);
break;
}
case OTBM_ATTR_TEXT: {
std::string text;
if(!stream->getString(text)) {
return false;
}
setText(text);
break;
}
case OTBM_ATTR_DESC: {
std::string text;
if(!stream->getString(text)) {
return false;
}
setDescription(text);
break;
}
case OTBM_ATTR_RUNE_CHARGES: {
uint8_t subtype;
if(!stream->getU8(subtype)) {
return false;
}
setSubtype(subtype);
break;
}
// The following *should* be handled in the derived classes
// However, we still need to handle them here since otherwise things
// will break horribly
case OTBM_ATTR_DEPOT_ID: return stream->skip(2);
case OTBM_ATTR_HOUSEDOORID: return stream->skip(1);
case OTBM_ATTR_TELE_DEST: return stream->skip(5);
default: return false;
}
return true;
}
bool Item::unserializeAttributes_OTBM(const IOMap& maphandle, BinaryNode* stream)
{
uint8_t attribute;
while(stream->getU8(attribute)) {
if(attribute == OTBM_ATTR_ATTRIBUTE_MAP) {
if(!ItemAttributes::unserializeAttributeMap(maphandle, stream)) {
return false;
}
} else if(!readItemAttribute_OTBM(maphandle, static_cast<OTBM_ItemAttribute>(attribute), stream)) {
return false;
}
}
return true;
}
bool Item::unserializeItemNode_OTBM(const IOMap& maphandle, BinaryNode* node)
{
return unserializeAttributes_OTBM(maphandle, node);
}
void Item::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const
{
if(maphandle.version.otbm >= MAP_OTBM_2) {
const ItemType& iType = g_items[id];
if(iType.stackable || iType.isSplash() || iType.isFluidContainer()) {
stream.addU8(OTBM_ATTR_COUNT);
stream.addU8(getSubtype());
}
}
if(maphandle.version.otbm >= MAP_OTBM_4) {
if(attributes && !attributes->empty()) {
stream.addU8(OTBM_ATTR_ATTRIBUTE_MAP);
serializeAttributeMap(maphandle, stream);
}
} else {
if(g_items.MinorVersion >= CLIENT_VERSION_820 && isCharged()) {
stream.addU8(OTBM_ATTR_CHARGES);
stream.addU16(getSubtype());
}
uint16_t actionId = getActionID();
if(actionId > 0) {
stream.addU8(OTBM_ATTR_ACTION_ID);
stream.addU16(actionId);
}
uint16_t uniqueId = getUniqueID();
if(uniqueId > 0) {
stream.addU8(OTBM_ATTR_UNIQUE_ID);
stream.addU16(uniqueId);
}
const std::string& text = getText();
if(!text.empty()) {
stream.addU8(OTBM_ATTR_TEXT);
stream.addString(text);
}
const std::string& description = getDescription();
if(!description.empty()) {
stream.addU8(OTBM_ATTR_DESC);
stream.addString(description);
}
}
}
void Item::serializeItemCompact_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const
{
stream.addU16(id);
/* This is impossible
const ItemType& iType = g_items[id];
if(iType.stackable || iType.isSplash() || iType.isFluidContainer()){
stream.addU8(getSubtype());
}
*/
}
bool Item::serializeItemNode_OTBM(const IOMap& maphandle, NodeFileWriteHandle& file) const
{
file.addNode(OTBM_ITEM);
file.addU16(id);
if(maphandle.version.otbm == MAP_OTBM_1) {
const ItemType& iType = g_items[id];
if(iType.stackable || iType.isSplash() || iType.isFluidContainer()) {
file.addU8(getSubtype());
}
}
serializeItemAttributes_OTBM(maphandle, file);
file.endNode();
return true;
}
// ============================================================================
// Teleport
bool Teleport::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attribute, BinaryNode* stream)
{
if(OTBM_ATTR_TELE_DEST == attribute) {
uint16_t x, y;
uint8_t z;
if(!stream->getU16(x) || !stream->getU16(y) || !stream->getU8(z)) {
return false;
}
destination = Position(x, y, z);
return true;
} else {
return Item::readItemAttribute_OTBM(maphandle, attribute, stream);
}
}
void Teleport::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const
{
Item::serializeItemAttributes_OTBM(maphandle, stream);
stream.addByte(OTBM_ATTR_TELE_DEST);
stream.addU16(destination.x);
stream.addU16(destination.y);
stream.addU8(destination.z);
}
// ============================================================================
// Door
bool Door::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attribute, BinaryNode* stream)
{
if(OTBM_ATTR_HOUSEDOORID == attribute) {
uint8_t id = 0;
if(!stream->getU8(id)) {
return false;
}
doorId = id;
return true;
} else {
return Item::readItemAttribute_OTBM(maphandle, attribute, stream);
}
}
void Door::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const
{
Item::serializeItemAttributes_OTBM(maphandle, stream);
if(doorId) {
stream.addByte(OTBM_ATTR_HOUSEDOORID);
stream.addU8(doorId);
}
}
// ============================================================================
// Depots
bool Depot::readItemAttribute_OTBM(const IOMap& maphandle, OTBM_ItemAttribute attribute, BinaryNode* stream)
{
if(OTBM_ATTR_DEPOT_ID == attribute) {
uint16_t id = 0;
if(!stream->getU16(id)) {
return false;
}
depotId = id;
return true;
} else {
return Item::readItemAttribute_OTBM(maphandle, attribute, stream);
}
}
void Depot::serializeItemAttributes_OTBM(const IOMap& maphandle, NodeFileWriteHandle& stream) const
{
Item::serializeItemAttributes_OTBM(maphandle, stream);
if(depotId) {
stream.addByte(OTBM_ATTR_DEPOT_ID);
stream.addU16(depotId);
}
}
// ============================================================================
// Container
bool Container::unserializeItemNode_OTBM(const IOMap& maphandle, BinaryNode* node)
{
if(!Item::unserializeAttributes_OTBM(maphandle, node)) {
return false;
}
BinaryNode* child = node->getChild();
if(child) {
do {
uint8_t type;
if(!child->getByte(type)) {
return false;
}
if(type != OTBM_ITEM) {
return false;
}
Item* item = Item::Create_OTBM(maphandle, child);
if(!item) {
return false;
}
if(!item->unserializeItemNode_OTBM(maphandle, child)) {
delete item;
return false;
}
contents.push_back(item);
} while(child->advance());
}
return true;
}
bool Container::serializeItemNode_OTBM(const IOMap& maphandle, NodeFileWriteHandle& file) const
{
file.addNode(OTBM_ITEM);
file.addU16(id);
if(maphandle.version.otbm == MAP_OTBM_1) {
// In the ludicrous event that an item is a container AND stackable, we have to do this. :p
const ItemType& iType = g_items[id];
if(iType.stackable || iType.isSplash() || iType.isFluidContainer()) {
file.addU8(getSubtype());
}
}
serializeItemAttributes_OTBM(maphandle, file);
for(Item* item : contents) {
item->serializeItemNode_OTBM(maphandle, file);
}
file.endNode();
return true;
}
/*
OTBM_ROOTV1
|
|--- OTBM_MAP_DATA
| |
| |--- OTBM_TILE_AREA
| | |--- OTBM_TILE
| | |--- OTBM_TILE_SQUARE (not implemented)
| | |--- OTBM_TILE_REF (not implemented)
| | |--- OTBM_HOUSETILE
| |
| |--- OTBM_SPAWNS (not implemented)
| | |--- OTBM_SPAWN_AREA (not implemented)
| | |--- OTBM_MONSTER (not implemented)
| |
| |--- OTBM_TOWNS
| |--- OTBM_TOWN
|
|--- OTBM_ITEM_DEF (not implemented)
*/
bool IOMapOTBM::getVersionInfo(const FileName& filename, MapVersion& out_ver)
{
#ifdef OTGZ_SUPPORT
if(filename.GetExt() == "otgz") {
// Open the archive
std::shared_ptr<struct archive> a(archive_read_new(), archive_read_free);
archive_read_support_filter_all(a.get());
archive_read_support_format_all(a.get());
if(archive_read_open_filename(a.get(), nstr(filename.GetFullPath()).c_str(), 10240) != ARCHIVE_OK)
return false;
// Loop over the archive entries until we find the otbm file
struct archive_entry* entry;
while(archive_read_next_header(a.get(), &entry) == ARCHIVE_OK) {
std::string entryName = archive_entry_pathname(entry);
if(entryName == "world/map.otbm") {
// Read the OTBM header into temporary memory
uint8_t buffer[8096];
memset(buffer, 0, 8096);
// Read from the archive
int read_bytes = archive_read_data(a.get(), buffer, 8096);
// Check so it at least contains the 4-byte file id
if(read_bytes < 4)
return false;
// Create a read handle on it
std::shared_ptr<NodeFileReadHandle> f(new MemoryNodeFileReadHandle(buffer + 4, read_bytes - 4));
// Read the version info
return getVersionInfo(f.get(), out_ver);
}
}
// Didn't find OTBM file, lame
return false;
}
#endif
// Just open a disk-based read handle
DiskNodeFileReadHandle f(nstr(filename.GetFullPath()), StringVector(1, "OTBM"));
if(!f.isOk())
return false;
return getVersionInfo(&f, out_ver);
}
bool IOMapOTBM::getVersionInfo(NodeFileReadHandle* f, MapVersion& out_ver)
{
BinaryNode* root = f->getRootNode();
if(!root)
return false;
root->skip(1); // Skip the type byte
uint16_t u16;
uint32_t u32;
if(!root->getU32(u32)) // Version
return false;
out_ver.otbm = (MapVersionID)u32;
root->getU16(u16);
root->getU16(u16);
root->getU32(u32);
if(!root->getU32(u32)) // OTB minor version
return false;
out_ver.client = ClientVersionID(u32);
return true;
}
bool IOMapOTBM::loadMap(Map& map, const FileName& filename)
{
#ifdef OTGZ_SUPPORT
if(filename.GetExt() == "otgz") {
// Open the archive
std::shared_ptr<struct archive> a(archive_read_new(), archive_read_free);
archive_read_support_filter_all(a.get());
archive_read_support_format_all(a.get());
if(archive_read_open_filename(a.get(), nstr(filename.GetFullPath()).c_str(), 10240) != ARCHIVE_OK)
return false;
// Memory buffers for the houses & monsters & npcs
std::shared_ptr<uint8_t> house_buffer;
std::shared_ptr<uint8_t> spawn_monster_buffer;
std::shared_ptr<uint8_t> spawn_npc_buffer;
size_t house_buffer_size = 0;
size_t spawn_monster_buffer_size = 0;
size_t spawn_npc_buffer_size = 0;
// See if the otbm file has been loaded
bool otbm_loaded = false;
// Loop over the archive entries until we find the otbm file
g_gui.SetLoadDone(0, "Decompressing archive...");
struct archive_entry* entry;
while(archive_read_next_header(a.get(), &entry) == ARCHIVE_OK) {
std::string entryName = archive_entry_pathname(entry);
if(entryName == "world/map.otbm") {
// Read the entire OTBM file into a memory region
size_t otbm_size = archive_entry_size(entry);
std::shared_ptr<uint8_t> otbm_buffer(new uint8_t[otbm_size], [](uint8_t* p) { delete[] p; });
// Read from the archive
size_t read_bytes = archive_read_data(a.get(), otbm_buffer.get(), otbm_size);
// Check so it at least contains the 4-byte file id
if(read_bytes < 4)
return false;
if(read_bytes < otbm_size) {
error("Could not read file.");
return false;
}
g_gui.SetLoadDone(0, "Loading OTBM map...");
// Create a read handle on it
std::shared_ptr<NodeFileReadHandle> f(
new MemoryNodeFileReadHandle(otbm_buffer.get() + 4, otbm_size - 4));
// Read the version info
if(!loadMap(map, *f.get())) {
error("Could not load OTBM file inside archive");
return false;
}
otbm_loaded = true;
} else if(entryName == "world/houses.xml") {
house_buffer_size = archive_entry_size(entry);
house_buffer.reset(new uint8_t[house_buffer_size]);
// Read from the archive
size_t read_bytes = archive_read_data(a.get(), house_buffer.get(), house_buffer_size);
// Check so it at least contains the 4-byte file id
if(read_bytes < house_buffer_size) {
house_buffer.reset();
house_buffer_size = 0;
warning("Failed to decompress houses.");
}
} else if(entryName == "world/monsters.xml") {
spawn_monster_buffer_size = archive_entry_size(entry);
spawn_monster_buffer.reset(new uint8_t[spawn_monster_buffer_size]);
// Read from the archive
size_t read_bytes = archive_read_data(a.get(), spawn_monster_buffer.get(), spawn_monster_buffer_size);
// Check so it at least contains the 4-byte file id
if(read_bytes < spawn_monster_buffer_size) {
spawn_monster_buffer.reset();
spawn_monster_buffer_size = 0;
warning("Failed to decompress monsters spawns.");
}
} else if(entryName == "world/npcs.xml") {
spawn_npc_buffer_size = archive_entry_size(entry);
spawn_npc_buffer.reset(new uint8_t[spawn_npc_buffer_size]);
// Read from the archive
size_t read_bytes = archive_read_data(a.get(), spawn_npc_buffer.get(), spawn_npc_buffer_size);
// Check so it at least contains the 4-byte file id
if(read_bytes < spawn_npc_buffer_size) {
spawn_npc_buffer.reset();
spawn_npc_buffer_size = 0;
warning("Failed to decompress npcs spawns.");
}
}
}
if(!otbm_loaded) {
error("OTBM file not found inside archive.");
return false;
}
// Load the houses from the stored buffer
if(house_buffer.get() && house_buffer_size > 0) {
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_buffer(house_buffer.get(), house_buffer_size);
if(result) {
if(!loadHouses(map, doc)) {
warning("Failed to load houses.");
}
} else {
warning("Failed to load houses due to XML parse error.");
}
}
// Load the monster spawns from the stored buffer
if(spawn_monster_buffer.get() && spawn_monster_buffer_size > 0) {
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_buffer(spawn_monster_buffer.get(), spawn_monster_buffer_size);
if(result) {
if(!loadSpawnsMonster(map, doc)) {
warning("Failed to load monsters spawns.");
}
} else {
warning("Failed to load monsters spawns due to XML parse error.");
}
}
// Load the npcs from the stored buffer
if(spawn_npc_buffer.get() && spawn_npc_buffer_size > 0) {
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_buffer(spawn_npc_buffer.get(), spawn_npc_buffer_size);
if(result) {
if(!loadSpawnsNpc(map, doc)) {
warning("Failed to load npcs spawns.");
}
} else {
warning("Failed to load npcs spawns due to XML parse error.");
}
}
return true;
}
#endif
DiskNodeFileReadHandle f(nstr(filename.GetFullPath()), StringVector(1, "OTBM"));
if(!f.isOk()) {
error(("Couldn't open file for reading\nThe error reported was: " + wxstr(f.getErrorMessage())).wc_str());
return false;
}
if(!loadMap(map, f))
return false;
// Read auxilliary files
if(!loadHouses(map, filename)) {
warning("Failed to load houses.");
map.housefile = nstr(filename.GetName()) + "-house.xml";
}
if(!loadSpawnsMonster(map, filename)) {
warning("Failed to load monsters spawns.");
map.spawnmonsterfile = nstr(filename.GetName()) + "-monster.xml";
}
if(!loadSpawnsNpc(map, filename)) {
warning("Failed to load npcs spawns.");
map.spawnnpcfile = nstr(filename.GetName()) + "-npc.xml";
}
return true;
}
bool IOMapOTBM::loadMap(Map& map, NodeFileReadHandle& f)
{
BinaryNode* root = f.getRootNode();
if(!root) {
error("Could not read root node.");
return false;
}
root->skip(1); // Skip the type byte
uint8_t u8;
uint16_t u16;
uint32_t u32;
if(!root->getU32(u32))
return false;
version.otbm = (MapVersionID) u32;
if(version.otbm > MAP_OTBM_4) {
// Failed to read version
if(g_gui.PopupDialog("Map error",
"The loaded map appears to be a OTBM format that is not supported by the editor."
"Do you still want to attempt to load the map?", wxYES | wxNO) == wxID_YES)
{
warning("Unsupported or damaged map version");
} else {
error("Unsupported OTBM version, could not load map");
return false;
}
}
if(!root->getU16(u16))
return false;
map.width = u16;
if(!root->getU16(u16))
return false;
map.height = u16;
if(!root->getU32(u32) || u32 > (unsigned long)g_items.MajorVersion) { // OTB major version
if(g_gui.PopupDialog("Map error",
"The loaded map appears to be a items.otb format that deviates from the "
"items.otb loaded by the editor. Do you still want to attempt to load the map?", wxYES | wxNO) == wxID_YES)
{
warning("Unsupported or damaged map version");
} else {
error("Outdated items.otb, could not load map");
return false;
}
}
if(!root->getU32(u32) || u32 > (unsigned long)g_items.MinorVersion) { // OTB minor version
warning("This editor needs an updated items.otb version");
}
version.client = (ClientVersionID)u32;
BinaryNode* mapHeaderNode = root->getChild();
if(mapHeaderNode == nullptr || !mapHeaderNode->getByte(u8) || u8 != OTBM_MAP_DATA) {
error("Could not get root child node. Cannot recover from fatal error!");
return false;
}
uint8_t attribute;
while(mapHeaderNode->getU8(attribute)) {
switch(attribute) {
case OTBM_ATTR_DESCRIPTION: {
if(!mapHeaderNode->getString(map.description)) {
warning("Invalid map description tag");
}
//std::cout << "Map description: " << mapDescription << std::endl;
break;
}
case OTBM_ATTR_EXT_SPAWN_MONSTER_FILE: {
if(!mapHeaderNode->getString(map.spawnmonsterfile)) {
warning("Invalid map spawnmonsterfile tag");
}
break;
}
case OTBM_ATTR_EXT_HOUSE_FILE: {
if(!mapHeaderNode->getString(map.housefile)) {
warning("Invalid map housefile tag");
}
break;
}
case OTBM_ATTR_EXT_SPAWN_NPC_FILE: {
if(!mapHeaderNode->getString(map.spawnnpcfile)) {
warning("Invalid map spawnnpcfile tag");
}
break;
}
default: {
warning("Unknown header node.");
break;
}
}
}
int nodes_loaded = 0;
for(BinaryNode* mapNode = mapHeaderNode->getChild(); mapNode != nullptr; mapNode = mapNode->advance()) {
++nodes_loaded;
if(nodes_loaded % 15 == 0) {
g_gui.SetLoadDone(static_cast<int32_t>(100.0 * f.tell() / f.size()));
}
uint8_t node_type;
if(!mapNode->getByte(node_type)) {
warning("Invalid map node");
continue;
}
if(node_type == OTBM_TILE_AREA) {
uint16_t base_x, base_y;
uint8_t base_z;
if(!mapNode->getU16(base_x) || !mapNode->getU16(base_y) || !mapNode->getU8(base_z)) {
warning("Invalid map node, no base coordinate");
continue;
}
for(BinaryNode* tileNode = mapNode->getChild(); tileNode != nullptr; tileNode = tileNode->advance()) {
Tile* tile = nullptr;
uint8_t tile_type;
if(!tileNode->getByte(tile_type)) {
warning("Invalid tile type");
continue;
}
if(tile_type == OTBM_TILE || tile_type == OTBM_HOUSETILE) {
//printf("Start\n");
uint8_t x_offset, y_offset;
if(!tileNode->getU8(x_offset) || !tileNode->getU8(y_offset)) {
warning("Could not read position of tile");
continue;
}
const Position pos(base_x + x_offset, base_y + y_offset, base_z);
if(map.getTile(pos)) {
warning("Duplicate tile at %d:%d:%d, discarding duplicate", pos.x, pos.y, pos.z);
continue;
}
tile = map.allocator(map.createTileL(pos));
House* house = nullptr;
if(tile_type == OTBM_HOUSETILE) {
uint32_t house_id;
if(!tileNode->getU32(house_id)) {
warning("House tile without house data, discarding tile");
continue;
}
if(house_id) {
house = map.houses.getHouse(house_id);
if(!house) {
house = newd House(map);
house->id = house_id;
map.houses.addHouse(house);
}
} else {
warning("Invalid house id from tile %d:%d:%d", pos.x, pos.y, pos.z);
}
}
//printf("So far so good\n");
uint8_t attribute;
while(tileNode->getU8(attribute)) {
switch(attribute) {
case OTBM_ATTR_TILE_FLAGS: {
uint32_t flags = 0;
if(!tileNode->getU32(flags)) {
warning("Invalid tile flags of tile on %d:%d:%d", pos.x, pos.y, pos.z);
}
tile->setMapFlags(flags);
break;
}
case OTBM_ATTR_ITEM: {
Item* item = Item::Create_OTBM(*this, tileNode);
if(item == nullptr)
{
warning("Invalid item at tile %d:%d:%d", pos.x, pos.y, pos.z);
}
tile->addItem(item);
break;
}
default: {
warning("Unknown tile attribute at %d:%d:%d", pos.x, pos.y, pos.z);
break;
}
}
}
//printf("Didn't die in loop\n");
for(BinaryNode* itemNode = tileNode->getChild(); itemNode != nullptr; itemNode = itemNode->advance()) {
Item* item = nullptr;
uint8_t item_type;
if(!itemNode->getByte(item_type)) {
warning("Unknown item type %d:%d:%d", pos.x, pos.y, pos.z);
continue;
}
if(item_type == OTBM_ITEM) {
item = Item::Create_OTBM(*this, itemNode);
if(item) {
if(!item->unserializeItemNode_OTBM(*this, itemNode)) {
warning("Couldn't unserialize item attributes at %d:%d:%d", pos.x, pos.y, pos.z);
}
//reform(&map, tile, item);
tile->addItem(item);
}
} else {
warning("Unknown type of tile child node");
}
}
tile->update();
if(house)
house->addTile(tile);
map.setTile(pos.x, pos.y, pos.z, tile);
} else {
warning("Unknown type of tile node");
}
}
} else if(node_type == OTBM_TOWNS) {
for(BinaryNode* townNode = mapNode->getChild(); townNode != nullptr; townNode = townNode->advance()) {
Town* town = nullptr;
uint8_t town_type;
if(!townNode->getByte(town_type)) {
warning("Invalid town type (1)");
continue;
}
if(town_type != OTBM_TOWN) {
warning("Invalid town type (2)");
continue;
}
uint32_t town_id;
if(!townNode->getU32(town_id)) {
warning("Invalid town id");
continue;
}
town = map.towns.getTown(town_id);
if(town) {
warning("Duplicate town id %d, discarding duplicate", town_id);
continue;
} else {
town = newd Town(town_id);
if(!map.towns.addTown(town)) {
delete town;
continue;
}
}
std::string town_name;
if(!townNode->getString(town_name)) {
warning("Invalid town name");
continue;
}
town->setName(town_name);
Position pos;
uint16_t x;
uint16_t y;
uint8_t z;
if(!townNode->getU16(x) || !townNode->getU16(y) || !townNode->getU8(z)) {
warning("Invalid town temple position");
continue;
}
pos.x = x;
pos.y = y;
pos.z = z;
town->setTemplePosition(pos);
}
} else if(node_type == OTBM_WAYPOINTS) {
for(BinaryNode* waypointNode = mapNode->getChild(); waypointNode != nullptr; waypointNode = waypointNode->advance()) {
uint8_t waypoint_type;
if(!waypointNode->getByte(waypoint_type)) {
warning("Invalid waypoint type (1)");
continue;
}
if(waypoint_type != OTBM_WAYPOINT) {
warning("Invalid waypoint type (2)");
continue;
}
Waypoint wp;
if(!waypointNode->getString(wp.name)) {
warning("Invalid waypoint name");
continue;
}
uint16_t x;
uint16_t y;
uint8_t z;
if(!waypointNode->getU16(x) || !waypointNode->getU16(y) || !waypointNode->getU8(z)) {
warning("Invalid waypoint position");
continue;
}
wp.pos.x = x;
wp.pos.y = y;
wp.pos.z = z;
map.waypoints.addWaypoint(newd Waypoint(wp));
}
}
}
if(!f.isOk())
warning(wxstr(f.getErrorMessage()).wc_str());
return true;
}
bool IOMapOTBM::loadSpawnsMonster(Map& map, const FileName& dir)
{
std::string fn = (const char*)(dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME).mb_str(wxConvUTF8));
fn += map.spawnmonsterfile;
FileName filename(wxstr(fn));
if(!filename.FileExists())
return false;
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(fn.c_str());
if(!result) {
return false;
}
return loadSpawnsMonster(map, doc);
}
bool IOMapOTBM::loadSpawnsMonster(Map& map, pugi::xml_document& doc)
{
pugi::xml_node node = doc.child("monsters");
if(!node) {
warnings.push_back("IOMapOTBM::loadSpawnsMonster: Invalid rootheader.");
return false;
}
for(pugi::xml_node spawnNode = node.first_child(); spawnNode; spawnNode = spawnNode.next_sibling()) {
if(as_lower_str(spawnNode.name()) != "monster") {
continue;
}
Position spawnPosition;
spawnPosition.x = spawnNode.attribute("centerx").as_int();
spawnPosition.y = spawnNode.attribute("centery").as_int();
spawnPosition.z = spawnNode.attribute("centerz").as_int();
if(spawnPosition.x == 0 || spawnPosition.y == 0) {
warning("Bad position data on one monster spawn, discarding...");
continue;
}
int32_t radius = spawnNode.attribute("radius").as_int();
if(radius < 1) {
warning("Couldn't read radius of monster spawn.. discarding spawn...");
continue;
}
Tile* tile = map.getTile(spawnPosition);
if(tile && tile->spawnMonster) {
warning("Duplicate monster spawn on position %d:%d:%d\n", tile->getX(), tile->getY(), tile->getZ());
continue;
}
SpawnMonster* spawnMonster = newd SpawnMonster(radius);
if(!tile) {
tile = map.allocator(map.createTileL(spawnPosition));
map.setTile(spawnPosition, tile);
}
tile->spawnMonster = spawnMonster;
map.addSpawnMonster(tile);
for(pugi::xml_node monsterNode = spawnNode.first_child(); monsterNode; monsterNode = monsterNode.next_sibling()) {
const std::string& monsterNodeName = as_lower_str(monsterNode.name());
if(monsterNodeName != "monster") {
continue;
}
const std::string& name = monsterNode.attribute("name").as_string();
if(name.empty()) {
wxString err;
err << "Bad monster position data, discarding monster at spawn " << spawnPosition.x << ":" << spawnPosition.y << ":" << spawnPosition.z << " due missing name.";
warnings.Add(err);
break;
}
int32_t spawntime = monsterNode.attribute("spawntime").as_int();
if(spawntime == 0) {
spawntime = g_settings.getInteger(Config::DEFAULT_SPAWN_MONSTER_TIME);
}
Direction direction = NORTH;
int dir = monsterNode.attribute("direction").as_int(-1);
if(dir >= DIRECTION_FIRST && dir <= DIRECTION_LAST) {
direction = (Direction)dir;
}
Position monsterPosition(spawnPosition);
pugi::xml_attribute xAttribute = monsterNode.attribute("x");
pugi::xml_attribute yAttribute = monsterNode.attribute("y");
if(!xAttribute || !yAttribute) {
wxString err;
err << "Bad monster position data, discarding monster \"" << name << "\" at spawn " << monsterPosition.x << ":" << monsterPosition.y << ":" << monsterPosition.z << " due to invalid position.";
warnings.Add(err);
break;
}
monsterPosition.x += xAttribute.as_int();
monsterPosition.y += yAttribute.as_int();
radius = std::max<int32_t>(radius, std::abs(monsterPosition.x - spawnPosition.x));
radius = std::max<int32_t>(radius, std::abs(monsterPosition.y - spawnPosition.y));
radius = std::min<int32_t>(radius, g_settings.getInteger(Config::MAX_SPAWN_MONSTER_RADIUS));
Tile* monsterTile;
if(monsterPosition == spawnPosition) {
monsterTile = tile;
} else {
monsterTile = map.getTile(monsterPosition);
}
if(!monsterTile) {
wxString err;
err << "Discarding monster \"" << name << "\" at " << monsterPosition.x << ":" << monsterPosition.y << ":" << monsterPosition.z << " due to invalid position.";
warnings.Add(err);
break;
}
if(monsterTile->monster) {
wxString err;
err << "Duplicate monster \"" << name << "\" at " << monsterPosition.x << ":" << monsterPosition.y << ":" << monsterPosition.z << " was discarded.";
warnings.Add(err);
break;
}
MonsterType* type = g_monsters[name];
if(!type) {
type = g_monsters.addMissingMonsterType(name);
}
Monster* monster = newd Monster(type);
monster->setDirection(direction);
monster->setSpawnMonsterTime(spawntime);
monsterTile->monster = monster;
if(monsterTile->getLocation()->getSpawnMonsterCount() == 0) {
// No monster spawn, create a newd one
ASSERT(monsterTile->spawnMonster == nullptr);
SpawnMonster* spawnMonster = newd SpawnMonster(1);
monsterTile->spawnMonster = spawnMonster;
map.addSpawnMonster(monsterTile);
}
}
}
return true;
}
bool IOMapOTBM::loadHouses(Map& map, const FileName& dir)
{
std::string fn = (const char*)(dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME).mb_str(wxConvUTF8));
fn += map.housefile;
FileName filename(wxstr(fn));
if(!filename.FileExists())
return false;
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(fn.c_str());
if(!result) {
return false;
}
return loadHouses(map, doc);
}
bool IOMapOTBM::loadHouses(Map& map, pugi::xml_document& doc)
{
pugi::xml_node node = doc.child("houses");
if(!node) {
warnings.push_back("IOMapOTBM::loadHouses: Invalid rootheader.");
return false;
}
pugi::xml_attribute attribute;
for(pugi::xml_node houseNode = node.first_child(); houseNode; houseNode = houseNode.next_sibling()) {
if(as_lower_str(houseNode.name()) != "house") {
continue;
}
House* house = nullptr;
if((attribute = houseNode.attribute("houseid"))) {
house = map.houses.getHouse(attribute.as_uint());
if(!house) {
break;
}
}
if((attribute = houseNode.attribute("name"))) {
house->name = attribute.as_string();
} else {
house->name = "House #" + std::to_string(house->id);
}
Position exitPosition(
houseNode.attribute("entryx").as_int(),
houseNode.attribute("entryy").as_int(),
houseNode.attribute("entryz").as_int()
);
if(exitPosition.x != 0 && exitPosition.y != 0 && exitPosition.z != 0) {
house->setExit(exitPosition);
}
if((attribute = houseNode.attribute("rent"))) {
house->rent = attribute.as_int();
}
if((attribute = houseNode.attribute("guildhall"))) {
house->guildhall = attribute.as_bool();
}
if((attribute = houseNode.attribute("townid"))) {
house->townid = attribute.as_uint();
} else {
warning("House %d has no town! House was removed.", house->id);
map.houses.removeHouse(house);
}
}
return true;
}
bool IOMapOTBM::loadSpawnsNpc(Map& map, const FileName& dir)
{
std::string fn = (const char*)(dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME).mb_str(wxConvUTF8));
fn += map.spawnnpcfile;
FileName filename(wxstr(fn));
if(!filename.FileExists())
return false;
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(fn.c_str());
if(!result) {
return false;
}
return loadSpawnsNpc(map, doc);
}
bool IOMapOTBM::loadSpawnsNpc(Map& map, pugi::xml_document& doc)
{
pugi::xml_node node = doc.child("npcs");
if(!node) {
warnings.push_back("IOMapOTBM::loadSpawnsNpc: Invalid rootheader.");
return false;
}
for(pugi::xml_node spawnNpcNode = node.first_child(); spawnNpcNode; spawnNpcNode = spawnNpcNode.next_sibling()) {
if(as_lower_str(spawnNpcNode.name()) != "npc") {
continue;
}
Position spawnPosition;
spawnPosition.x = spawnNpcNode.attribute("centerx").as_int();
spawnPosition.y = spawnNpcNode.attribute("centery").as_int();
spawnPosition.z = spawnNpcNode.attribute("centerz").as_int();
if(spawnPosition.x == 0 || spawnPosition.y == 0) {
warning("Bad position data on one npc spawn, discarding...");
continue;
}
int32_t radius = spawnNpcNode.attribute("radius").as_int();
if(radius < 1) {
warning("Couldn't read radius of npc spawn.. discarding spawn...");
continue;
}
Tile* spawnTile = map.getTile(spawnPosition);
if(spawnTile && spawnTile->spawnNpc) {
warning("Duplicate npc spawn on position %d:%d:%d\n", spawnTile->getX(), spawnTile->getY(), spawnTile->getZ());
continue;
}
SpawnNpc* spawnNpc = newd SpawnNpc(radius);
if(!spawnTile) {
spawnTile = map.allocator(map.createTileL(spawnPosition));
map.setTile(spawnPosition, spawnTile);
}
spawnTile->spawnNpc = spawnNpc;
map.addSpawnNpc(spawnTile);
for(pugi::xml_node npcNode = spawnNpcNode.first_child(); npcNode; npcNode = npcNode.next_sibling()) {
const std::string& npcNodeName = as_lower_str(npcNode.name());
if(npcNodeName != "npc") {
continue;
}
const std::string& name = npcNode.attribute("name").as_string();
if(name.empty()) {
wxString err;
err << "Bad npc position data, discarding npc at spawn " << spawnPosition.x << ":" << spawnPosition.y << ":" << spawnPosition.z << " due missing name.";
warnings.Add(err);
break;
}
int32_t spawntime = npcNode.attribute("spawntime").as_int();
if(spawntime == 0) {
spawntime = g_settings.getInteger(Config::DEFAULT_SPAWN_NPC_TIME);
}
Direction direction = NORTH;
int dir = npcNode.attribute("direction").as_int(-1);
if(dir >= DIRECTION_FIRST && dir <= DIRECTION_LAST) {
direction = (Direction)dir;
}
Position npcPosition(spawnPosition);
pugi::xml_attribute xAttribute = npcNode.attribute("x");
pugi::xml_attribute yAttribute = npcNode.attribute("y");
if(!xAttribute || !yAttribute) {
wxString err;
err << "Bad npc position data, discarding npc \"" << name << "\" at spawn " << npcPosition.x << ":" << npcPosition.y << ":" << npcPosition.z << " due to invalid position.";
warnings.Add(err);
break;
}
npcPosition.x += xAttribute.as_int();
npcPosition.y += yAttribute.as_int();
radius = std::max<int32_t>(radius, std::abs(npcPosition.x - spawnPosition.x));
radius = std::max<int32_t>(radius, std::abs(npcPosition.y - spawnPosition.y));
radius = std::min<int32_t>(radius, g_settings.getInteger(Config::MAX_SPAWN_NPC_RADIUS));
Tile* npcTile;
if(npcPosition == spawnPosition) {
npcTile = spawnTile;
} else {
npcTile = map.getTile(npcPosition);
}
if(!npcTile) {
wxString err;
err << "Discarding npc \"" << name << "\" at " << npcPosition.x << ":" << npcPosition.y << ":" << npcPosition.z << " due to invalid position.";
warnings.Add(err);
break;
}
if(npcTile->npc) {
wxString err;
err << "Duplicate npc \"" << name << "\" at " << npcPosition.x << ":" << npcPosition.y << ":" << npcPosition.z << " was discarded.";
warnings.Add(err);
break;
}
NpcType* type = g_npcs[name];
if(!type) {
type = g_npcs.addMissingNpcType(name);
}
Npc* npc = newd Npc(type);
npc->setDirection(direction);
npc->setSpawnNpcTime(spawntime);
npcTile->npc = npc;
if(npcTile->getLocation()->getSpawnNpcCount() == 0) {
// No npc spawn, create a newd one
ASSERT(npcTile->spawnNpc == nullptr);
SpawnNpc* spawnNpc = newd SpawnNpc(1);
npcTile->spawnNpc = spawnNpc;
map.addSpawnNpc(npcTile);
}
}
}
return true;
}
bool IOMapOTBM::saveMap(Map& map, const FileName& identifier)
{
#ifdef OTGZ_SUPPORT
if(identifier.GetExt() == "otgz") {
// Create the archive
struct archive* a = archive_write_new();
struct archive_entry* entry = nullptr;
std::ostringstream streamData;
archive_write_set_compression_gzip(a);
archive_write_set_format_pax_restricted(a);
archive_write_open_filename(a, nstr(identifier.GetFullPath()).c_str());
g_gui.SetLoadDone(0, "Saving monsters...");
pugi::xml_document spawnDoc;
if(saveSpawns(map, spawnDoc)) {
// Write the data
spawnDoc.save(streamData, "", pugi::format_raw, pugi::encoding_utf8);
std::string xmlData = streamData.str();
// Write to the arhive
entry = archive_entry_new();
archive_entry_set_pathname(entry, "world/monsters.xml");
archive_entry_set_size(entry, xmlData.size());
archive_entry_set_filetype(entry, AE_IFREG);
archive_entry_set_perm(entry, 0644);
// Write to the archive
archive_write_header(a, entry);
archive_write_data(a, xmlData.data(), xmlData.size());
// Free the entry
archive_entry_free(entry);
streamData.str("");
}
g_gui.SetLoadDone(0, "Saving houses...");
pugi::xml_document houseDoc;
if(saveHouses(map, houseDoc)) {
// Write the data
houseDoc.save(streamData, "", pugi::format_raw, pugi::encoding_utf8);
std::string xmlData = streamData.str();
// Write to the arhive
entry = archive_entry_new();
archive_entry_set_pathname(entry, "world/houses.xml");
archive_entry_set_size(entry, xmlData.size());
archive_entry_set_filetype(entry, AE_IFREG);
archive_entry_set_perm(entry, 0644);
// Write to the archive
archive_write_header(a, entry);
archive_write_data(a, xmlData.data(), xmlData.size());
// Free the entry
archive_entry_free(entry);
streamData.str("");
}
g_gui.SetLoadDone(0, "Saving npcs...");
pugi::xml_document npcDoc;
if(saveSpawnsNpc(map, npcDoc)) {
// Write the data
npcDoc.save(streamData, "", pugi::format_raw, pugi::encoding_utf8);
std::string xmlData = streamData.str();
// Write to the arhive
entry = archive_entry_new();
archive_entry_set_pathname(entry, "world/npcs.xml");
archive_entry_set_size(entry, xmlData.size());
archive_entry_set_filetype(entry, AE_IFREG);
archive_entry_set_perm(entry, 0644);
// Write to the archive
archive_write_header(a, entry);
archive_write_data(a, xmlData.data(), xmlData.size());
// Free the entry
archive_entry_free(entry);
streamData.str("");
}
g_gui.SetLoadDone(0, "Saving OTBM map...");
MemoryNodeFileWriteHandle otbmWriter;
saveMap(map, otbmWriter);
g_gui.SetLoadDone(75, "Compressing...");
// Create an archive entry for the otbm file
entry = archive_entry_new();
archive_entry_set_pathname(entry, "world/map.otbm");
archive_entry_set_size(entry, otbmWriter.getSize() + 4); // 4 bytes extra for header
archive_entry_set_filetype(entry, AE_IFREG);
archive_entry_set_perm(entry, 0644);
archive_write_header(a, entry);
// Write the version header
char otbm_identifier[] = "OTBM";
archive_write_data(a, otbm_identifier, 4);
// Write the OTBM data
archive_write_data(a, otbmWriter.getMemory(), otbmWriter.getSize());
archive_entry_free(entry);
// Free / close the archive
archive_write_close(a);
archive_write_free(a);
g_gui.DestroyLoadBar();
return true;
}
#endif
DiskNodeFileWriteHandle f(
nstr(identifier.GetFullPath()),
(g_settings.getInteger(Config::SAVE_WITH_OTB_MAGIC_NUMBER) ? "OTBM" : std::string(4, '\0'))
);
if(!f.isOk()) {
error("Can not open file %s for writing", (const char*)identifier.GetFullPath().mb_str(wxConvUTF8));
return false;
}
if(!saveMap(map, f))
return false;
SaveKMap saveKmap;
saveKmap.build(map);
saveKmap.save(map.getPath());
g_gui.SetLoadDone(99, "Saving monster spawns...");
saveSpawns(map, identifier);
g_gui.SetLoadDone(99, "Saving houses...");
saveHouses(map, identifier);
g_gui.SetLoadDone(99, "Saving npcs spawns...");
saveSpawnsNpc(map, identifier);
return true;
}
bool IOMapOTBM::saveMap(Map& map, NodeFileWriteHandle& f)
{
/* STOP!
* Before you even think about modifying this, please reconsider.
* while adding stuff to the binary format may be "cool", you'll
* inevitably make it incompatible with any future releases of
* the map editor, meaning you cannot reuse your map. Before you
* try to modify this, PLEASE consider using an external file
* like monster.xml or house.xml, as that will be MUCH easier
* to port to newer versions of the editor than a custom binary
* format.
*/
const IOMapOTBM& self = *this;
FileName tmpName;
MapVersion mapVersion = map.getVersion();
f.addNode(0);
{
f.addU32(mapVersion.otbm); // Version
f.addU16(map.width);
f.addU16(map.height);
f.addU32(g_items.MajorVersion);
f.addU32(g_items.MinorVersion);
f.addNode(OTBM_MAP_DATA);
{
f.addByte(OTBM_ATTR_DESCRIPTION);
// Neither SimOne's nor OpenTibia cares for additional description tags
f.addString("Saved with Remere's Map Editor " + __RME_VERSION__);
f.addU8(OTBM_ATTR_DESCRIPTION);
f.addString(map.description);
tmpName.Assign(wxstr(map.spawnmonsterfile));
f.addU8(OTBM_ATTR_EXT_SPAWN_MONSTER_FILE);
f.addString(nstr(tmpName.GetFullName()));
tmpName.Assign(wxstr(map.spawnnpcfile));
f.addU8(OTBM_ATTR_EXT_SPAWN_NPC_FILE);
f.addString(nstr(tmpName.GetFullName()));
tmpName.Assign(wxstr(map.housefile));
f.addU8(OTBM_ATTR_EXT_HOUSE_FILE);
f.addString(nstr(tmpName.GetFullName()));
// Start writing tiles
uint32_t tiles_saved = 0;
bool first = true;
int local_x = -1, local_y = -1, local_z = -1;
MapIterator map_iterator = map.begin();
while(map_iterator != map.end()) {
// Update progressbar
++tiles_saved;
if(tiles_saved % 8192 == 0)
g_gui.SetLoadDone(int(tiles_saved / double(map.getTileCount()) * 100.0));
// Get tile
Tile* save_tile = (*map_iterator)->get();
// Is it an empty tile that we can skip? (Leftovers...)
if(!save_tile || save_tile->size() == 0) {
++map_iterator;
continue;
}
const Position& pos = save_tile->getPosition();
// Decide if newd node should be created
if(pos.x < local_x || pos.x >= local_x + 256 || pos.y < local_y || pos.y >= local_y + 256 || pos.z != local_z) {
// End last node
if(!first) {
f.endNode();
}
first = false;
// Start newd node
f.addNode(OTBM_TILE_AREA);
f.addU16(local_x = pos.x & 0xFF00);
f.addU16(local_y = pos.y & 0xFF00);
f.addU8( local_z = pos.z);
}
f.addNode(save_tile->isHouseTile()? OTBM_HOUSETILE : OTBM_TILE);
f.addU8(save_tile->getX() & 0xFF);
f.addU8(save_tile->getY() & 0xFF);
if(save_tile->isHouseTile()) {
f.addU32(save_tile->getHouseID());
}
if(save_tile->getMapFlags()) {
f.addByte(OTBM_ATTR_TILE_FLAGS);
f.addU32(save_tile->getMapFlags());
}
if(save_tile->ground) {
Item* ground = save_tile->ground;
if(ground->isMetaItem()) {
// Do nothing, we don't save metaitems...
} else if(ground->hasBorderEquivalent()) {
bool found = false;
for(Item* item : save_tile->items) {
if(item->getGroundEquivalent() == ground->getID()) {
// Do nothing
// Found equivalent
found = true;
break;
}
}
if(!found) {
ground->serializeItemNode_OTBM(self, f);
}
} else if(ground->isComplex()) {
ground->serializeItemNode_OTBM(self, f);
} else {
f.addByte(OTBM_ATTR_ITEM);
ground->serializeItemCompact_OTBM(self, f);
}
}
for(Item* item : save_tile->items) {
if(!item->isMetaItem()) {
item->serializeItemNode_OTBM(self, f);
}
}
f.endNode();
++map_iterator;
}
// Only close the last node if one has actually been created
if(!first) {
f.endNode();
}
f.addNode(OTBM_TOWNS);
for(const auto& townEntry : map.towns) {
Town* town = townEntry.second;
const Position& townPosition = town->getTemplePosition();
f.addNode(OTBM_TOWN);
f.addU32(town->getID());
f.addString(town->getName());
f.addU16(townPosition.x);
f.addU16(townPosition.y);
f.addU8(townPosition.z);
f.endNode();
}
f.endNode();
if(version.otbm >= MAP_OTBM_3) {
f.addNode(OTBM_WAYPOINTS);
for(const auto& waypointEntry : map.waypoints) {
Waypoint* waypoint = waypointEntry.second;
f.addNode(OTBM_WAYPOINT);
f.addString(waypoint->name);
f.addU16(waypoint->pos.x);
f.addU16(waypoint->pos.y);
f.addU8(waypoint->pos.z);
f.endNode();
}
f.endNode();
}
}
f.endNode();
}
f.endNode();
return true;
}
bool IOMapOTBM::saveSpawns(Map& map, const FileName& dir)
{
wxString filepath = dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME);
filepath += wxString(map.spawnmonsterfile.c_str(), wxConvUTF8);
// Create the XML file
pugi::xml_document doc;
if(saveSpawns(map, doc)) {
return doc.save_file(filepath.wc_str(), "\t", pugi::format_default, pugi::encoding_utf8);
}
return false;
}
bool IOMapOTBM::saveSpawns(Map& map, pugi::xml_document& doc)
{
pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
if(!decl) {
return false;
}
decl.append_attribute("version") = "1.0";
MonsterList monsterList;
pugi::xml_node spawnNodes = doc.append_child("monsters");
for(const auto& spawnPosition : map.spawnsMonster) {
Tile *tile = map.getTile(spawnPosition);
if (tile == nullptr)
continue;
SpawnMonster* spawnMonster = tile->spawnMonster;
ASSERT(spawnMonster);
pugi::xml_node spawnNode = spawnNodes.append_child("monster");
spawnNode.append_attribute("centerx") = spawnPosition.x;
spawnNode.append_attribute("centery") = spawnPosition.y;
spawnNode.append_attribute("centerz") = spawnPosition.z;
int32_t radius = spawnMonster->getSize();
spawnNode.append_attribute("radius") = radius;
for(int32_t y = -radius; y <= radius; ++y) {
for(int32_t x = -radius; x <= radius; ++x) {
Tile* monster_tile = map.getTile(spawnPosition + Position(x, y, 0));
if(monster_tile) {
Monster* monster = monster_tile->monster;
if(monster && !monster->isSaved()) {
pugi::xml_node monsterNode = spawnNode.append_child("monster");
monsterNode.append_attribute("name") = monster->getName().c_str();
monsterNode.append_attribute("x") = x;
monsterNode.append_attribute("y") = y;
monsterNode.append_attribute("z") = spawnPosition.z;
monsterNode.append_attribute("spawntime") = monster->getSpawnMonsterTime();
if(monster->getDirection() != NORTH) {
monsterNode.append_attribute("direction") = monster->getDirection();
}
// Mark as saved
monster->save();
monsterList.push_back(monster);
}
}
}
}
}
for(Monster* monster : monsterList) {
monster->reset();
}
return true;
}
bool IOMapOTBM::saveHouses(Map& map, const FileName& dir)
{
wxString filepath = dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME);
filepath += wxString(map.housefile.c_str(), wxConvUTF8);
// Create the XML file
pugi::xml_document doc;
if(saveHouses(map, doc)) {
return doc.save_file(filepath.wc_str(), "\t", pugi::format_default, pugi::encoding_utf8);
}
return false;
}
bool IOMapOTBM::saveHouses(Map& map, pugi::xml_document& doc)
{
pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
if(!decl) {
return false;
}
decl.append_attribute("version") = "1.0";
pugi::xml_node houseNodes = doc.append_child("houses");
for(const auto& houseEntry : map.houses) {
const House* house = houseEntry.second;
pugi::xml_node houseNode = houseNodes.append_child("house");
houseNode.append_attribute("name") = house->name.c_str();
houseNode.append_attribute("houseid") = house->id;
const Position& exitPosition = house->getExit();
houseNode.append_attribute("entryx") = exitPosition.x;
houseNode.append_attribute("entryy") = exitPosition.y;
houseNode.append_attribute("entryz") = exitPosition.z;
houseNode.append_attribute("rent") = house->rent;
if(house->guildhall) {
houseNode.append_attribute("guildhall") = true;
}
houseNode.append_attribute("townid") = house->townid;
houseNode.append_attribute("size") = static_cast<int32_t>(house->size());
}
return true;
}
bool IOMapOTBM::saveSpawnsNpc(Map& map, const FileName& dir)
{
wxString filepath = dir.GetPath(wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME);
filepath += wxString(map.spawnnpcfile.c_str(), wxConvUTF8);
// Create the XML file
pugi::xml_document doc;
if(saveSpawnsNpc(map, doc)) {
return doc.save_file(filepath.wc_str(), "\t", pugi::format_default, pugi::encoding_utf8);
}
return false;
}
bool IOMapOTBM::saveSpawnsNpc(Map& map, pugi::xml_document& doc)
{
pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
if(!decl) {
return false;
}
decl.append_attribute("version") = "1.0";
NpcList npcList;
pugi::xml_node spawnNodes = doc.append_child("npcs");
for(const auto& spawnPosition : map.spawnsNpc) {
Tile *tile = map.getTile(spawnPosition);
if (tile == nullptr)
continue;
SpawnNpc* spawnNpc = tile->spawnNpc;
ASSERT(spawnNpc);
pugi::xml_node spawnNpcNode = spawnNodes.append_child("npc");
spawnNpcNode.append_attribute("centerx") = spawnPosition.x;
spawnNpcNode.append_attribute("centery") = spawnPosition.y;
spawnNpcNode.append_attribute("centerz") = spawnPosition.z;
int32_t radius = spawnNpc->getSize();
spawnNpcNode.append_attribute("radius") = radius;
for(int32_t y = -radius; y <= radius; ++y) {
for(int32_t x = -radius; x <= radius; ++x) {
Tile* npcTile = map.getTile(spawnPosition + Position(x, y, 0));
if(npcTile) {
Npc* npc = npcTile->npc;
if(npc && !npc->isSaved()) {
pugi::xml_node npcNode = spawnNpcNode.append_child("npc");
npcNode.append_attribute("name") = npc->getName().c_str();
npcNode.append_attribute("x") = x;
npcNode.append_attribute("y") = y;
npcNode.append_attribute("z") = spawnPosition.z;
npcNode.append_attribute("spawntime") = npc->getSpawnNpcTime();
if(npc->getDirection() != NORTH) {
npcNode.append_attribute("direction") = npc->getDirection();
}
// Mark as saved
npc->save();
npcList.push_back(npc);
}
}
}
}
}
for(Npc* npc : npcList) {
npc->reset();
}
return true;
}