tibia-rme/source/materials.cpp
2024-06-07 18:16:45 -03:00

386 lines
12 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 "editor.h"
#include "items.h"
#include "monsters.h"
#include "gui.h"
#include "materials.h"
#include "brush.h"
#include "monster_brush.h"
#include "npc_brush.h"
#include "raw_brush.h"
Materials g_materials;
Materials::Materials() {
////
}
Materials::~Materials() {
clear();
}
void Materials::clear() {
for (TilesetContainer::iterator iter = tilesets.begin(); iter != tilesets.end(); ++iter) {
delete iter->second;
}
for (MaterialsExtensionList::iterator iter = extensions.begin(); iter != extensions.end(); ++iter) {
delete *iter;
}
tilesets.clear();
extensions.clear();
}
const MaterialsExtensionList &Materials::getExtensions() {
return extensions;
}
MaterialsExtensionList Materials::getExtensionsByVersion(uint16_t version_id) {
MaterialsExtensionList ret_list;
for (MaterialsExtensionList::iterator iter = extensions.begin(); iter != extensions.end(); ++iter) {
if ((*iter)->isForVersion(version_id)) {
ret_list.push_back(*iter);
}
}
return ret_list;
}
bool Materials::loadMaterials(const FileName &identifier, wxString &error, wxArrayString &warnings) {
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(identifier.GetFullPath().mb_str());
if (!result) {
warnings.push_back("Could not open " + identifier.GetFullName() + " (file not found or syntax error)");
return false;
}
pugi::xml_node node = doc.child("materials");
if (!node) {
warnings.push_back(identifier.GetFullName() + ": Invalid rootheader.");
return false;
}
unserializeMaterials(identifier, node, error, warnings);
return true;
}
bool Materials::loadExtensions(FileName directoryName, wxString &error, wxArrayString &warnings) {
directoryName.Mkdir(0755, wxPATH_MKDIR_FULL); // Create if it doesn't exist
wxDir ext_dir(directoryName.GetPath());
if (!ext_dir.IsOpened()) {
error = "Could not open extensions directory.";
return false;
}
wxString filename;
if (!ext_dir.GetFirst(&filename)) {
// No extensions found
return true;
}
StringVector clientVersions;
do {
FileName fn;
fn.SetPath(directoryName.GetPath());
fn.SetFullName(filename);
if (fn.GetExt() != "xml") {
continue;
}
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(fn.GetFullPath().mb_str());
if (!result) {
warnings.push_back("Could not open " + filename + " (file not found or syntax error)");
continue;
}
pugi::xml_node extensionNode = doc.child("materialsextension");
if (!extensionNode) {
warnings.push_back(filename + ": Invalid rootheader.");
continue;
}
pugi::xml_attribute attribute;
if (!(attribute = extensionNode.attribute("name"))) {
warnings.push_back(filename + ": Couldn't read extension name.");
continue;
}
const std::string &extensionName = attribute.as_string();
if (!(attribute = extensionNode.attribute("author"))) {
warnings.push_back(filename + ": Couldn't read extension name.");
continue;
}
const std::string &extensionAuthor = attribute.as_string();
if (!(attribute = extensionNode.attribute("description"))) {
warnings.push_back(filename + ": Couldn't read extension name.");
continue;
}
const std::string &extensionDescription = attribute.as_string();
if (extensionName.empty() || extensionAuthor.empty() || extensionDescription.empty()) {
warnings.push_back(filename + ": Couldn't read extension attributes (name, author, description).");
continue;
}
std::string extensionUrl = extensionNode.attribute("url").as_string();
extensionUrl.erase(std::remove(extensionUrl.begin(), extensionUrl.end(), '\''));
std::string extensionAuthorLink = extensionNode.attribute("authorurl").as_string();
extensionAuthorLink.erase(std::remove(extensionAuthorLink.begin(), extensionAuthorLink.end(), '\''));
MaterialsExtension* materialExtension = newd MaterialsExtension(extensionName, extensionAuthor, extensionDescription);
materialExtension->url = extensionUrl;
materialExtension->author_url = extensionAuthorLink;
if ((attribute = extensionNode.attribute("client"))) {
clientVersions.clear();
const std::string &extensionClientString = attribute.as_string();
size_t lastPosition = 0;
size_t position = extensionClientString.find(';');
while (position != std::string::npos) {
clientVersions.push_back(extensionClientString.substr(lastPosition, position - lastPosition));
lastPosition = position + 1;
position = extensionClientString.find(';', lastPosition);
}
clientVersions.push_back(extensionClientString.substr(lastPosition));
for (const std::string &version : clientVersions) {
materialExtension->addVersion(version);
}
std::sort(materialExtension->version_list.begin(), materialExtension->version_list.end(), VersionComparisonPredicate);
auto duplicate = std::unique(materialExtension->version_list.begin(), materialExtension->version_list.end());
while (duplicate != materialExtension->version_list.end()) {
materialExtension->version_list.erase(duplicate);
duplicate = std::unique(materialExtension->version_list.begin(), materialExtension->version_list.end());
}
} else {
warnings.push_back(filename + ": Extension is not available for any version.");
}
extensions.push_back(materialExtension);
if (materialExtension->isForVersion(g_gui.GetCurrentVersionID())) {
unserializeMaterials(filename, extensionNode, error, warnings);
}
} while (ext_dir.GetNext(&filename));
return true;
}
bool Materials::unserializeMaterials(const FileName &filename, pugi::xml_node node, wxString &error, wxArrayString &warnings) {
wxString warning;
pugi::xml_attribute attribute;
for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
const std::string &childName = as_lower_str(childNode.name());
if (childName == "include") {
if (!(attribute = childNode.attribute("file"))) {
continue;
}
FileName includeName;
includeName.SetPath(filename.GetPath());
includeName.SetName(wxString(attribute.as_string(), wxConvUTF8));
wxString subError;
if (!loadMaterials(includeName, subError, warnings)) {
warnings.push_back("Error while loading file \"" + includeName.GetFullName() + "\": " + subError);
}
} else if (childName == "metaitem") {
g_items.loadMetaItem(childNode);
} else if (childName == "border") {
g_brushes.unserializeBorder(childNode, warnings);
if (warning.size()) {
warnings.push_back("materials.xml: " + warning);
}
} else if (childName == "brush") {
g_brushes.unserializeBrush(childNode, warnings);
if (warning.size()) {
warnings.push_back("materials.xml: " + warning);
}
} else if (childName == "tileset") {
unserializeTileset(childNode, warnings);
}
}
return true;
}
void Materials::createOtherTileset() {
Tileset* others;
if (tilesets["Others"] != nullptr) {
others = tilesets["Others"];
others->clear();
} else {
others = newd Tileset(g_brushes, "Others");
tilesets["Others"] = others;
}
// There should really be an iterator to do this
for (int32_t id = 0; id <= g_items.getMaxID(); ++id) {
auto type = g_items.getRawItemType(id);
if (!type) {
continue;
}
if (!type->isMetaItem()) {
Brush* brush;
if (type->in_other_tileset) {
others->getCategory(TILESET_RAW)->brushlist.push_back(type->raw_brush);
continue;
} else if (!type->raw_brush) {
brush = type->raw_brush = newd RAWBrush(type->id);
type->has_raw = true;
g_brushes.addBrush(type->raw_brush);
} else if (!type->has_raw) {
brush = type->raw_brush;
} else {
continue;
}
brush->flagAsVisible();
others->getCategory(TILESET_RAW)->brushlist.push_back(type->raw_brush);
type->in_other_tileset = true;
}
}
for (MonsterMap::iterator iter = g_monsters.begin(); iter != g_monsters.end(); ++iter) {
MonsterType* type = iter->second;
if (type->in_other_tileset) {
others->getCategory(TILESET_MONSTER)->brushlist.push_back(type->brush);
} else if (type->brush == nullptr) {
type->brush = newd MonsterBrush(type);
g_brushes.addBrush(type->brush);
type->brush->flagAsVisible();
type->in_other_tileset = true;
others->getCategory(TILESET_MONSTER)->brushlist.push_back(type->brush);
}
}
}
void Materials::createNpcTileset() {
Tileset* npcTileset;
if (tilesets["NPCs"] != nullptr) {
npcTileset = tilesets["NPCs"];
npcTileset->clear();
} else {
npcTileset = newd Tileset(g_brushes, "NPCs");
tilesets["NPCs"] = npcTileset;
}
for (NpcMap::iterator iter = g_npcs.begin(); iter != g_npcs.end(); ++iter) {
NpcType* type = iter->second;
if (type->in_other_tileset) {
npcTileset->getCategory(TILESET_NPC)->brushlist.push_back(type->brush);
} else if (type->brush == nullptr) {
type->brush = newd NpcBrush(type);
g_brushes.addBrush(type->brush);
type->brush->flagAsVisible();
type->in_other_tileset = true;
npcTileset->getCategory(TILESET_NPC)->brushlist.push_back(type->brush);
}
}
}
bool Materials::unserializeTileset(pugi::xml_node node, wxArrayString &warnings) {
pugi::xml_attribute attribute;
if (!(attribute = node.attribute("name"))) {
warnings.push_back("Couldn't read tileset name");
return false;
}
const std::string &name = attribute.as_string();
Tileset* tileset;
auto it = tilesets.find(name);
if (it != tilesets.end()) {
tileset = it->second;
} else {
tileset = newd Tileset(g_brushes, name);
tilesets.insert(std::make_pair(name, tileset));
}
for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
tileset->loadCategory(childNode, warnings);
}
return true;
}
void Materials::addToTileset(std::string tilesetName, int itemId, TilesetCategoryType categoryType) {
ItemType &it = g_items[itemId];
if (it.id == 0) {
return;
}
Tileset* tileset;
auto _it = tilesets.find(tilesetName);
if (_it != tilesets.end()) {
tileset = _it->second;
} else {
tileset = newd Tileset(g_brushes, tilesetName);
tilesets.insert(std::make_pair(tilesetName, tileset));
}
TilesetCategory* category = tileset->getCategory(categoryType);
if (!it.isMetaItem()) {
Brush* brush;
if (it.in_other_tileset) {
category->brushlist.push_back(it.raw_brush);
return;
} else if (it.raw_brush == nullptr) {
brush = it.raw_brush = newd RAWBrush(it.id);
it.has_raw = true;
g_brushes.addBrush(it.raw_brush);
} else {
brush = it.raw_brush;
}
brush->flagAsVisible();
category->brushlist.push_back(it.raw_brush);
it.in_other_tileset = true;
}
}
bool Materials::isInTileset(Item* item, std::string tilesetName) const {
const ItemType &type = g_items.getItemType(item->getID());
return type.id != 0 && (isInTileset(type.brush, tilesetName) || isInTileset(type.doodad_brush, tilesetName) || isInTileset(type.raw_brush, tilesetName));
}
bool Materials::isInTileset(Brush* brush, std::string tilesetName) const {
if (!brush) {
return false;
}
TilesetContainer::const_iterator tilesetiter = tilesets.find(tilesetName);
if (tilesetiter == tilesets.end()) {
return false;
}
Tileset* tileset = tilesetiter->second;
return tileset->containsBrush(brush);
}