//////////////////////////////////////////////////////////////////////// // OpenTibia - an opensource roleplaying game //////////////////////////////////////////////////////////////////////// // This program 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. // // This program 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 "otpch.h" #include "iomapserialize.h" #include "house.h" #include "iologindata.h" #include "configmanager.h" #include "game.h" extern ConfigManager g_config; extern Game g_game; bool IOMapSerialize::loadMap(Map* map) { if(g_config.getBool(ConfigManager::HOUSE_STORAGE)) return loadMapBinary(map); return loadMapRelational(map); } bool IOMapSerialize::saveMap(Map* map) { if(g_config.getBool(ConfigManager::HOUSE_STORAGE)) return saveMapBinary(map); return saveMapRelational(map); } bool IOMapSerialize::updateAuctions() { Database* db = Database::getInstance(); DBQuery query; time_t now = time(NULL); query << "SELECT `house_id`, `player_id`, `bid` FROM `house_auctions` WHERE `endtime` < " << now; DBResult* result; if(!(result = db->storeQuery(query.str()))) return true; bool success = true; House* house = NULL; do { query.str(""); query << "DELETE FROM `house_auctions` WHERE `house_id` = " << result->getDataInt("house_id"); if(!(house = Houses::getInstance().getHouse(result->getDataInt( "house_id"))) || !db->executeQuery(query.str())) { success = false; continue; } house->setHouseOwner(result->getDataInt("player_id")); Houses::getInstance().payHouse(house, now, result->getDataInt("bid")); } while(result->next()); result->free(); return success; } bool IOMapSerialize::loadHouses() { Database* db = Database::getInstance(); DBQuery query; query << "SELECT * FROM `houses` WHERE `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID); DBResult* result; if(!(result = db->storeQuery(query.str()))) return false; House* house = NULL; do { if(!(house = Houses::getInstance().getHouse(result->getDataInt("id")))) continue; house->setRentWarnings(result->getDataInt("warnings")); house->setLastWarning(result->getDataInt("lastwarning")); house->setPaidUntil(result->getDataInt("paid")); house->setHouseOwner(result->getDataInt("owner")); if(result->getDataInt("clear")) house->setPendingTransfer(true); } while(result->next()); result->free(); for(HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { if(!(house = it->second) || !house->getHouseId() || !house->getHouseOwner()) continue; query.str(""); query << "SELECT `listid`, `list` FROM `house_lists` WHERE `house_id` = " << house->getHouseId(); query << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID); if(!(result = db->storeQuery(query.str()))) continue; do house->setAccessList(result->getDataInt("listid"), result->getDataString("list")); while(result->next()); result->free(); } return true; } bool IOMapSerialize::updateHouses() { Database* db = Database::getInstance(); DBQuery query; House* house = NULL; for(HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { if(!(house = it->second)) continue; query << "SELECT `id` FROM `houses` WHERE `id` = " << house->getHouseId() << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID) << " LIMIT 1"; if(DBResult* result = db->storeQuery(query.str())) { result->free(); query.str(""); query << "UPDATE `houses` SET "; if(house->hasSyncFlag(House::HOUSE_SYNC_NAME)) query << "`name` = " << db->escapeString(house->getName()) << ", "; if(house->hasSyncFlag(House::HOUSE_SYNC_TOWN)) query << "`town` = " << house->getTownId() << ", "; if(house->hasSyncFlag(House::HOUSE_SYNC_SIZE)) query << "`size` = " << house->getSize() << ", "; if(house->hasSyncFlag(House::HOUSE_SYNC_PRICE)) query << "`price` = " << house->getPrice() << ", "; if(house->hasSyncFlag(House::HOUSE_SYNC_RENT)) query << "`rent` = " << house->getRent() << ", "; query << "`doors` = " << house->getDoorsCount() << ", `beds` = " << house->getBedsCount() << ", `tiles` = " << house->getTilesCount(); if(house->hasSyncFlag(House::HOUSE_SYNC_GUILD)) query << ", `guild` = " << house->isGuild(); query << " WHERE `id` = " << house->getHouseId() << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID) << db->getUpdateLimiter(); } else { query.str(""); query << "INSERT INTO `houses` (`id`, `world_id`, `owner`, `name`, `town`, `size`, `price`, `rent`, `doors`, `beds`, `tiles`, `guild`) VALUES (" << house->getHouseId() << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", 0, " //we need owner for compatibility reasons (field doesn't have a default value) << db->escapeString(house->getName()) << ", " << house->getTownId() << ", " << house->getSize() << ", " << house->getPrice() << ", " << house->getRent() << ", " << house->getDoorsCount() << ", " << house->getBedsCount() << ", " << house->getTilesCount() << ", " << house->isGuild() << ")"; } if(!db->executeQuery(query.str())) return false; query.str(""); } return true; } bool IOMapSerialize::saveHouses() { Database* db = Database::getInstance(); DBTransaction trans(db); if(!trans.begin()) return false; for(HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) saveHouse(db, it->second); return trans.commit(); } bool IOMapSerialize::saveHouse(Database* db, House* house) { DBQuery query; query << "UPDATE `houses` SET `owner` = " << house->getHouseOwner() << ", `paid` = " << house->getPaidUntil() << ", `warnings` = " << house->getRentWarnings() << ", `lastwarning` = " << house->getLastWarning() << ", `clear` = 0 WHERE `id` = " << house->getHouseId() << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID) << db->getUpdateLimiter(); if(!db->executeQuery(query.str())) return false; query.str(""); query << "DELETE FROM `house_lists` WHERE `house_id` = " << house->getHouseId() << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID); if(!db->executeQuery(query.str())) return false; DBInsert queryInsert(db); queryInsert.setQuery("INSERT INTO `house_lists` (`house_id`, `world_id`, `listid`, `list`) VALUES "); std::string listText; if(house->getAccessList(GUEST_LIST, listText) && !listText.empty()) { query.str(""); query << house->getHouseId() << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", " << GUEST_LIST << ", " << db->escapeString(listText); if(!queryInsert.addRow(query.str())) return false; } if(house->getAccessList(SUBOWNER_LIST, listText) && !listText.empty()) { query.str(""); query << house->getHouseId() << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", " << SUBOWNER_LIST << ", " << db->escapeString(listText); if(!queryInsert.addRow(query.str())) return false; } for(HouseDoorList::iterator it = house->getHouseDoorBegin(); it != house->getHouseDoorEnd(); ++it) { const Door* door = (*it); if(!door) continue; if(door->getAccessList(listText) && !listText.empty()) { query.str(""); query << house->getHouseId() << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", " << door->getDoorId() << ", " << db->escapeString(listText); if(!queryInsert.addRow(query.str())) return false; } } return query.str().empty() || queryInsert.execute(); } bool IOMapSerialize::loadMapRelational(Map* map) { Database* db = Database::getInstance(); DBQuery query; //lock mutex! House* house = NULL; for(HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { if(!(house = it->second)) continue; query.str(""); query << "SELECT * FROM `tiles` WHERE `house_id` = " << house->getHouseId() << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID); if(DBResult* result = db->storeQuery(query.str())) { do { query.str(""); query << "SELECT * FROM `tile_items` WHERE `tile_id` = " << result->getDataInt("id") << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID) << " ORDER BY `sid` DESC"; if(DBResult* itemsResult = db->storeQuery(query.str())) { if(house->hasPendingTransfer()) { if(Player* player = g_game.getPlayerByGuidEx(house->getHouseOwner())) { Depot* depot = player->getDepot(house->getTownId(), true); loadItems(db, itemsResult, depot, true); if(player->isVirtual()) { IOLoginData::getInstance()->savePlayer(player); delete player; } } } else { Position pos(result->getDataInt("x"), result->getDataInt("y"), result->getDataInt("z")); Tile* tile = map->getTile(pos); if(!tile) { std::cout << "[Error - IOMapSerialize::loadMapRelational] Unserialization" << " of invalid tile at position "<< pos << std::endl; continue; } loadItems(db, itemsResult, tile, false); } itemsResult->free(); } } while(result->next()); result->free(); } else //backward compatibility { for(HouseTileList::iterator it = house->getHouseTileBegin(); it != house->getHouseTileEnd(); ++it) { query.str(""); query << "SELECT `id` FROM `tiles` WHERE `x` = " << (*it)->getPosition().x << " AND `y` = " << (*it)->getPosition().y << " AND `z` = " << (*it)->getPosition().z << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID) << " LIMIT 1"; if(DBResult* result = db->storeQuery(query.str())) { query.str(""); query << "SELECT * FROM `tile_items` WHERE `tile_id` = " << result->getDataInt("id") << " AND `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID) << " ORDER BY `sid` DESC"; if(DBResult* itemsResult = db->storeQuery(query.str())) { if(house->hasPendingTransfer()) { if(Player* player = g_game.getPlayerByGuidEx(house->getHouseOwner())) { Depot* depot = player->getDepot(house->getTownId(), true); loadItems(db, itemsResult, depot, true); if(player->isVirtual()) { IOLoginData::getInstance()->savePlayer(player); delete player; } } } else loadItems(db, itemsResult, (*it), false); itemsResult->free(); } result->free(); } } } } return true; } bool IOMapSerialize::saveMapRelational(Map* map) { Database* db = Database::getInstance(); //Start the transaction DBTransaction trans(db); if(!trans.begin()) return false; //clear old tile data DBQuery query; query << "DELETE FROM `tile_items` WHERE `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID); if(!db->executeQuery(query.str())) return false; query.str(""); query << "DELETE FROM `tiles` WHERE `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID); if(!db->executeQuery(query.str())) return false; uint32_t tileId = 0; for(HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { //save house items for(HouseTileList::iterator tit = it->second->getHouseTileBegin(); tit != it->second->getHouseTileEnd(); ++tit) saveItems(db, tileId, it->second->getHouseId(), *tit); } //End the transaction return trans.commit(); } bool IOMapSerialize::loadMapBinary(Map* map) { Database* db = Database::getInstance(); DBResult* result; DBQuery query; query << "SELECT `house_id`, `data` FROM `house_data` WHERE `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID); if(!(result = db->storeQuery(query.str()))) return false; House* house = NULL; do { int32_t houseId = result->getDataInt("house_id"); house = Houses::getInstance().getHouse(houseId); uint64_t attrSize = 0; const char* attr = result->getDataStream("data", attrSize); PropStream propStream; propStream.init(attr, attrSize); while(propStream.size()) { uint16_t x = 0, y = 0; uint8_t z = 0; propStream.GET_USHORT(x); propStream.GET_USHORT(y); propStream.GET_UCHAR(z); Position pos(x, y, (int16_t)z); uint32_t itemCount = 0; propStream.GET_ULONG(itemCount); if(house && house->hasPendingTransfer()) { if(Player* player = g_game.getPlayerByGuidEx(house->getHouseOwner())) { Depot* depot = player->getDepot(player->getTown(), true); while(itemCount--) loadItem(propStream, depot, true); if(player->isVirtual()) { IOLoginData::getInstance()->savePlayer(player); delete player; } } } else if(Tile* tile = map->getTile(pos)) { while(itemCount--) loadItem(propStream, tile, false); } else { std::cout << "[Error - IOMapSerialize::loadMapBinary] Unserialization of invalid tile at position "; std::cout << pos << std::endl; break; } } } while(result->next()); result->free(); return true; } bool IOMapSerialize::saveMapBinary(Map* map) { Database* db = Database::getInstance(); //Start the transaction DBTransaction transaction(db); if(!transaction.begin()) return false; DBQuery query; query << "DELETE FROM `house_data` WHERE `world_id` = " << g_config.getNumber(ConfigManager::WORLD_ID); if(!db->executeQuery(query.str())) return false; DBInsert stmt(db); stmt.setQuery("INSERT INTO `house_data` (`house_id`, `world_id`, `data`) VALUES "); for(HouseMap::iterator it = Houses::getInstance().getHouseBegin(); it != Houses::getInstance().getHouseEnd(); ++it) { //save house items PropWriteStream stream; for(HouseTileList::iterator tit = it->second->getHouseTileBegin(); tit != it->second->getHouseTileEnd(); ++tit) { if(!saveTile(stream, *tit)) return false; } uint32_t attributesSize = 0; const char* attributes = stream.getStream(attributesSize); query.str(""); query << it->second->getHouseId() << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", " << db->escapeBlob(attributes, attributesSize); if(!stmt.addRow(query)) return false; } query.str(""); if(!stmt.execute()) return false; //End the transaction return transaction.commit(); } bool IOMapSerialize::loadItems(Database* db, DBResult* result, Cylinder* parent, bool depotTransfer/* = false*/) { ItemMap itemMap; Tile* tile = NULL; if(!parent->getItem()) tile = parent->getTile(); Item* item = NULL; int32_t sid, pid, id, count; do { sid = result->getDataInt("sid"); pid = result->getDataInt("pid"); id = result->getDataInt("itemtype"); count = result->getDataInt("count"); uint64_t attrSize = 0; const char* attr = result->getDataStream("attributes", attrSize); PropStream propStream; propStream.init(attr, attrSize); item = NULL; const ItemType& iType = Item::items[id]; if(iType.moveable || iType.forceSerialize || pid) { if(!(item = Item::CreateItem(id, count))) continue; if(item->unserializeAttr(propStream)) { if(!pid) { parent->__internalAddThing(item); item->__startDecaying(); } } else std::cout << "[Warning - IOMapSerialize::loadItems] Unserialization error [0] for item type " << id << std::endl; } else if(tile) { //find this type in the tile Item* findItem = NULL; for(uint32_t i = 0; i < tile->getThingCount(); ++i) { if(!(findItem = tile->__getThing(i)->getItem())) continue; if(findItem->getID() == id) { item = findItem; break; } if(iType.isDoor() && findItem->getDoor()) { item = findItem; break; } if(iType.isBed() && findItem->getBed()) { item = findItem; break; } } } if(item) { if(item->unserializeAttr(propStream)) { if((item = g_game.transformItem(item, id))) itemMap[sid] = std::make_pair(item, pid); } else std::cout << "[Warning - IOMapSerialize::loadItems] Unserialization error [1] for item type " << id << std::endl; } else if((item = Item::CreateItem(id))) { item->unserializeAttr(propStream); if(!depotTransfer) std::cout << "[Warning - IOMapSerialize::loadItems] NULL item at " << tile->getPosition() << " (type = " << id << ", sid = " << sid << ", pid = " << pid << ")" << std::endl; else itemMap[sid] = std::make_pair(parent->getItem(), pid); delete item; item = NULL; } } while(result->next()); ItemMap::iterator it; for(ItemMap::reverse_iterator rit = itemMap.rbegin(); rit != itemMap.rend(); ++rit) { if(!(item = rit->second.first)) continue; int32_t pid = rit->second.second; it = itemMap.find(pid); if(it == itemMap.end()) continue; if(Container* container = it->second.first->getContainer()) { container->__internalAddThing(item); g_game.startDecay(item); } } return true; } bool IOMapSerialize::saveItems(Database* db, uint32_t& tileId, uint32_t houseId, const Tile* tile) { int32_t thingCount = tile->getThingCount(); if(!thingCount) return true; bool stored = false; int32_t runningId = 0, parentId = 0; Item* item = NULL; ContainerStackList containerStackList; Container* container = NULL; DBInsert query_insert(db); query_insert.setQuery("INSERT INTO `tile_items` (`tile_id`, `world_id`, `sid`, `pid`, `itemtype`, `count`, `attributes`) VALUES "); DBQuery query; for(int32_t i = 0; i < thingCount; ++i) { if(!(item = tile->__getThing(i)->getItem()) || (item->isNotMoveable() && !item->forceSerialize())) continue; if(!stored) { const Position& tilePosition = tile->getPosition(); query << "INSERT INTO `tiles` (`id`, `world_id`, `house_id`, `x`, `y`, `z`) VALUES (" << tileId << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", " << houseId << ", " << tilePosition.x << ", " << tilePosition.y << ", " << tilePosition.z << ")"; if(!db->executeQuery(query.str())) return false; stored = true; query.str(""); } PropWriteStream propWriteStream; item->serializeAttr(propWriteStream); uint32_t attributesSize = 0; const char* attributes = propWriteStream.getStream(attributesSize); query << tileId << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", " << ++runningId << ", " << parentId << ", " << item->getID() << ", " << (int32_t)item->getSubType() << ", " << db->escapeBlob(attributes, attributesSize); if(!query_insert.addRow(query.str())) return false; query.str(""); if(item->getContainer()) containerStackList.push_back(std::make_pair(item->getContainer(), runningId)); } for(ContainerStackList::iterator cit = containerStackList.begin(); cit != containerStackList.end(); ++cit) { container = cit->first; parentId = cit->second; for(ItemList::const_iterator it = container->getItems(); it != container->getEnd(); ++it) { if(!(item = (*it))) continue; PropWriteStream propWriteStream; item->serializeAttr(propWriteStream); uint32_t attributesSize = 0; const char* attributes = propWriteStream.getStream(attributesSize); query << tileId << ", " << g_config.getNumber(ConfigManager::WORLD_ID) << ", " << ++runningId << ", " << parentId << ", " << item->getID() << ", " << (int32_t)item->getSubType() << ", " << db->escapeBlob(attributes, attributesSize); if(!query_insert.addRow(query.str())) return false; query.str(""); if(item->getContainer()) containerStackList.push_back(std::make_pair(item->getContainer(), runningId)); } } if(stored) ++tileId; return query_insert.execute(); } bool IOMapSerialize::loadContainer(PropStream& propStream, Container* container) { while(container->serializationCount > 0) { if(!loadItem(propStream, container, false)) { std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error [0] for item in container " << container->getID() << std::endl; return false; } container->serializationCount--; } uint8_t endAttr = 0; propStream.GET_UCHAR(endAttr); if(endAttr == ATTR_END) return true; std::cout << "[Warning - IOMapSerialize::loadContainer] Unserialization error [1] for item in container " << container->getID() << std::endl; return false; } bool IOMapSerialize::loadItem(PropStream& propStream, Cylinder* parent, bool depotTransfer/* = false*/) { Tile* tile = NULL; if(!parent->getItem()) tile = parent->getTile(); uint16_t id = 0; propStream.GET_USHORT(id); Item* item = NULL; const ItemType& iType = Item::items[id]; if(iType.moveable || iType.forceSerialize || (!depotTransfer && !tile)) { if((item = Item::CreateItem(id))) { if(item->unserializeAttr(propStream)) { if(Container* container = item->getContainer()) { if(!loadContainer(propStream, container)) { delete item; return false; } } if(parent) { parent->__internalAddThing(item); item->__startDecaying(); } else delete item; } else { std::cout << "[Warning - IOMapSerialize::loadItem] Unserialization error [0] for item type " << id << std::endl; delete item; return false; } } return true; } if(tile) { //Stationary items Item* findItem = NULL; for(uint32_t i = 0; i < tile->getThingCount(); ++i) { if(!(findItem = tile->__getThing(i)->getItem())) continue; if(findItem->getID() == id) { item = findItem; break; } if(iType.isDoor() && findItem->getDoor()) { item = findItem; break; } if(iType.isBed() && findItem->getBed()) { item = findItem; break; } } } if(item) { if(item->unserializeAttr(propStream)) { Container* container = item->getContainer(); if(container && !loadContainer(propStream, container)) return false; item = g_game.transformItem(item, id); } else std::cout << "[Warning - IOMapSerialize::loadItem] Unserialization error [1] for item type " << id << std::endl; return true; } //The map changed since the last save, just read the attributes if(!(item = Item::CreateItem(id))) return true; item->unserializeAttr(propStream); if(Container* container = item->getContainer()) { if(!loadContainer(propStream, container)) { delete item; return false; } if(depotTransfer) { for(ItemList::const_iterator it = container->getItems(); it != container->getEnd(); ++it) parent->__addThing(NULL, (*it)); container->itemlist.clear(); } } delete item; return true; } bool IOMapSerialize::saveTile(PropWriteStream& stream, const Tile* tile) { int32_t tileCount = tile->getThingCount(); if(!tileCount) return true; std::vector items; Item* item = NULL; for(; tileCount > 0; --tileCount) { if((item = tile->__getThing(tileCount - 1)->getItem()) && (item->isMoveable() || item->forceSerialize())) items.push_back(item); } tileCount = items.size(); //lame, but at least we don't need new variable if(tileCount > 0) { stream.ADD_USHORT(tile->getPosition().x); stream.ADD_USHORT(tile->getPosition().y); stream.ADD_UCHAR(tile->getPosition().z); stream.ADD_ULONG(tileCount); for(std::vector::iterator it = items.begin(); it != items.end(); ++it) saveItem(stream, (*it)); } return true; } bool IOMapSerialize::saveItem(PropWriteStream& stream, const Item* item) { stream.ADD_USHORT(item->getID()); item->serializeAttr(stream); if(const Container* container = item->getContainer()) { stream.ADD_UCHAR(ATTR_CONTAINER_ITEMS); stream.ADD_ULONG(container->size()); for(ItemList::const_reverse_iterator rit = container->getReversedItems(); rit != container->getReversedEnd(); ++rit) saveItem(stream, (*rit)); } stream.ADD_UCHAR(ATTR_END); return true; }