////////////////////////////////////////////////////////////////////// // 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 . ////////////////////////////////////////////////////////////////////// #include "main.h" #include "materials.h" #include "gui.h" #include "items.h" // #include "item.h" ItemDatabase g_items; ItemType::ItemType() : sprite(nullptr), id(0), clientID(0), brush(nullptr), doodad_brush(nullptr), raw_brush(nullptr), is_metaitem(false), has_raw(false), in_other_tileset(false), group(ITEM_GROUP_NONE), type(ITEM_TYPE_NONE), volume(0), maxTextLen(0), // writeOnceItemID(0), ground_equivalent(0), border_group(0), has_equivalent(false), wall_hate_me(false), name(""), description(""), weight(0.0f), attack(0), defense(0), armor(0), charges(0), client_chargeable(false), extra_chargeable(false), ignoreLook(false), isHangable(false), hookEast(false), hookSouth(false), canReadText(false), canWriteText(false), replaceable(true), decays(false), stackable(false), moveable(true), alwaysOnBottom(false), pickupable(false), rotable(false), isBorder(false), isOptionalBorder(false), isWall(false), isBrushDoor(false), isOpen(false), isTable(false), isCarpet(false), floorChangeDown(false), floorChangeNorth(false), floorChangeSouth(false), floorChangeEast(false), floorChangeWest(false), floorChange(false), unpassable(false), blockPickupable(false), blockMissiles(false), blockPathfinder(false), hasElevation(false), alwaysOnTopOrder(0), rotateTo(0), border_alignment(BORDER_NONE) { //// } bool ItemType::isFloorChange() const noexcept { return floorChange || floorChangeDown || floorChangeNorth || floorChangeSouth || floorChangeEast || floorChangeWest; } ItemDatabase::ItemDatabase() : // Version information MajorVersion(0), MinorVersion(0), BuildNumber(0), // Count of GameSprite types item_count(0), effect_count(0), monster_count(0), distance_count(0), minClientID(0), maxClientID(0), maxItemId(0) { //// } ItemDatabase::~ItemDatabase() { clear(); } void ItemDatabase::clear() { for (size_t i = 0; i < items.size(); i++) { items.set(i, nullptr); } } bool ItemDatabase::loadFromOtbVer1(BinaryNode* itemNode, wxString &error, wxArrayString &warnings) { for (; itemNode != nullptr; itemNode = itemNode->advance()) { uint8_t group; if (!itemNode->getU8(group)) { // Invalid! warnings.push_back("Invalid item type encountered..."); continue; } if (group == ITEM_GROUP_DEPRECATED) { continue; } auto item = newd(); item->group = static_cast(group); switch (item->group) { case ITEM_GROUP_NONE: case ITEM_GROUP_GROUND: case ITEM_GROUP_SPLASH: case ITEM_GROUP_FLUID: case ITEM_GROUP_WEAPON: case ITEM_GROUP_AMMUNITION: case ITEM_GROUP_ARMOR: case ITEM_GROUP_WRITEABLE: case ITEM_GROUP_KEY: break; case ITEM_GROUP_DOOR: item->type = ITEM_TYPE_DOOR; break; case ITEM_GROUP_CONTAINER: item->type = ITEM_TYPE_CONTAINER; break; case ITEM_GROUP_RUNE: item->client_chargeable = true; break; case ITEM_GROUP_TELEPORT: item->type = ITEM_TYPE_TELEPORT; break; case ITEM_GROUP_MAGICFIELD: item->type = ITEM_TYPE_MAGICFIELD; break; default: warnings.push_back("Unknown item group declaration"); break; } uint32_t flags; if (itemNode->getU32(flags)) { item->unpassable = ((flags & FLAG_UNPASSABLE) == FLAG_UNPASSABLE); item->blockMissiles = ((flags & FLAG_BLOCK_MISSILES) == FLAG_BLOCK_MISSILES); item->blockPathfinder = ((flags & FLAG_BLOCK_PATHFINDER) == FLAG_BLOCK_PATHFINDER); item->hasElevation = ((flags & FLAG_HAS_ELEVATION) == FLAG_HAS_ELEVATION); // t->useable = ((flags & FLAG_USEABLE) == FLAG_USEABLE); item->pickupable = ((flags & FLAG_PICKUPABLE) == FLAG_PICKUPABLE); item->moveable = ((flags & FLAG_MOVEABLE) == FLAG_MOVEABLE); item->stackable = ((flags & FLAG_STACKABLE) == FLAG_STACKABLE); item->floorChangeDown = ((flags & FLAG_FLOORCHANGEDOWN) == FLAG_FLOORCHANGEDOWN); item->floorChangeNorth = ((flags & FLAG_FLOORCHANGENORTH) == FLAG_FLOORCHANGENORTH); item->floorChangeEast = ((flags & FLAG_FLOORCHANGEEAST) == FLAG_FLOORCHANGEEAST); item->floorChangeSouth = ((flags & FLAG_FLOORCHANGESOUTH) == FLAG_FLOORCHANGESOUTH); item->floorChangeWest = ((flags & FLAG_FLOORCHANGEWEST) == FLAG_FLOORCHANGEWEST); item->floorChange = item->floorChangeDown || item->floorChangeNorth || item->floorChangeEast || item->floorChangeSouth || item->floorChangeWest; // Now this is confusing, just accept that the ALWAYSONTOP flag means it's always on bottom, got it?! item->alwaysOnBottom = ((flags & FLAG_ALWAYSONTOP) == FLAG_ALWAYSONTOP); item->isHangable = ((flags & FLAG_HANGABLE) == FLAG_HANGABLE); item->hookEast = ((flags & FLAG_HOOK_EAST) == FLAG_HOOK_EAST); item->hookSouth = ((flags & FLAG_HOOK_SOUTH) == FLAG_HOOK_SOUTH); item->allowDistRead = ((flags & FLAG_ALLOWDISTREAD) == FLAG_ALLOWDISTREAD); item->rotable = ((flags & FLAG_ROTABLE) == FLAG_ROTABLE); item->canReadText = ((flags & FLAG_READABLE) == FLAG_READABLE); } uint8_t attribute; while (itemNode->getU8(attribute)) { uint16_t datalen; if (!itemNode->getU16(datalen)) { warnings.push_back("Invalid item type property"); break; } switch (attribute) { case ITEM_ATTR_SERVERID: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of server id block (Should be 2 bytes)"; return false; } if (!itemNode->getU16(item->id)) { warnings.push_back("Invalid item type property (2)"); } if (maxItemId < item->id) { maxItemId = item->id; } break; } case ITEM_ATTR_CLIENTID: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of client id block (Should be 2 bytes)"; return false; } if (!itemNode->getU16(item->clientID)) { warnings.push_back("Invalid item type property (2)"); } item->sprite = static_cast(g_gui.gfx.getSprite(item->clientID)); break; } case ITEM_ATTR_SPEED: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of speed block (Should be 2 bytes)"; return false; } // t->speed = itemNode->getU16(); if (!itemNode->skip(2)) { // Just skip two bytes, we don't need speed warnings.push_back("Invalid item type property (3)"); } break; } case ITEM_ATTR_LIGHT2: { if (datalen != sizeof(lightBlock2)) { warnings.push_back("items.otb: Unexpected data length of item light (2) block (Should be " + i2ws(sizeof(lightBlock2)) + " bytes)"); break; } if (!itemNode->skip(4)) { // Just skip two bytes, we don't need light warnings.push_back("Invalid item type property (4)"); } // t->lightLevel = itemNode->getU16(); // t->lightColor = itemNode->getU16(); break; } case ITEM_ATTR_TOPORDER: { if (datalen != sizeof(uint8_t)) { warnings.push_back("items.otb: Unexpected data length of item toporder block (Should be 1 byte)"); break; } uint8_t value = 0; if (!itemNode->getU8(value)) { warnings.push_back("Invalid item type property (5)"); } item->alwaysOnTopOrder = value; break; } case ITEM_ATTR_NAME: { if (datalen >= 128) { warnings.push_back("items.otb: Unexpected data length of item name block (Should be 128 bytes)"); break; } uint8_t name[128]; memset(&name, 0, 128); if (!itemNode->getRAW(name, datalen)) { warnings.push_back("Invalid item type property (6)"); break; } item->name = (char*)name; break; } case ITEM_ATTR_DESCR: { if (datalen >= 128) { warnings.push_back("items.otb: Unexpected data length of item descr block (Should be 128 bytes)"); break; } uint8_t description[128]; memset(&description, 0, 128); if (!itemNode->getRAW(description, datalen)) { warnings.push_back("Invalid item type property (7)"); break; } item->description = (char*)description; break; } case ITEM_ATTR_MAXITEMS: { if (datalen != sizeof(unsigned short)) { warnings.push_back("items.otb: Unexpected data length of item volume block (Should be 2 bytes)"); break; } if (!itemNode->getU16(item->volume)) { warnings.push_back("Invalid item type property (8)"); } break; } case ITEM_ATTR_WEIGHT: { if (datalen != sizeof(double)) { warnings.push_back("items.otb: Unexpected data length of item weight block (Should be 8 bytes)"); break; } uint8_t weight[sizeof(double)]; if (!itemNode->getRAW(weight, sizeof(double))) { warnings.push_back("Invalid item type property (7)"); break; } double actualWeight; memcpy(&actualWeight, weight, sizeof(actualWeight)); item->weight = actualWeight; break; } case ITEM_ATTR_ROTATETO: { if (datalen != sizeof(unsigned short)) { warnings.push_back("items.otb: Unexpected data length of item rotateTo block (Should be 2 bytes)"); break; } uint16_t rotate; if (!itemNode->getU16(rotate)) { warnings.push_back("Invalid item type property (8)"); break; } item->rotateTo = rotate; break; } case ITEM_ATTR_WRITEABLE3: { if (datalen != sizeof(writeableBlock3)) { warnings.push_back("items.otb: Unexpected data length of item toporder block (Should be 1 byte)"); break; } uint16_t readOnlyID; uint16_t maxTextLen; if (!itemNode->getU16(readOnlyID)) { warnings.push_back("Invalid item type property (9)"); break; } if (!itemNode->getU16(maxTextLen)) { warnings.push_back("Invalid item type property (10)"); break; } // t->readOnlyId = wb3->readOnlyId; item->maxTextLen = maxTextLen; break; } default: { // skip unknown attributes itemNode->skip(datalen); // warnings.push_back("items.otb: Skipped unknown attribute"); break; } } } if (items[item->id]) { warnings.push_back("items.otb: Duplicate items"); } items.set(item->id, item); } return true; } bool ItemDatabase::loadFromOtbVer2(BinaryNode* itemNode, wxString &error, wxArrayString &warnings) { uint8_t group; for (; itemNode != nullptr; itemNode = itemNode->advance()) { if (!itemNode->getU8(group)) { // Invalid! warnings.push_back("Invalid item type encountered..."); continue; } if (group == ITEM_GROUP_DEPRECATED) { continue; } auto item = newd(); item->group = static_cast(group); switch (item->group) { case ITEM_GROUP_NONE: case ITEM_GROUP_GROUND: case ITEM_GROUP_SPLASH: case ITEM_GROUP_FLUID: break; case ITEM_GROUP_DOOR: item->type = ITEM_TYPE_DOOR; break; case ITEM_GROUP_CONTAINER: item->type = ITEM_TYPE_CONTAINER; break; case ITEM_GROUP_RUNE: item->client_chargeable = true; break; case ITEM_GROUP_TELEPORT: item->type = ITEM_TYPE_TELEPORT; break; case ITEM_GROUP_MAGICFIELD: item->type = ITEM_TYPE_MAGICFIELD; break; default: warnings.push_back("Unknown item group declaration"); break; } uint32_t flags; if (itemNode->getU32(flags)) { item->unpassable = ((flags & FLAG_UNPASSABLE) == FLAG_UNPASSABLE); item->blockMissiles = ((flags & FLAG_BLOCK_MISSILES) == FLAG_BLOCK_MISSILES); item->blockPathfinder = ((flags & FLAG_BLOCK_PATHFINDER) == FLAG_BLOCK_PATHFINDER); item->hasElevation = ((flags & FLAG_HAS_ELEVATION) == FLAG_HAS_ELEVATION); item->pickupable = ((flags & FLAG_PICKUPABLE) == FLAG_PICKUPABLE); item->moveable = ((flags & FLAG_MOVEABLE) == FLAG_MOVEABLE); item->stackable = ((flags & FLAG_STACKABLE) == FLAG_STACKABLE); item->floorChangeDown = ((flags & FLAG_FLOORCHANGEDOWN) == FLAG_FLOORCHANGEDOWN); item->floorChangeNorth = ((flags & FLAG_FLOORCHANGENORTH) == FLAG_FLOORCHANGENORTH); item->floorChangeEast = ((flags & FLAG_FLOORCHANGEEAST) == FLAG_FLOORCHANGEEAST); item->floorChangeSouth = ((flags & FLAG_FLOORCHANGESOUTH) == FLAG_FLOORCHANGESOUTH); item->floorChangeWest = ((flags & FLAG_FLOORCHANGEWEST) == FLAG_FLOORCHANGEWEST); item->floorChange = item->floorChangeDown || item->floorChangeNorth || item->floorChangeEast || item->floorChangeSouth || item->floorChangeWest; // Now this is confusing, just accept that the ALWAYSONTOP flag means it's always on bottom, got it?! item->alwaysOnBottom = ((flags & FLAG_ALWAYSONTOP) == FLAG_ALWAYSONTOP); item->isHangable = ((flags & FLAG_HANGABLE) == FLAG_HANGABLE); item->hookEast = ((flags & FLAG_HOOK_EAST) == FLAG_HOOK_EAST); item->hookSouth = ((flags & FLAG_HOOK_SOUTH) == FLAG_HOOK_SOUTH); item->allowDistRead = ((flags & FLAG_ALLOWDISTREAD) == FLAG_ALLOWDISTREAD); item->rotable = ((flags & FLAG_ROTABLE) == FLAG_ROTABLE); item->canReadText = ((flags & FLAG_READABLE) == FLAG_READABLE); } uint8_t attribute; while (itemNode->getU8(attribute)) { uint16_t datalen; if (!itemNode->getU16(datalen)) { warnings.push_back("Invalid item type property"); break; } switch (attribute) { case ITEM_ATTR_SERVERID: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of server id block (Should be 2 bytes)"; return false; } if (!itemNode->getU16(item->id)) { warnings.push_back("Invalid item type property (2)"); } if (maxItemId < item->id) { maxItemId = item->id; } break; } case ITEM_ATTR_CLIENTID: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of client id block (Should be 2 bytes)"; return false; } if (!itemNode->getU16(item->clientID)) { warnings.push_back("Invalid item type property (2)"); } item->sprite = static_cast(g_gui.gfx.getSprite(item->clientID)); break; } case ITEM_ATTR_SPEED: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of speed block (Should be 2 bytes)"; return false; } // t->speed = itemNode->getU16(); if (!itemNode->skip(2)) { // Just skip two bytes, we don't need speed warnings.push_back("Invalid item type property (3)"); } break; } case ITEM_ATTR_LIGHT2: { if (datalen != sizeof(lightBlock2)) { warnings.push_back("items.otb: Unexpected data length of item light (2) block (Should be " + i2ws(sizeof(lightBlock2)) + " bytes)"); break; } if (!itemNode->skip(4)) { // Just skip two bytes, we don't need light warnings.push_back("Invalid item type property (4)"); } // t->lightLevel = itemNode->getU16(); // t->lightColor = itemNode->getU16(); break; } case ITEM_ATTR_TOPORDER: { if (datalen != sizeof(uint8_t)) { warnings.push_back("items.otb: Unexpected data length of item toporder block (Should be 1 byte)"); break; } uint8_t value = 0; if (!itemNode->getU8(value)) { warnings.push_back("Invalid item type property (5)"); break; } item->alwaysOnTopOrder = value; break; } default: { // skip unknown attributes itemNode->skip(datalen); // warnings.push_back("items.otb: Skipped unknown attribute"); break; } } } if (items[item->id]) { warnings.push_back("items.otb: Duplicate items"); } items.set(item->id, item); } return true; } bool ItemDatabase::loadFromOtbVer3(BinaryNode* itemNode, wxString &error, wxArrayString &warnings) { uint8_t group; for (; itemNode != nullptr; itemNode = itemNode->advance()) { if (!itemNode->getU8(group)) { // Invalid! warnings.push_back("Invalid item type encountered..."); continue; } if (group == ITEM_GROUP_DEPRECATED) { continue; } auto item = newd(); item->group = static_cast(group); switch (item->group) { case ITEM_GROUP_NONE: case ITEM_GROUP_GROUND: case ITEM_GROUP_SPLASH: case ITEM_GROUP_FLUID: break; case ITEM_GROUP_CONTAINER: item->type = ITEM_TYPE_CONTAINER; break; default: warnings.push_back("Unknown item group declaration"); break; } uint32_t flags; if (itemNode->getU32(flags)) { item->unpassable = ((flags & FLAG_UNPASSABLE) == FLAG_UNPASSABLE); item->blockMissiles = ((flags & FLAG_BLOCK_MISSILES) == FLAG_BLOCK_MISSILES); item->blockPathfinder = ((flags & FLAG_BLOCK_PATHFINDER) == FLAG_BLOCK_PATHFINDER); item->hasElevation = ((flags & FLAG_HAS_ELEVATION) == FLAG_HAS_ELEVATION); item->pickupable = ((flags & FLAG_PICKUPABLE) == FLAG_PICKUPABLE); item->moveable = ((flags & FLAG_MOVEABLE) == FLAG_MOVEABLE); item->stackable = ((flags & FLAG_STACKABLE) == FLAG_STACKABLE); item->floorChangeDown = ((flags & FLAG_FLOORCHANGEDOWN) == FLAG_FLOORCHANGEDOWN); item->floorChangeNorth = ((flags & FLAG_FLOORCHANGENORTH) == FLAG_FLOORCHANGENORTH); item->floorChangeEast = ((flags & FLAG_FLOORCHANGEEAST) == FLAG_FLOORCHANGEEAST); item->floorChangeSouth = ((flags & FLAG_FLOORCHANGESOUTH) == FLAG_FLOORCHANGESOUTH); item->floorChangeWest = ((flags & FLAG_FLOORCHANGEWEST) == FLAG_FLOORCHANGEWEST); item->floorChange = item->floorChangeDown || item->floorChangeNorth || item->floorChangeEast || item->floorChangeSouth || item->floorChangeWest; // Now this is confusing, just accept that the ALWAYSONTOP flag means it's always on bottom, got it?! item->alwaysOnBottom = ((flags & FLAG_ALWAYSONTOP) == FLAG_ALWAYSONTOP); item->isHangable = ((flags & FLAG_HANGABLE) == FLAG_HANGABLE); item->hookEast = ((flags & FLAG_HOOK_EAST) == FLAG_HOOK_EAST); item->hookSouth = ((flags & FLAG_HOOK_SOUTH) == FLAG_HOOK_SOUTH); item->allowDistRead = ((flags & FLAG_ALLOWDISTREAD) == FLAG_ALLOWDISTREAD); item->rotable = ((flags & FLAG_ROTABLE) == FLAG_ROTABLE); item->canReadText = ((flags & FLAG_READABLE) == FLAG_READABLE); item->client_chargeable = ((flags & FLAG_CLIENTCHARGES) == FLAG_CLIENTCHARGES); item->ignoreLook = ((flags & FLAG_IGNORE_LOOK) == FLAG_IGNORE_LOOK); } uint8_t attribute; while (itemNode->getU8(attribute)) { uint16_t datalen; if (!itemNode->getU16(datalen)) { warnings.push_back("Invalid item type property"); break; } switch (attribute) { case ITEM_ATTR_SERVERID: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of server id block (Should be 2 bytes)"; return false; } if (!itemNode->getU16(item->id)) { warnings.push_back("Invalid item type property (2)"); } if (maxItemId < item->id) { maxItemId = item->id; } break; } case ITEM_ATTR_CLIENTID: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of client id block (Should be 2 bytes)"; return false; } if (!itemNode->getU16(item->clientID)) { warnings.push_back("Invalid item type property (2)"); } item->sprite = static_cast(g_gui.gfx.getSprite(item->clientID)); break; } case ITEM_ATTR_SPEED: { if (datalen != sizeof(uint16_t)) { error = "items.otb: Unexpected data length of speed block (Should be 2 bytes)"; return false; } // t->speed = itemNode->getU16(); if (!itemNode->skip(2)) { // Just skip two bytes, we don't need speed warnings.push_back("Invalid item type property (3)"); } break; } case ITEM_ATTR_LIGHT2: { if (datalen != sizeof(lightBlock2)) { warnings.push_back("items.otb: Unexpected data length of item light (2) block (Should be " + i2ws(sizeof(lightBlock2)) + " bytes)"); break; } if (!itemNode->skip(4)) { // Just skip two bytes, we don't need light warnings.push_back("Invalid item type property (4)"); } // t->lightLevel = itemNode->getU16(); // t->lightColor = itemNode->getU16(); break; } case ITEM_ATTR_TOPORDER: { if (datalen != sizeof(uint8_t)) { warnings.push_back("items.otb: Unexpected data length of item toporder block (Should be 1 byte)"); break; } uint8_t value; if (!itemNode->getU8(value)) { warnings.push_back("Invalid item type property (5)"); break; } item->alwaysOnTopOrder = value; break; } default: { // skip unknown attributes itemNode->skip(datalen); // warnings.push_back("items.otb: Skipped unknown attribute"); break; } } } if (items[item->id]) { warnings.push_back("items.otb: Duplicate items"); } items.set(item->id, item); } return true; } bool ItemDatabase::loadFromOtb(const FileName &datafile, wxString &error, wxArrayString &warnings) { std::string filename = nstr((datafile.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + datafile.GetFullName())); DiskNodeFileReadHandle f(filename, StringVector(1, "OTBI")); if (!f.isOk()) { error = "Couldn't open file \"" + wxstr(filename) + "\":" + wxstr(f.getErrorMessage()); return false; } BinaryNode* root = f.getRootNode(); #define safe_get(node, func, ...) \ do { \ if (!node->get##func(__VA_ARGS__)) { \ error = wxstr(f.getErrorMessage()); \ return false; \ } \ } while (false) // Read root flags root->skip(1); // Type info // uint32_t flags = root->skip(4); // Unused? uint8_t attr; safe_get(root, U8, attr); if (attr == ROOT_ATTR_VERSION) { uint16_t datalen; if (!root->getU16(datalen) || datalen != 4 + 4 + 4 + 1 * 128) { error = "items.otb: Size of version header is invalid, updated .otb version?"; return false; } safe_get(root, U32, MajorVersion); // items otb format file version safe_get(root, U32, MinorVersion); // client version safe_get(root, U32, BuildNumber); // revision std::string csd; csd.resize(128); if (!root->getRAW((uint8_t*)csd.data(), 128)) { // CSDVersion ?? error = wxstr(f.getErrorMessage()); return false; } } else { error = "Expected ROOT_ATTR_VERSION as first node of items.otb!"; } if (g_settings.getInteger(Config::CHECK_SIGNATURES)) { if (g_gui.GetCurrentVersion().getOTBVersion().format_version != MajorVersion) { error = "Unsupported items.otb version (version " + i2ws(MajorVersion) + ")"; return false; } } BinaryNode* itemNode = root->getChild(); switch (MajorVersion) { case 1: return loadFromOtbVer1(itemNode, error, warnings); case 2: return loadFromOtbVer2(itemNode, error, warnings); case 3: return loadFromOtbVer3(itemNode, error, warnings); } return true; } bool ItemDatabase::loadItemFromGameXml(pugi::xml_node itemNode, uint16_t id) { ClientVersionID clientVersion = g_gui.GetCurrentVersionID(); if (clientVersion < CLIENT_VERSION_980 && id > 20000 && id < 20100) { itemNode = itemNode.next_sibling(); return true; } else if (id > 30000 && id < 30100) { itemNode = itemNode.next_sibling(); return true; } // Não verificar isValidID se o ID estiver entre 1 e 18, mas verificar para outros IDs. if (!(id >= 1 && id <= 18) && !isValidID(id)) { return false; } ItemType &item = getItemType(id); item.name = itemNode.attribute("name").as_string(); item.editorsuffix = itemNode.attribute("editorsuffix").as_string(); pugi::xml_attribute attribute; for (pugi::xml_node itemAttributesNode = itemNode.first_child(); itemAttributesNode; itemAttributesNode = itemAttributesNode.next_sibling()) { if (!(attribute = itemAttributesNode.attribute("key"))) { continue; } std::string key = attribute.as_string(); to_lower_str(key); if (key == "type") { if (!(attribute = itemAttributesNode.attribute("value"))) { continue; } std::string typeValue = attribute.as_string(); to_lower_str(key); if (typeValue == "depot") { item.type = ITEM_TYPE_DEPOT; } else if (typeValue == "mailbox") { item.type = ITEM_TYPE_MAILBOX; } else if (typeValue == "trashholder") { item.type = ITEM_TYPE_TRASHHOLDER; } else if (typeValue == "container") { item.type = ITEM_TYPE_CONTAINER; } else if (typeValue == "door") { item.type = ITEM_TYPE_DOOR; } else if (typeValue == "magicfield") { item.group = ITEM_GROUP_MAGICFIELD; item.type = ITEM_TYPE_MAGICFIELD; } else if (typeValue == "teleport") { item.type = ITEM_TYPE_TELEPORT; } else if (typeValue == "bed") { item.type = ITEM_TYPE_BED; } else if (typeValue == "key") { item.type = ITEM_TYPE_KEY; } } else if (key == "name") { if ((attribute = itemAttributesNode.attribute("value"))) { item.name = attribute.as_string(); } } else if (key == "description") { if ((attribute = itemAttributesNode.attribute("value"))) { item.description = attribute.as_string(); } } else if (key == "runespellName") { /*if((attribute = itemAttributesNode.attribute("value"))) { it.runeSpellName = attribute.as_string(); }*/ } else if (key == "weight") { if ((attribute = itemAttributesNode.attribute("value"))) { item.weight = attribute.as_int() / 100.f; } } else if (key == "armor") { if ((attribute = itemAttributesNode.attribute("value"))) { item.armor = attribute.as_int(); } } else if (key == "defense") { if ((attribute = itemAttributesNode.attribute("value"))) { item.defense = attribute.as_int(); } } else if (key == "rotateto") { if ((attribute = itemAttributesNode.attribute("value"))) { item.rotateTo = attribute.as_uint(); } } else if (key == "containersize") { if ((attribute = itemAttributesNode.attribute("value"))) { item.volume = attribute.as_uint(); } } else if (key == "readable") { if ((attribute = itemAttributesNode.attribute("value"))) { item.canReadText = attribute.as_bool(); } } else if (key == "writeable") { if ((attribute = itemAttributesNode.attribute("value"))) { item.canWriteText = item.canReadText = attribute.as_bool(); } } else if (key == "decayto") { item.decays = true; } else if (key == "maxtextlen" || key == "maxtextlength") { if ((attribute = itemAttributesNode.attribute("value"))) { item.maxTextLen = attribute.as_uint(); item.canReadText = item.maxTextLen > 0; } } else if (key == "writeonceitemid") { /*if((attribute = itemAttributesNode.attribute("value"))) { it.writeOnceItemId = pugi::cast(attribute.value()); }*/ } else if (key == "allowdistread") { if ((attribute = itemAttributesNode.attribute("value"))) { item.allowDistRead = attribute.as_bool(); } } else if (key == "charges") { if ((attribute = itemAttributesNode.attribute("value"))) { item.charges = attribute.as_uint(); item.extra_chargeable = true; } } else if (key == "floorchange") { if ((attribute = itemAttributesNode.attribute("value"))) { std::string value = attribute.as_string(); if (value == "down") { item.floorChangeDown = true; item.floorChange = true; } else if (value == "north") { item.floorChangeNorth = true; item.floorChange = true; } else if (value == "south") { item.floorChangeSouth = true; item.floorChange = true; } else if (value == "west") { item.floorChangeWest = true; item.floorChange = true; } else if (value == "east") { item.floorChangeEast = true; item.floorChange = true; } else if (value == "northex") { item.floorChange = true; } else if (value == "southex") { item.floorChange = true; } else if (value == "westex") { item.floorChange = true; } else if (value == "eastex") { item.floorChange = true; } else if (value == "southalt") { item.floorChange = true; } else if (value == "eastalt") { item.floorChange = true; } } } } return true; } bool ItemDatabase::loadFromGameXml(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) { error = "Could not load items.xml (Syntax error?)"; return false; } pugi::xml_node node = doc.child("items"); if (!node) { error = "items.xml, invalid root node."; return false; } for (pugi::xml_node itemNode = node.first_child(); itemNode; itemNode = itemNode.next_sibling()) { if (as_lower_str(itemNode.name()) != "item") { continue; } uint16_t fromId = 0; uint16_t toId = 0; if (const pugi::xml_attribute attribute = itemNode.attribute("id")) { fromId = toId = attribute.as_uint(); } else { fromId = itemNode.attribute("fromid").as_uint(); toId = itemNode.attribute("toid").as_uint(); } if (fromId == 0 || toId == 0) { error = "Could not read item id from item node, fromid " + std::to_string(fromId) + ", toid " + std::to_string(toId) + "."; return false; } for (uint16_t id = fromId; id <= toId; ++id) { if (!loadItemFromGameXml(itemNode, id)) { error = wxString::Format("Could not load item id %d. Item id not found.", id); return false; } } } return true; } bool ItemDatabase::loadMetaItem(pugi::xml_node node) { if (const pugi::xml_attribute attribute = node.attribute("id")) { const uint16_t id = attribute.as_uint(); if (id == 0 || items[id]) { return false; } auto item = newd(); item->is_metaitem = true; item->id = id; items.set(id, item); return true; } return false; } std::shared_ptr ItemDatabase::getItemType(uint16_t id) { if (id == 0 || id > maxItemId) { return dummy; } auto type = items[id]; if (type) { return type; } return dummy; } std::shared_ptr ItemDatabase::getRawItemType(uint16_t id) { if (id == 0 || id > maxItemId) { return nullptr; } return items[id]; } bool ItemDatabase::isValidID(uint16_t id) const { if (id == 0 || id > maxItemId) { return false; } return items[id] != nullptr; }