mirror of
https://github.com/opentibiabr/remeres-map-editor
synced 2026-08-15 18:26:04 -04:00
Add editor support for exporting Cyclopedia and CipSoft protobuf assets from the currently loaded map. Main changes: - Add File > Client Assets actions for Static House Data export, Cyclopedia minimap and satellite export, and restoring timestamped client asset backups. - Keep generic minimap and tileset exports under File > Export. - Export staticdata, staticmapdata, and mapdata protobuf files with content-hashed filenames. - Update catalog-content.json so generated assets are picked up by the client. - Merge compatible CipSoft templates when they match the loaded map. - Fall back to generated data for custom maps instead of failing the export. - Support filtered house exports. - Report warnings for missing or failed static map entries. - Emit aligned MINIMAP and sprite-based SATELLITE assets for the documented scales, including the 1/16 layer. - Keep Cyclopedia export progress in the status bar instead of using a blocking modal progress dialog. Performance: - Precompute floor and chunk plans for global map exports. - Write BMP output directly. - Reuse minimap renders for matching satellite assets. - Parallelize asset hash and LZMA encoding through a bounded worker queue. - Remove modal progress overhead from the export path. - Revert the tile-grid satellite optimization after profiling showed it shifted cost without improving total export time. Documentation: - Document the export contract in docs/static-data.md. - Add docs/cyclopedia-export-roadmap.md with deferred optimization candidates and follow-up notes. Scope: - Focus this change on the Cyclopedia/staticdata protobuf export path and the export-specific performance work needed for practical global map exports. - Move unrelated runtime, performance, and UI experiments out of this branch. Validation: - Ran static inspection and git diff checks during the export changes. - Reviewed Visual Studio CPU profiles after each global map export performance pass. - Verified hash and LZMA work moved into bounded async workers. - Verified the restore action is labeled as restore and grouped under File > Client Assets, not File > Export. This adds the editor-side pipeline needed to generate client Cyclopedia assets from a loaded map while keeping the export flow recoverable, documented, and practical for large maps.
348 lines
8.9 KiB
C++
348 lines
8.9 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 "tile.h"
|
|
#include "basemap.h"
|
|
|
|
#include <memory>
|
|
|
|
BaseMap::BaseMap() :
|
|
allocator(),
|
|
tilecount(0),
|
|
root(*this) {
|
|
////
|
|
}
|
|
|
|
BaseMap::~BaseMap() {
|
|
////
|
|
}
|
|
|
|
void BaseMap::clear(bool del) {
|
|
PositionVector pos_vec;
|
|
for (MapIterator map_iter = begin(); map_iter != end(); ++map_iter) {
|
|
Tile* t = (*map_iter)->get();
|
|
pos_vec.push_back(t->getPosition());
|
|
}
|
|
for (PositionVector::iterator pos_iter = pos_vec.begin(); pos_iter != pos_vec.end(); ++pos_iter) {
|
|
setTile(*pos_iter, nullptr, del);
|
|
}
|
|
}
|
|
|
|
void BaseMap::clearVisible(uint32_t mask) {
|
|
root.clearVisible(mask);
|
|
}
|
|
|
|
Tile* BaseMap::createTile(int x, int y, int z) {
|
|
ASSERT(z < rme::MapLayers);
|
|
QTreeNode* leaf = root.getLeafForce(x, y);
|
|
TileLocation* loc = leaf->createTile(x, y, z);
|
|
if (loc->get()) {
|
|
return loc->get();
|
|
}
|
|
Tile* t = allocator(loc);
|
|
leaf->setTile(x, y, z, t);
|
|
return t;
|
|
}
|
|
|
|
TileLocation* BaseMap::getTileL(int x, int y, int z) {
|
|
ASSERT(z < rme::MapLayers);
|
|
QTreeNode* leaf = root.getLeaf(x, y);
|
|
if (leaf) {
|
|
Floor* floor = leaf->getFloor(z);
|
|
if (floor) {
|
|
return &floor->locs[(x & 3) * 4 + (y & 3)];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const TileLocation* BaseMap::getTileL(int x, int y, int z) const {
|
|
// Don't create static const maps!
|
|
BaseMap* self = const_cast<BaseMap*>(this);
|
|
return self->getTileL(x, y, z);
|
|
}
|
|
|
|
TileLocation* BaseMap::getTileL(const Position &pos) {
|
|
return getTileL(pos.x, pos.y, pos.z);
|
|
}
|
|
|
|
const TileLocation* BaseMap::getTileL(const Position &pos) const {
|
|
return getTileL(pos.x, pos.y, pos.z);
|
|
}
|
|
|
|
TileLocation* BaseMap::createTileL(int x, int y, int z) {
|
|
ASSERT(z < rme::MapLayers);
|
|
|
|
QTreeNode* leaf = root.getLeafForce(x, y);
|
|
Floor* floor = leaf->createFloor(x, y, z);
|
|
uint32_t offsetX = x & 3;
|
|
uint32_t offsetY = y & 3;
|
|
|
|
return &floor->locs[offsetX * 4 + offsetY];
|
|
}
|
|
|
|
TileLocation* BaseMap::createTileL(const Position &pos) {
|
|
return createTileL(pos.x, pos.y, pos.z);
|
|
}
|
|
|
|
void BaseMap::setTile(TileLocation* location, Tile* new_tile, bool remove) {
|
|
ASSERT(location);
|
|
ASSERT(!new_tile || new_tile->getX() == location->getX());
|
|
ASSERT(!new_tile || new_tile->getY() == location->getY());
|
|
ASSERT(!new_tile || new_tile->getZ() == location->getZ());
|
|
|
|
Tile* old_tile = location->tile;
|
|
location->tile = new_tile;
|
|
|
|
if ((remove && old_tile) || new_tile) {
|
|
updateUniqueIds(remove ? old_tile : nullptr, new_tile);
|
|
}
|
|
|
|
if (new_tile && !old_tile) {
|
|
++tilecount;
|
|
} else if (old_tile && !new_tile) {
|
|
--tilecount;
|
|
}
|
|
|
|
if (remove) {
|
|
std::unique_ptr<Tile> { old_tile };
|
|
}
|
|
}
|
|
|
|
void BaseMap::setTile(int x, int y, int z, Tile* new_tile, bool remove) {
|
|
ASSERT(z >= 0 && z < rme::MapLayers);
|
|
ASSERT(!new_tile || new_tile->getX() == x);
|
|
ASSERT(!new_tile || new_tile->getY() == y);
|
|
ASSERT(!new_tile || new_tile->getZ() == z);
|
|
|
|
QTreeNode* leaf = root.getLeafForce(x, y);
|
|
Tile* old_tile = leaf->setTile(x, y, z, new_tile);
|
|
|
|
if ((remove && old_tile) || new_tile) {
|
|
updateUniqueIds(remove ? old_tile : nullptr, new_tile);
|
|
}
|
|
|
|
if (remove) {
|
|
std::unique_ptr<Tile> { old_tile };
|
|
}
|
|
}
|
|
|
|
void BaseMap::setTile(const Position &position, Tile* new_tile, bool remove) {
|
|
setTile(position.x, position.y, position.z, new_tile, remove);
|
|
}
|
|
|
|
void BaseMap::setTile(Tile* new_tile, bool remove) {
|
|
ASSERT(new_tile);
|
|
|
|
const Position &position = new_tile->getPosition();
|
|
setTile(position.x, position.y, position.z, new_tile, remove);
|
|
}
|
|
|
|
Tile* BaseMap::swapTile(int x, int y, int z, Tile* new_tile) {
|
|
ASSERT(z < rme::MapLayers);
|
|
ASSERT(!new_tile || new_tile->getX() == x);
|
|
ASSERT(!new_tile || new_tile->getY() == y);
|
|
ASSERT(!new_tile || new_tile->getZ() == z);
|
|
|
|
QTreeNode* leaf = root.getLeafForce(x, y);
|
|
Tile* old_tile = leaf->setTile(x, y, z, new_tile);
|
|
|
|
if (old_tile || new_tile) {
|
|
updateUniqueIds(old_tile, new_tile);
|
|
}
|
|
|
|
return old_tile;
|
|
}
|
|
|
|
Tile* BaseMap::swapTile(const Position &position, Tile* new_tile) {
|
|
return swapTile(position.x, position.y, position.z, new_tile);
|
|
}
|
|
|
|
// Iterators
|
|
|
|
MapIterator::MapIterator(BaseMap* _map) :
|
|
local_i(0),
|
|
local_z(0),
|
|
current_tile(nullptr),
|
|
map(_map) {
|
|
////
|
|
}
|
|
|
|
MapIterator::~MapIterator() {
|
|
////
|
|
}
|
|
|
|
MapIterator::MapIterator(const MapIterator &other) {
|
|
for (std::vector<MapIterator::NodeIndex>::const_iterator it = other.nodestack.begin(); it != other.nodestack.end(); it++) {
|
|
nodestack.push_back(MapIterator::NodeIndex(*it));
|
|
}
|
|
local_i = other.local_i;
|
|
local_z = other.local_z;
|
|
map = other.map;
|
|
current_tile = other.current_tile;
|
|
}
|
|
|
|
MapIterator BaseMap::begin() {
|
|
MapIterator it(this);
|
|
it.nodestack.push_back(MapIterator::NodeIndex(&root));
|
|
|
|
while (true) {
|
|
MapIterator::NodeIndex ¤t = it.nodestack.back();
|
|
QTreeNode* node = current.node;
|
|
int &index = current.index;
|
|
// printf("Contemplating %p of %p (stack size %d)\n", node, this, it.nodestack.size());
|
|
|
|
bool unwind = false;
|
|
for (; index < 16; ++index) {
|
|
// printf("\tChecking index %d of %p\n", index, node);
|
|
if (QTreeNode* child = node->child[index]) {
|
|
if (child->isLeaf) {
|
|
QTreeNode* leaf = child;
|
|
// printf("\t%p is leaf\n", child);
|
|
for (it.local_z = 0; it.local_z < rme::MapLayers; ++it.local_z) {
|
|
if (Floor* floor = leaf->array[it.local_z]) {
|
|
for (it.local_i = 0; it.local_i < 16; ++it.local_i) {
|
|
// printf("\tit(%d;%d;%d)\n", it.local_x, it.local_y, it.local_z);
|
|
TileLocation &t = floor->locs[it.local_i];
|
|
if (t.get()) {
|
|
// printf("return it\n");
|
|
it.current_tile = &t;
|
|
return it;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// printf("\tAdding %p\n", child);
|
|
++index;
|
|
it.nodestack.push_back(MapIterator::NodeIndex(child));
|
|
unwind = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (unwind) {
|
|
continue;
|
|
}
|
|
|
|
// printf("Discarding dead node %p\n", node);
|
|
it.nodestack.pop_back();
|
|
if (it.nodestack.empty()) {
|
|
break;
|
|
}
|
|
}
|
|
return end();
|
|
}
|
|
|
|
MapIterator BaseMap::end() {
|
|
MapIterator it(this);
|
|
it.local_i = -1;
|
|
it.local_z = -1;
|
|
return it;
|
|
}
|
|
|
|
TileLocation* MapIterator::operator*() {
|
|
return current_tile;
|
|
}
|
|
|
|
TileLocation* MapIterator::operator->() {
|
|
return current_tile;
|
|
}
|
|
|
|
MapIterator &MapIterator::operator++() {
|
|
// printf("MapIterator::operator++");
|
|
bool increased = false;
|
|
bool first = true;
|
|
while (true) {
|
|
MapIterator::NodeIndex ¤t = nodestack.back();
|
|
QTreeNode* node = current.node;
|
|
int &index = current.index;
|
|
// printf("Contemplating %p (stack size %d)\n", node, nodestack.size());
|
|
|
|
bool unwind = false;
|
|
for (; index < rme::MapLayers; ++index) {
|
|
// printf("\tChecking index %d of %p\n", index, node);
|
|
if (QTreeNode* child = node->child[index]) {
|
|
if (child->isLeaf) {
|
|
QTreeNode* leaf = child;
|
|
// printf("\t%p is leaf\n", child);
|
|
for (; local_z < rme::MapLayers; ++local_z) {
|
|
// printf("\t\tIterating over Z:%d of %p", local_z, child);
|
|
if (Floor* floor = leaf->array[local_z]) {
|
|
// printf("\n");
|
|
for (; local_i < rme::MapLayers; ++local_i) {
|
|
// printf("\t\tIterating over Y:%d of %p\n", local_y, child);
|
|
TileLocation &t = floor->locs[local_i];
|
|
if (t.get()) {
|
|
if (increased) {
|
|
// printf("Modified %p to %p\n", current_tile, t);
|
|
current_tile = &t;
|
|
return *this;
|
|
} else {
|
|
increased = true;
|
|
}
|
|
} else if (first) {
|
|
increased = true;
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
if (local_i > rme::MapMaxLayer) {
|
|
// printf("\t\tReset local_x\n");
|
|
local_i = 0;
|
|
}
|
|
} else {
|
|
// printf(":dead floor\n");
|
|
}
|
|
}
|
|
if (local_z == rme::MapLayers) {
|
|
// printf("\t\tReset local_z\n");
|
|
local_z = 0;
|
|
}
|
|
} else {
|
|
// printf("\tAdding %p\n", child);
|
|
++index;
|
|
nodestack.push_back(MapIterator::NodeIndex(child));
|
|
unwind = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (unwind) {
|
|
continue;
|
|
}
|
|
|
|
// printf("Discarding dead node %p\n", node);
|
|
nodestack.pop_back();
|
|
if (nodestack.size() == 0) {
|
|
// Set all values to "end"
|
|
// printf("END\n");
|
|
local_z = -1;
|
|
local_i = -1;
|
|
return *this;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MapIterator MapIterator::operator++(int) {
|
|
MapIterator i(*this);
|
|
++*this;
|
|
return i;
|
|
}
|