void ThingTypeManager::loadOtb(const std::string& file) { FileStreamPtr fin = g_resources.openFile(file); uint signature = fin->getU32(); if(signature != 0) stdext::throw_exception("invalid otb file"); BinaryTreePtr root = fin->getBinaryTree(); signature = root->getU32(); if(signature != 0) stdext::throw_exception("invalid otb file"); root->getU32(); // flags m_otbMajorVersion = root->getU32(); m_otbMinorVersion = root->getU32(); root->getU32(); // build number root->skip(128); // description m_itemTypes.resize(root->getChildren().size(), m_nullItemType); for(const BinaryTreePtr& node : root->getChildren()) { ItemTypePtr itemType(new ItemType); itemType->unserialize(node); addItemType(itemType); } m_otbLoaded = true; }
SoundFilePtr SoundFile::loadSoundFile(const std::string& filename) { FileStreamPtr file = g_resources.openFile(filename); if(!file) { logTraceError("unable to open ", filename); return nullptr; } // cache file buffer to avoid lags from hard drive file->cache(); char magic[4]; file->read(magic, 4); file->seek(0); SoundFilePtr soundFile; if(strncmp(magic, "OggS", 4) == 0) { OggSoundFilePtr oggSoundFile = OggSoundFilePtr(new OggSoundFile(file)); if(oggSoundFile->prepareOgg()) soundFile = oggSoundFile; } else logError("unknown sound file format ", filename); return soundFile; }
bool ThingTypeManager::loadDat(const std::string& file) { m_datLoaded = false; m_datSignature = 0; try { FileStreamPtr fin = g_resources.openFile(file); m_datSignature = fin->getU32(); int numThings[ThingLastCategory]; for(int category = 0; category < ThingLastCategory; ++category) { int count = fin->getU16() + 1; m_thingTypes[category].clear(); m_thingTypes[category].resize(count, m_nullThingType); } for(int category = 0; category < ThingLastCategory; ++category) { uint16 firstId = 1; if(category == ThingCategoryItem) firstId = 100; for(uint16 id = firstId; id < m_thingTypes[category].size(); ++id) { ThingTypePtr type(new ThingType); type->unserialize(id, (ThingCategory)category, fin); m_thingTypes[category][id] = type; } } m_datLoaded = true; return true; } catch(stdext::exception& e) { g_logger.error(stdext::format("Failed to read dat '%s': %s'", file, e.what())); return false; } }
void Image::savePNG(const std::string& fileName) { FileStreamPtr fin = g_resources.createFile(fileName); if(!fin) stdext::throw_exception(stdext::format("failed to open file '%s' for write", fileName)); fin->cache(); std::stringstream data; save_png(data, m_size.width(), m_size.height(), 4, (unsigned char*)getPixelData()); fin->write(data.str().c_str(), data.str().length()); fin->flush(); fin->close(); }
void ThingTypeManager::loadOtb(const std::string& file) { try { FileStreamPtr fin = g_resources.openFile(file); uint signature = fin->getU32(); if(signature != 0) stdext::throw_exception("invalid otb file"); BinaryTreePtr root = fin->getBinaryTree(); root->skip(1); // otb first byte is always 0 signature = root->getU32(); if(signature != 0) stdext::throw_exception("invalid otb file"); uint8 rootAttr = root->getU8(); if(rootAttr == 0x01) { // OTB_ROOT_ATTR_VERSION uint16 size = root->getU16(); if(size != 4 + 4 + 4 + 128) stdext::throw_exception("invalid otb root attr version size"); m_otbMajorVersion = root->getU32(); m_otbMinorVersion = root->getU32(); root->skip(4); // buildNumber root->skip(128); // description } m_reverseItemTypes.clear(); m_itemTypes.resize(root->getChildren().size() + 1, m_nullItemType); m_reverseItemTypes.resize(root->getChildren().size() + 1, m_nullItemType); for(const BinaryTreePtr& node : root->getChildren()) { ItemTypePtr itemType(new ItemType); itemType->unserialize(node); addItemType(itemType); uint16 clientId = itemType->getClientId(); if(unlikely(clientId >= m_reverseItemTypes.size())) m_reverseItemTypes.resize(clientId + 1); m_reverseItemTypes[clientId] = itemType; } m_otbLoaded = true; g_lua.callGlobalField("g_things", "onLoadOtb", file); } catch(std::exception& e) { g_logger.error(stdext::format("Failed to load '%s' (OTB file): %s", file, e.what())); } }
void ThingTypeManager::loadOtb(const std::string& file) { try { FileStreamPtr fin = g_resources.openFile(file); uint signature = fin->getU32(); if(signature != 0) stdext::throw_exception("invalid otb file"); BinaryTreePtr root = fin->getBinaryTree(); signature = root->getU32(); if(signature != 0) stdext::throw_exception("invalid otb file"); root->skip(4); m_otbMajorVersion = root->getU32(); m_otbMinorVersion = root->getU32(); root->skip(4); root->skip(128); // description m_reverseItemTypes.clear(); m_itemTypes.resize(root->getChildren().size() + 1, m_nullItemType); m_reverseItemTypes.resize(root->getChildren().size() + 1, m_nullItemType); for(const BinaryTreePtr& node : root->getChildren()) { ItemTypePtr itemType(new ItemType); itemType->unserialize(node); addItemType(itemType); uint16 clientId = itemType->getClientId(); if(unlikely(clientId >= m_reverseItemTypes.size())) m_reverseItemTypes.resize(clientId + 1); m_reverseItemTypes[clientId] = itemType; } m_otbLoaded = true; } catch(std::exception& e) { g_logger.error(stdext::format("Failed to load '%s' (OTB file): %s", file, e.what())); } }
void ThingTypeManager::saveDat(std::string fileName) { if(!m_datLoaded) stdext::throw_exception("failed to save, dat is not loaded"); try { FileStreamPtr fin = g_resources.createFile(fileName); if(!fin) stdext::throw_exception(stdext::format("failed to open file '%s' for write", fileName)); fin->cache(); fin->addU32(m_datSignature); for(int category = 0; category < ThingLastCategory; ++category) fin->addU16(m_thingTypes[category].size() - 1); for(int category = 0; category < ThingLastCategory; ++category) { uint16 firstId = 1; if(category == ThingCategoryItem) firstId = 100; for(uint16 id = firstId; id < m_thingTypes[category].size(); ++id) m_thingTypes[category][id]->serialize(fin); } fin->flush(); fin->close(); } catch(std::exception& e) { g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); } }
void BinaryTree::unserialize(const FileStreamPtr& fin) { while(true) { uint8 byte = fin->getU8(); switch(byte) { case BINARYTREE_NODE_START: { BinaryTreePtr node(new BinaryTree(shared_from_this())); m_children.push_back(node); node->unserialize(fin); break; } case BINARYTREE_NODE_END: return; case BINARYTREE_ESCAPE_CHAR: m_buffer.add(fin->getU8()); break; default: m_buffer.add(byte); break; } } }
bool CSceneData::save(const std::string& file, f32 version) { if (m_objectList.empty()) { LOG_ERROR("CSceneData::save: Object list empty, nothing save"); return false; } LOG_DEBUG("CSceneData::save: Serialize model to memory stream"); MemoryStreamPtr stream = CStreamManager::createMemoryStream(); stream->seekBeg(0); stream->write(version); stream->write(m_id); stream->write(m_name); if (!CSceneData::saveMaterial(stream)) { LOG_ERROR("CSceneData::save: Material serialize failed"); return false; } if (!CSceneData::saveGeometry(stream)) { LOG_ERROR("CSceneData::save: Geometry serialize failed"); return false; } LOG_DEBUG("CSceneData::save: Save memory stream to file stream [%s]. Size [%d]", file.c_str(), stream->size()); FileStreamPtr fileStream = CStreamManager::createFileStream(file, FileStream::e_create); fileStream->write(stream->getData(), sizeof(u8), stream->size()); fileStream->close(); return true; }
void Map::saveOtcm(const std::string& fileName) { #if 0 try { g_clock.update(); FileStreamPtr fin = g_resources.createFile(fileName); fin->cache(); //TODO: compression flag with zlib uint32 flags = 0; // header fin->addU32(OTCM_SIGNATURE); fin->addU16(0); // data start, will be overwritten later fin->addU16(OTCM_VERSION); fin->addU32(flags); // version 1 header fin->addString("OTCM 1.0"); // map description fin->addU32(g_things.getDatSignature()); fin->addU16(g_game.getClientVersion()); fin->addString(g_game.getWorldName()); // go back and rewrite where the map data starts uint32 start = fin->tell(); fin->seek(4); fin->addU16(start); fin->seek(start); for(auto& pair : m_tiles) { const TilePtr& tile = pair.second; if(!tile || tile->isEmpty()) continue; Position pos = pair.first; fin->addU16(pos.x); fin->addU16(pos.y); fin->addU8(pos.z); const auto& list = tile->getThings(); auto first = std::find_if(list.begin(), list.end(), [](const ThingPtr& thing) { return thing->isItem(); }); for(auto it = first, end = list.end(); it != end; ++it) { const ThingPtr& thing = *it; if(thing->isItem()) { ItemPtr item = thing->static_self_cast<Item>(); fin->addU16(item->getId()); fin->addU8(item->getCountOrSubType()); } } // end of tile fin->addU16(0xFFFF); } // end of file Position invalidPos; fin->addU16(invalidPos.x); fin->addU16(invalidPos.y); fin->addU8(invalidPos.z); fin->flush(); fin->close(); } catch(stdext::exception& e) { g_logger.error(stdext::format("failed to save OTCM map: %s", e.what())); } #endif }
bool Map::loadOtcm(const std::string& fileName) { try { FileStreamPtr fin = g_resources.openFile(fileName); if(!fin) stdext::throw_exception("unable to open file"); stdext::timer loadTimer; fin->cache(); uint32 signature = fin->getU32(); if(signature != OTCM_SIGNATURE) stdext::throw_exception("invalid otcm file"); uint16 start = fin->getU16(); uint16 version = fin->getU16(); fin->getU32(); // flags switch(version) { case 1: { fin->getString(); // description uint32 datSignature = fin->getU32(); fin->getU16(); // protocol version fin->getString(); // world name if(datSignature != g_things.getDatSignature()) g_logger.warning("otcm map loaded was created with a different dat signature"); break; } default: stdext::throw_exception("otcm version not supported"); } fin->seek(start); while(true) { Position pos; pos.x = fin->getU16(); pos.y = fin->getU16(); pos.z = fin->getU8(); // end of file if(!pos.isValid()) break; const TilePtr& tile = g_map.createTile(pos); int stackPos = 0; while(true) { int id = fin->getU16(); // end of tile if(id == 0xFFFF) break; int countOrSubType = fin->getU8(); ItemPtr item = Item::create(id); item->setCountOrSubType(countOrSubType); if(item->isValid()) tile->addThing(item, stackPos++); } } fin->close(); g_logger.debug(stdext::format("Otcm load time: %.2f seconds", loadTimer.elapsed_seconds())); return true; } catch(stdext::exception& e) { g_logger.error(stdext::format("failed to load OTCM map: %s", e.what())); return false; } }
void Map::loadOtbm(const std::string& fileName) { FileStreamPtr fin = g_resources.openFile(fileName); if(!fin) stdext::throw_exception(stdext::format("Unable to load map '%s'", fileName)); fin->cache(); if(!g_things.isOtbLoaded()) stdext::throw_exception("OTB isn't loaded yet to load a map."); if(fin->getU32()) stdext::throw_exception("Unknown file version detected"); BinaryTreePtr root = fin->getBinaryTree(); if(root->getU8()) stdext::throw_exception("could not read root property!"); uint32 headerVersion = root->getU32(); if(!headerVersion || headerVersion > 3) stdext::throw_exception(stdext::format("Unknown OTBM version detected: %u.", headerVersion)); setWidth(root->getU16()); setHeight(root->getU16()); uint32 headerMajorItems = root->getU8(); if(headerMajorItems < 3) { stdext::throw_exception(stdext::format("This map needs to be upgraded. read %d what it's supposed to be: %u", headerMajorItems, g_things.getOtbMajorVersion())); } if(headerMajorItems > g_things.getOtbMajorVersion()) { stdext::throw_exception(stdext::format("This map was saved with different OTB version. read %d what it's supposed to be: %d", headerMajorItems, g_things.getOtbMajorVersion())); } root->skip(3); uint32 headerMinorItems = root->getU32(); if(headerMinorItems > g_things.getOtbMinorVersion()) { g_logger.warning(stdext::format("This map needs an updated OTB. read %d what it's supposed to be: %d or less", headerMinorItems, g_things.getOtbMinorVersion())); } BinaryTreePtr node = root->getChildren()[0]; if(node->getU8() != OTBM_MAP_DATA) stdext::throw_exception("Could not read root data node"); while (node->canRead()) { uint8 attribute = node->getU8(); std::string tmp = node->getString(); switch (attribute) { case OTBM_ATTR_DESCRIPTION: setDescription(tmp); break; case OTBM_ATTR_SPAWN_FILE: setSpawnFile(fileName.substr(0, fileName.rfind('/') + 1) + tmp); break; case OTBM_ATTR_HOUSE_FILE: setHouseFile(fileName.substr(0, fileName.rfind('/') + 1) + tmp); break; default: stdext::throw_exception(stdext::format("Invalid attribute '%d'", (int)attribute)); } } for(const BinaryTreePtr &nodeMapData : node->getChildren()) { uint8 mapDataType = nodeMapData->getU8(); if(mapDataType == OTBM_TILE_AREA) { Position basePos = nodeMapData->getPosition(); for(const BinaryTreePtr &nodeTile : nodeMapData->getChildren()) { uint8 type = nodeTile->getU8(); if(type != OTBM_TILE && type != OTBM_HOUSETILE) stdext::throw_exception(stdext::format("invalid node tile type %d", (int)type)); HousePtr house = nullptr; uint32 flags = TILESTATE_NONE; Position pos = basePos + nodeTile->getPoint(); if(type == OTBM_HOUSETILE) { uint32 hId = nodeTile->getU32(); TilePtr tile = getOrCreateTile(pos); if(!(house = m_houses.getHouse(hId))) { house = HousePtr(new House(hId)); m_houses.addHouse(house); } house->setTile(tile); } while(nodeTile->canRead()) { uint8 tileAttr = nodeTile->getU8(); switch (tileAttr) { case OTBM_ATTR_TILE_FLAGS: { uint32 _flags = nodeTile->getU32(); if((_flags & TILESTATE_PROTECTIONZONE) == TILESTATE_PROTECTIONZONE) flags |= TILESTATE_PROTECTIONZONE; else if((_flags & TILESTATE_OPTIONALZONE) == TILESTATE_OPTIONALZONE) flags |= TILESTATE_OPTIONALZONE; else if((_flags & TILESTATE_HARDCOREZONE) == TILESTATE_HARDCOREZONE) flags |= TILESTATE_HARDCOREZONE; if((_flags & TILESTATE_NOLOGOUT) == TILESTATE_NOLOGOUT) flags |= TILESTATE_NOLOGOUT; if((_flags & TILESTATE_REFRESH) == TILESTATE_REFRESH) flags |= TILESTATE_REFRESH; break; } case OTBM_ATTR_ITEM: { addThing(Item::createFromOtb(nodeTile->getU16()), pos); break; } default: { stdext::throw_exception(stdext::format("invalid tile attribute %d at pos %s", (int)tileAttr, stdext::to_string(pos))); } } } for(const BinaryTreePtr &nodeItem : nodeTile->getChildren()) { if(nodeItem->getU8() != OTBM_ITEM) stdext::throw_exception("invalid item node"); ItemPtr item = Item::createFromOtb(nodeItem->getU16()); item->unserializeItem(nodeItem); if(item->isContainer()) { for(const BinaryTreePtr& containerItem : nodeItem->getChildren()) { if(containerItem->getU8() != OTBM_ITEM) stdext::throw_exception("invalid container item node"); ItemPtr cItem = Item::createFromOtb(containerItem->getU16()); cItem->unserializeItem(containerItem); //item->addContainerItem(cItem); } } if(house && item->isMoveable()) { g_logger.warning(stdext::format("Movable item found in house: %d at pos %s - escaping...", item->getId(), stdext::to_string(pos))); item.reset(); } addThing(item, pos); } if(const TilePtr& tile = getTile(pos)) tile->setFlags((tileflags_t)flags); } } else if(mapDataType == OTBM_TOWNS) { TownPtr town = nullptr; for(const BinaryTreePtr &nodeTown : nodeMapData->getChildren()) { if(nodeTown->getU8() != OTBM_TOWN) stdext::throw_exception("invalid town node."); uint32 townId = nodeTown->getU32(); std::string townName = nodeTown->getString(); Position townCoords = nodeTown->getPosition(); if(!(town = m_towns.getTown(townId))) { town = TownPtr(new Town(townId, townName, townCoords)); m_towns.addTown(town); } } } else if(mapDataType == OTBM_WAYPOINTS && headerVersion > 1) { for(const BinaryTreePtr &nodeWaypoint : nodeMapData->getChildren()) { if(nodeWaypoint->getU8() != OTBM_WAYPOINT) stdext::throw_exception("invalid waypoint node."); std::string name = nodeWaypoint->getString(); Position waypointPos = nodeWaypoint->getPosition(); if(waypointPos.isValid() && !name.empty() && m_waypoints.find(waypointPos) == m_waypoints.end()) m_waypoints.insert(std::make_pair(waypointPos, name)); } } else stdext::throw_exception(stdext::format("Unknown map data node %d", (int)mapDataType)); } g_logger.debug("OTBM read successfully."); fin->close(); loadSpawns(getSpawnFile()); // m_houses.load(getHouseFile()); }
void Map::saveOtbm(const std::string &fileName) { FileStreamPtr fin = g_resources.createFile(fileName); if(!fin) stdext::throw_exception(stdext::format("failed to open file '%s' for write", fileName)); std::string dir; if(fileName.find_last_of('/') == std::string::npos) dir = g_resources.getWorkDir(); else dir = fileName.substr(0, fileName.find_last_of('/')); uint32 version = 0; /// Support old versions (< 810 or 860 IIRC) /// TODO: Use constants? if(g_things.getOtbMajorVersion() < 10) version = 1; else version = 2; /// Usually when a map has empty house/spawn file it means the map is new. /// TODO: Ask the user for a map name instead of those ugly uses of substr std::string::size_type sep_pos; std::string houseFile = getHouseFile(); std::string spawnFile = getSpawnFile(); std::string cpyf; if((sep_pos = fileName.rfind('.')) != std::string::npos && stdext::ends_with(fileName, ".otbm")) cpyf = fileName.substr(0, sep_pos); if(houseFile.empty()) houseFile = cpyf + "-houses.xml"; if(spawnFile.empty()) spawnFile = cpyf + "-spawns.xml"; /// we only need the filename to save to, the directory should be resolved by the OTBM loader not here if((sep_pos = spawnFile.rfind('/')) != std::string::npos) spawnFile = spawnFile.substr(sep_pos + 1); if((sep_pos = houseFile.rfind('/')) != std::string::npos) houseFile = houseFile.substr(sep_pos + 1); #if 0 if(version > 1) m_houses->save(dir + "/" + houseFile); saveSpawns(dir + "/" + spawnFile); #endif fin->addU32(0); // file version BinaryWriteTreePtr root(new BinaryWriteTree(fin)); root->startNode(0); { root->writeU32(version); Size mapSize = getSize(); root->writeU16(mapSize.width()); root->writeU16(mapSize.height()); root->writeU32(g_things.getOtbMajorVersion()); root->writeU32(g_things.getOtbMinorVersion()); root->startNode(OTBM_MAP_DATA); { // own description. for(const auto& desc : getDescriptions()) { root->writeU8(OTBM_ATTR_DESCRIPTION); root->writeString(desc); } // special one root->writeU8(OTBM_ATTR_DESCRIPTION); root->writeString(stdext::format("Saved with %s v%s", g_app.getName(), g_app.getVersion())); // spawn file. root->writeU8(OTBM_ATTR_SPAWN_FILE); root->writeString(spawnFile); // house file. if(version > 1) { root->writeU8(OTBM_ATTR_HOUSE_FILE); root->writeString(houseFile); } Position base(-1, -1, -1); bool firstNode = true; for(uint8_t z = 0; z < Otc::MAX_Z + 1; ++z) { for(const auto& it : m_tileBlocks[z]) { const TileBlock& block = it.second; for(const TilePtr& tile : block.getTiles()) { if(!tile) continue; const Position& pos = tile->getPosition(); if(!pos.isValid()) continue; if(pos.x < base.x || pos.x >= base.x + 256 || pos.y < base.y|| pos.y >= base.y + 256 || pos.z != base.z) { if(!firstNode) root->endNode(); /// OTBM_TILE_AREA root->startNode(OTBM_TILE_AREA); firstNode = false; root->writePos(base = pos & 0xFF00); } uint32 flags = tile->getFlags(); root->startNode((flags & TILESTATE_HOUSE) == TILESTATE_HOUSE ? OTBM_HOUSETILE : OTBM_TILE); root->writePoint(Point(pos.x, pos.y) & 0xFF); // if(tileNode->getType() == OTBM_HOUSETILE) // tileNode->writeU32(tile->getHouseId()); if(flags) { root->writeU8(OTBM_ATTR_TILE_FLAGS); root->writeU32(flags); } for(const ItemPtr& item : tile->getItems()) { if(item->isGround()) { root->writeU8(OTBM_ATTR_ITEM); root->writeU16(item->getId()); continue; } item->serializeItem(root); } root->endNode(); // OTBM_TILE } } } if(!firstNode) root->endNode(); // OTBM_TILE_AREA root->startNode(OTBM_TOWNS); for(const TownPtr& town : m_towns.getTowns()) { root->writeU32(town->getId()); root->writeString(town->getName()); root->writePos(town->getPos()); } root->endNode(); if(version > 1) { root->startNode(OTBM_WAYPOINTS); for(const auto& it : m_waypoints) { root->writeString(it.second); root->writePos(it.first); } root->endNode(); } } root->endNode(); // OTBM_MAP_DATA } root->endNode(); // 0 (root) }
void Map::saveOtcm(const std::string& fileName) { try { stdext::timer saveTimer; FileStreamPtr fin = g_resources.createFile(fileName); fin->cache(); //TODO: compression flag with zlib uint32 flags = 0; // header fin->addU32(OTCM_SIGNATURE); fin->addU16(0); // data start, will be overwritten later fin->addU16(OTCM_VERSION); fin->addU32(flags); // version 1 header fin->addString("OTCM 1.0"); // map description fin->addU32(g_things.getDatSignature()); fin->addU16(g_game.getProtocolVersion()); fin->addString(g_game.getWorldName()); // go back and rewrite where the map data starts uint32 start = fin->tell(); fin->seek(4); fin->addU16(start); fin->seek(start); for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { for(const auto& it : m_tileBlocks[z]) { const TileBlock& block = it.second; for(const TilePtr& tile : block.getTiles()) { if(!tile || tile->isEmpty()) continue; Position pos = tile->getPosition(); fin->addU16(pos.x); fin->addU16(pos.y); fin->addU8(pos.z); for(const ThingPtr& thing : tile->getThings()) { if(thing->isItem()) { ItemPtr item = thing->static_self_cast<Item>(); fin->addU16(item->getId()); fin->addU8(item->getCountOrSubType()); } } // end of tile fin->addU16(0xFFFF); } } } // end of file Position invalidPos; fin->addU16(invalidPos.x); fin->addU16(invalidPos.y); fin->addU8(invalidPos.z); fin->flush(); fin->close(); } catch(stdext::exception& e) { g_logger.error(stdext::format("failed to save OTCM map: %s", e.what())); } }
void Map::saveOtbm(const std::string& fileName) { try { FileStreamPtr fin = g_resources.createFile(fileName); if(!fin) stdext::throw_exception(stdext::format("failed to open file '%s' for write", fileName)); fin->cache(); std::string dir; if(fileName.find_last_of('/') == std::string::npos) dir = g_resources.getWorkDir(); else dir = fileName.substr(0, fileName.find_last_of('/')); uint32 version = 0; if(g_things.getOtbMajorVersion() < ClientVersion820) version = 1; else version = 2; /// Usually when a map has empty house/spawn file it means the map is new. /// TODO: Ask the user for a map name instead of those ugly uses of substr std::string::size_type sep_pos; std::string houseFile = getHouseFile(); std::string spawnFile = getSpawnFile(); std::string cpyf; if((sep_pos = fileName.rfind('.')) != std::string::npos && stdext::ends_with(fileName, ".otbm")) cpyf = fileName.substr(0, sep_pos); if(houseFile.empty()) houseFile = cpyf + "-houses.xml"; if(spawnFile.empty()) spawnFile = cpyf + "-spawns.xml"; /// we only need the filename to save to, the directory should be resolved by the OTBM loader not here if((sep_pos = spawnFile.rfind('/')) != std::string::npos) spawnFile = spawnFile.substr(sep_pos + 1); if((sep_pos = houseFile.rfind('/')) != std::string::npos) houseFile = houseFile.substr(sep_pos + 1); fin->addU32(0); // file version OutputBinaryTreePtr root(new OutputBinaryTree(fin)); { root->addU32(version); Size mapSize = getSize(); root->addU16(mapSize.width()); root->addU16(mapSize.height()); root->addU32(g_things.getOtbMajorVersion()); root->addU32(g_things.getOtbMinorVersion()); root->startNode(OTBM_MAP_DATA); { root->addU8(OTBM_ATTR_DESCRIPTION); root->addString(m_attribs.get<std::string>(OTBM_ATTR_DESCRIPTION)); root->addU8(OTBM_ATTR_SPAWN_FILE); root->addString(spawnFile); root->addU8(OTBM_ATTR_HOUSE_FILE); root->addString(houseFile); int px = -1, py = -1, pz =-1; bool firstNode = true; for(uint8_t z = 0; z <= Otc::MAX_Z; ++z) { for(const auto& it : m_tileBlocks[z]) { const TileBlock& block = it.second; for(const TilePtr& tile : block.getTiles()) { if(unlikely(!tile || tile->isEmpty())) continue; const Position& pos = tile->getPosition(); if(unlikely(!pos.isValid())) continue; if(pos.x < px || pos.x >= px + 256 || pos.y < py || pos.y >= py + 256 || pos.z != pz) { if(!firstNode) root->endNode(); /// OTBM_TILE_AREA firstNode = false; root->startNode(OTBM_TILE_AREA); px = pos.x & 0xFF00; py = pos.y & 0xFF00; pz = pos.z; root->addPos(px, py, pz); } root->startNode(tile->isHouseTile() ? OTBM_HOUSETILE : OTBM_TILE); root->addPoint(Point(pos.x, pos.y) & 0xFF); if(tile->isHouseTile()) root->addU32(tile->getHouseId()); if(tile->getFlags()) { root->addU8(OTBM_ATTR_TILE_FLAGS); root->addU32(tile->getFlags()); } const auto& itemList = tile->getItems(); const ItemPtr& ground = tile->getGround(); if(ground) { // Those types are called "complex" needs other stuff to be written. // For containers, there is container items, for depot, depot it and so on. if(!ground->isContainer() && !ground->isDepot() && !ground->isDoor() && !ground->isTeleport()) { root->addU8(OTBM_ATTR_ITEM); root->addU16(ground->getServerId()); } else ground->serializeItem(root); } for(const ItemPtr& item : itemList) if(!item->isGround()) item->serializeItem(root); root->endNode(); // OTBM_TILE } } } if(!firstNode) root->endNode(); // OTBM_TILE_AREA root->startNode(OTBM_TOWNS); for(const TownPtr& town : g_towns.getTowns()) { root->startNode(OTBM_TOWN); root->addU32(town->getId()); root->addString(town->getName()); Position townPos = town->getPos(); root->addPos(townPos.x, townPos.y, townPos.z); root->endNode(); } root->endNode(); if(version > 1) { root->startNode(OTBM_WAYPOINTS); for(const auto& it : m_waypoints) { root->startNode(OTBM_WAYPOINT); root->addString(it.second); Position pos = it.first; root->addPos(pos.x, pos.y, pos.z); root->endNode(); } root->endNode(); } } root->endNode(); // OTBM_MAP_DATA } root->endNode(); fin->flush(); fin->close(); } catch(std::exception& e) { g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); } }
void SpriteManager::saveSpr(std::string fileName) { if(!m_loaded) stdext::throw_exception("failed to save, spr is not loaded"); try { FileStreamPtr fin = g_resources.createFile(fileName); if(!fin) stdext::throw_exception(stdext::format("failed to open file '%s' for write", fileName)); fin->cache(); fin->addU32(m_signature); if(g_game.getFeature(Otc::GameSpritesU32)) fin->addU32(m_spritesCount); else fin->addU16(m_spritesCount); uint32 offset = fin->tell(); uint32 spriteAddress = offset + 4 * m_spritesCount; for(int i = 1; i <= m_spritesCount; i++) fin->addU32(0); for(int i = 1; i <= m_spritesCount; i++) { m_spritesFile->seek((i - 1) * 4 + m_spritesOffset); uint32 fromAdress = m_spritesFile->getU32(); if(fromAdress != 0) { fin->seek(offset + (i - 1) * 4); fin->addU32(spriteAddress); fin->seek(spriteAddress); m_spritesFile->seek(fromAdress); fin->addU8(m_spritesFile->getU8()); fin->addU8(m_spritesFile->getU8()); fin->addU8(m_spritesFile->getU8()); uint16 dataSize = m_spritesFile->getU16(); fin->addU16(dataSize); char spriteData[SPRITE_DATA_SIZE]; m_spritesFile->read(spriteData, dataSize); fin->write(spriteData, dataSize); spriteAddress = fin->tell(); } //TODO: Check for overwritten sprites. } fin->flush(); fin->close(); } catch(std::exception& e) { g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what())); } }
void ThingType::unserialize(uint16 clientId, ThingCategory category, const FileStreamPtr& fin) { m_null = false; m_id = clientId; m_category = category; bool done = false; for(int i = 0 ; i < ThingLastAttr;++i) { int attr = fin->getU8(); if(attr == ThingLastAttr) { done = true; break; } if(g_game.getFeature(Otc::GameChargeableItems)) { if(attr == ThingAttrWritable) { m_attribs.set(ThingAttrChargeable, true); continue; } else if(attr > ThingAttrWritable) attr -= 1; } if(g_game.getProtocolVersion() >= 1010) { /* In 10.10 all attributes from 16 and up were * incremented by 1 to make space for 16 as * "No Movement Animation" flag. */ if(attr == 16) attr = ThingAttrNoMoveAnimation; else if(attr > 16) attr -= 1; } switch(attr) { case ThingAttrDisplacement: { m_displacement.x = fin->getU16(); m_displacement.y = fin->getU16(); m_attribs.set(attr, true); break; } case ThingAttrLight: { Light light; light.intensity = fin->getU16(); light.color = fin->getU16(); m_attribs.set(attr, light); break; } case ThingAttrMarket: { MarketData market; market.category = fin->getU16(); market.tradeAs = fin->getU16(); market.showAs = fin->getU16(); market.name = fin->getString(); market.restrictVocation = fin->getU16(); market.requiredLevel = fin->getU16(); m_attribs.set(attr, market); break; } case ThingAttrElevation: { m_elevation = fin->getU16(); m_attribs.set(attr, m_elevation); break; } case ThingAttrGround: case ThingAttrWritable: case ThingAttrWritableOnce: case ThingAttrMinimapColor: case ThingAttrCloth: case ThingAttrLensHelp: m_attribs.set(attr, fin->getU16()); break; default: m_attribs.set(attr, true); break; }; } if(!done) stdext::throw_exception("corrupt data"); uint8 width = fin->getU8(); uint8 height = fin->getU8(); m_size = Size(width, height); m_exactSize = (width > 1 || height > 1) ? std::min((int)fin->getU8(), std::max(width * 32, height * 32)) : 32; m_layers = fin->getU8(); m_numPatternX = fin->getU8(); m_numPatternY = fin->getU8(); m_numPatternZ = fin->getU8(); m_animationPhases = fin->getU8(); int totalSprites = m_size.area() * m_layers * m_numPatternX * m_numPatternY * m_numPatternZ * m_animationPhases; // if(totalSprites == 0) // stdext::throw_exception("a thing type has no sprites"); if(totalSprites > 4096) stdext::throw_exception("a thing type has more than 4096 sprites"); m_spritesIndex.resize(totalSprites); for(int i = 0; i < totalSprites; i++) m_spritesIndex[i] = g_game.getFeature(Otc::GameSpritesU32) ? fin->getU32() : fin->getU16(); m_textures.resize(m_animationPhases); m_texturesFramesRects.resize(m_animationPhases); m_texturesFramesOriginRects.resize(m_animationPhases); m_texturesFramesOffsets.resize(m_animationPhases); }
BinaryTree::BinaryTree(const FileStreamPtr& fin) : m_fin(fin), m_pos(0xFFFFFFFF) { m_startPos = fin->tell(); }
void ThingType::unserialize(uint16 clientId, ThingCategory category, const FileStreamPtr& fin) { m_null = false; m_id = clientId; m_category = category; int count = 0, attr = -1; bool done = false; for(int i = 0 ; i < ThingLastAttr;++i) { count++; attr = fin->getU8(); if(attr == ThingLastAttr) { done = true; break; } if(g_game.getClientVersion() >= 1000) { /* In 10.10+ all attributes from 16 and up were * incremented by 1 to make space for 16 as * "No Movement Animation" flag. */ if(attr == 16) attr = ThingAttrNoMoveAnimation; else if(attr > 16) attr -= 1; } else if(g_game.getClientVersion() >= 860) { /* Default attribute values follow * the format of 8.6-9.86. * Therefore no changes here. */ } else if(g_game.getClientVersion() >= 780) { /* In 7.80-8.54 all attributes from 8 and higher were * incremented by 1 to make space for 8 as * "Item Charges" flag. */ if(attr == 8) { m_attribs.set(ThingAttrChargeable, true); continue; } else if(attr > 8) attr -= 1; } else if(g_game.getClientVersion() >= 755) { /* In 7.55-7.72 attributes 23 is "Floor Change". */ if(attr == 23) attr = ThingAttrFloorChange; } else if(g_game.getClientVersion() >= 740) { /* In 7.4-7.5 attribute "Ground Border" did not exist * attributes 1-15 have to be adjusted. * Several other changes in the format. */ if(attr > 0 && attr <= 15) attr += 1; else if(attr == 16) attr = ThingAttrLight; else if(attr == 17) attr = ThingAttrFloorChange; else if(attr == 18) attr = ThingAttrFullGround; else if(attr == 19) attr = ThingAttrElevation; else if(attr == 20) attr = ThingAttrDisplacement; else if(attr == 22) attr = ThingAttrMinimapColor; else if(attr == 23) attr = ThingAttrRotateable; else if(attr == 24) attr = ThingAttrLyingCorpse; else if(attr == 25) attr = ThingAttrHangable; else if(attr == 26) attr = ThingAttrHookSouth; else if(attr == 27) attr = ThingAttrHookEast; else if(attr == 28) attr = ThingAttrAnimateAlways; /* "Multi Use" and "Force Use" are swapped */ if(attr == ThingAttrMultiUse) attr = ThingAttrForceUse; else if(attr == ThingAttrForceUse) attr = ThingAttrMultiUse; } switch(attr) { case ThingAttrDisplacement: { if(g_game.getClientVersion() >= 755) { m_displacement.x = fin->getU16(); m_displacement.y = fin->getU16(); } else { m_displacement.x = 8; m_displacement.y = 8; } m_attribs.set(attr, true); break; } case ThingAttrLight: { Light light; light.intensity = fin->getU16(); light.color = fin->getU16(); m_attribs.set(attr, light); break; } case ThingAttrMarket: { MarketData market; market.category = fin->getU16(); market.tradeAs = fin->getU16(); market.showAs = fin->getU16(); market.name = fin->getString(); market.restrictVocation = fin->getU16(); market.requiredLevel = fin->getU16(); m_attribs.set(attr, market); break; } case ThingAttrElevation: { m_elevation = fin->getU16(); m_attribs.set(attr, m_elevation); break; } case ThingAttrUsable: case ThingAttrGround: case ThingAttrWritable: case ThingAttrWritableOnce: case ThingAttrMinimapColor: case ThingAttrCloth: case ThingAttrLensHelp: m_attribs.set(attr, fin->getU16()); break; default: m_attribs.set(attr, true); break; }; } if(!done) stdext::throw_exception(stdext::format("corrupt data (id: %d, category: %d, count: %d, lastAttr: %d)", m_id, m_category, count, attr)); bool hasFrameGroups = (category == ThingCategoryCreature && g_game.getFeature(Otc::GameIdleAnimations)); uint8 groupCount = hasFrameGroups ? fin->getU8() : 1; for(int i = 0; i < groupCount; ++i) { uint8 frameGroupType = FrameGroupDefault; if(hasFrameGroups) frameGroupType = fin->getU8(); uint8 width = fin->getU8(); uint8 height = fin->getU8(); m_size = Size(width, height); if(width > 1 || height > 1) { m_realSize = fin->getU8(); m_exactSize = std::min<int>(m_realSize, std::max<int>(width * 32, height * 32)); } else m_exactSize = 32; m_layers = fin->getU8(); m_numPatternX = fin->getU8(); m_numPatternY = fin->getU8(); if(g_game.getClientVersion() >= 755) m_numPatternZ = fin->getU8(); else m_numPatternZ = 1; m_animationPhases = fin->getU8(); if(m_animationPhases > 1 && g_game.getFeature(Otc::GameEnhancedAnimations)) { m_animator = AnimatorPtr(new Animator); m_animator->unserialize(m_animationPhases, fin); } int totalSprites = m_size.area() * m_layers * m_numPatternX * m_numPatternY * m_numPatternZ * m_animationPhases; if(totalSprites > 4096) stdext::throw_exception("a thing type has more than 4096 sprites"); m_spritesIndex.resize(totalSprites); for(int i = 0; i < totalSprites; i++) m_spritesIndex[i] = g_game.getFeature(Otc::GameSpritesU32) ? fin->getU32() : fin->getU16(); } m_textures.resize(m_animationPhases); m_texturesFramesRects.resize(m_animationPhases); m_texturesFramesOriginRects.resize(m_animationPhases); m_texturesFramesOffsets.resize(m_animationPhases); }
void ThingType::serialize(const FileStreamPtr& fin) { for(int i = 0; i < ThingLastAttr; ++i) { if(!hasAttr((ThingAttr)i)) continue; int attr = i; if(g_game.getClientVersion() >= 780) { if(attr == ThingAttrChargeable) attr = ThingAttrWritable; else if(attr >= ThingAttrWritable) attr += 1; } else if(g_game.getClientVersion() >= 1000) { if(attr == ThingAttrNoMoveAnimation) attr = 16; else if(attr >= ThingAttrPickupable) attr += 1; } fin->addU8(attr); switch(attr) { case ThingAttrDisplacement: { fin->addU16(m_displacement.x); fin->addU16(m_displacement.y); break; } case ThingAttrLight: { Light light = m_attribs.get<Light>(attr); fin->addU16(light.intensity); fin->addU16(light.color); break; } case ThingAttrMarket: { MarketData market = m_attribs.get<MarketData>(attr); fin->addU16(market.category); fin->addU16(market.tradeAs); fin->addU16(market.showAs); fin->addString(market.name); fin->addU16(market.restrictVocation); fin->addU16(market.requiredLevel); break; } case ThingAttrUsable: case ThingAttrElevation: case ThingAttrGround: case ThingAttrWritable: case ThingAttrWritableOnce: case ThingAttrMinimapColor: case ThingAttrCloth: case ThingAttrLensHelp: fin->addU16(m_attribs.get<uint16>(attr)); break; default: break; }; } fin->addU8(ThingLastAttr); fin->addU8(m_size.width()); fin->addU8(m_size.height()); if(m_size.width() > 1 || m_size.height() > 1) fin->addU8(m_realSize); fin->addU8(m_layers); fin->addU8(m_numPatternX); fin->addU8(m_numPatternY); fin->addU8(m_numPatternZ); fin->addU8(m_animationPhases); if(g_game.getFeature(Otc::GameEnhancedAnimations)) { if(m_animationPhases > 1 && m_animator != nullptr) { m_animator->serialize(fin); } } for(uint i = 0; i < m_spritesIndex.size(); i++) { if(g_game.getFeature(Otc::GameSpritesU32)) fin->addU32(m_spritesIndex[i]); else fin->addU16(m_spritesIndex[i]); } }