mirror of
https://github.com/opentibiabr/remeres-map-editor
synced 2026-08-15 18:26:04 -04:00
feat: migrate Remeres to 12.x assets system and modernized item handling • Migrated old Remeres .dat/.spr system to new 12.x client /assets/ appearance & sprite format • Removed OTB dependency and outdated client version structures • Integrated Protobuf-based sprite reader (credit: @nekiro) • Major refactor on graphics.cpp structure and brush icon handling • Updated brushes and tags system • Fixed top order drawing bugs and missing brushes icons • Fixed outfit color paint and correct offset handling for non-32x32 sprites • Re-enabled map saving and loading • Improved creature sprite rendering and brush previews • Added new item properties window with toggle for legacy/new layout Co-authored-by: Marcos <66353315+marcosvf132@users.noreply.github.com> Co-authored-by: Pedro Henrique Alves Cruz <phac@cin.ufpe.br>
402 lines
10 KiB
C++
402 lines
10 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 "live_socket.h"
|
|
|
|
#include "client_assets.h"
|
|
#include "map_region.h"
|
|
#include "iomap_otbm.h"
|
|
#include "live_tab.h"
|
|
#include "editor.h"
|
|
|
|
LiveSocket::LiveSocket() :
|
|
cursors(), mapReader(nullptr, 0), mapWriter(),
|
|
mapVersion(MapVersion()), log(nullptr),
|
|
name("User"), password("") {
|
|
//
|
|
}
|
|
|
|
LiveSocket::~LiveSocket() {
|
|
//
|
|
}
|
|
|
|
wxString LiveSocket::getName() const {
|
|
return name;
|
|
}
|
|
|
|
bool LiveSocket::setName(const wxString &newName) {
|
|
if (newName.empty()) {
|
|
setLastError("Must provide a name.");
|
|
return false;
|
|
} else if (newName.length() > 32) {
|
|
setLastError("Name is too long.");
|
|
return false;
|
|
}
|
|
name = newName;
|
|
return true;
|
|
}
|
|
|
|
wxString LiveSocket::getPassword() const {
|
|
return password;
|
|
}
|
|
|
|
bool LiveSocket::setPassword(const wxString &newPassword) {
|
|
if (newPassword.length() > 32) {
|
|
setLastError("Password is too long.");
|
|
return false;
|
|
}
|
|
password = newPassword;
|
|
return true;
|
|
}
|
|
|
|
wxString LiveSocket::getLastError() const {
|
|
return lastError;
|
|
}
|
|
|
|
void LiveSocket::setLastError(const wxString &error) {
|
|
lastError = error;
|
|
}
|
|
|
|
std::string LiveSocket::getHostName() const {
|
|
return "?";
|
|
}
|
|
|
|
std::vector<LiveCursor> LiveSocket::getCursorList() const {
|
|
std::vector<LiveCursor> cursorList;
|
|
for (auto &cursorEntry : cursors) {
|
|
cursorList.push_back(cursorEntry.second);
|
|
}
|
|
return cursorList;
|
|
}
|
|
|
|
void LiveSocket::logMessage(const wxString &message) {
|
|
wxTheApp->CallAfter([this, message]() {
|
|
if (log) {
|
|
log->Message(message);
|
|
}
|
|
});
|
|
}
|
|
|
|
void LiveSocket::receiveNode(NetworkMessage &message, Editor &editor, Action* action, int32_t ndx, int32_t ndy, bool underground) {
|
|
QTreeNode* node = editor.getMap().getLeaf(ndx * 4, ndy * 4);
|
|
if (!node) {
|
|
log->Message("Warning: Received update for unknown tile (" + std::to_string(ndx * 4) + "/" + std::to_string(ndy * 4) + "/" + (underground ? "true" : "false") + ")");
|
|
return;
|
|
}
|
|
|
|
node->setRequested(underground, false);
|
|
node->setVisible(underground, true);
|
|
|
|
uint16_t floorBits = message.read<uint16_t>();
|
|
if (floorBits == 0) {
|
|
return;
|
|
}
|
|
|
|
for (uint_fast8_t z = 0; z < 16; ++z) {
|
|
if (testFlags(floorBits, static_cast<uint64_t>(1) << z)) {
|
|
receiveFloor(message, editor, action, ndx, ndy, z, node, node->getFloor(z));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LiveSocket::sendNode(uint32_t clientId, QTreeNode* node, int32_t ndx, int32_t ndy, uint32_t floorMask) {
|
|
bool underground;
|
|
if (floorMask & 0xFF00) {
|
|
if (floorMask & 0x00FF) {
|
|
underground = false;
|
|
} else {
|
|
underground = true;
|
|
}
|
|
} else {
|
|
underground = false;
|
|
}
|
|
|
|
node->setVisible(clientId, underground, true);
|
|
|
|
// Send message
|
|
NetworkMessage message;
|
|
message.write<uint8_t>(PACKET_NODE);
|
|
message.write<uint32_t>((ndx << 18) | (ndy << 4) | ((floorMask & 0xFF00) ? 1 : 0));
|
|
|
|
if (!node) {
|
|
message.write<uint8_t>(0x00);
|
|
} else {
|
|
Floor** floors = node->getFloors();
|
|
|
|
uint16_t sendMask = 0;
|
|
for (uint32_t z = 0; z < 16; ++z) {
|
|
uint32_t bit = 1 << z;
|
|
if (floors[z] && testFlags(floorMask, bit)) {
|
|
sendMask |= bit;
|
|
}
|
|
}
|
|
|
|
message.write<uint16_t>(sendMask);
|
|
for (uint32_t z = 0; z < 16; ++z) {
|
|
if (testFlags(sendMask, static_cast<uint64_t>(1) << z)) {
|
|
sendFloor(message, floors[z]);
|
|
}
|
|
}
|
|
}
|
|
|
|
send(message);
|
|
}
|
|
|
|
void LiveSocket::receiveFloor(NetworkMessage &message, Editor &editor, Action* action, int32_t ndx, int32_t ndy, int32_t z, QTreeNode* node, Floor* floor) {
|
|
Map &map = editor.getMap();
|
|
|
|
uint16_t tileBits = message.read<uint16_t>();
|
|
if (tileBits == 0) {
|
|
for (uint_fast8_t x = 0; x < 4; ++x) {
|
|
for (uint_fast8_t y = 0; y < 4; ++y) {
|
|
action->addChange(new Change(map.allocator(node->createTile(ndx * 4 + x, ndy * 4 + y, z))));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// -1 on address since we skip the first START_NODE when sending
|
|
const std::string &data = message.read<std::string>();
|
|
mapReader.assign(reinterpret_cast<const uint8_t*>(data.c_str() - 1), data.size());
|
|
|
|
BinaryNode* rootNode = mapReader.getRootNode();
|
|
BinaryNode* tileNode = rootNode->getChild();
|
|
|
|
Position position(0, 0, z);
|
|
for (uint_fast8_t x = 0; x < 4; ++x) {
|
|
for (uint_fast8_t y = 0; y < 4; ++y) {
|
|
position.x = (ndx * 4) + x;
|
|
position.y = (ndy * 4) + y;
|
|
|
|
if (testFlags(tileBits, static_cast<uint64_t>(1) << ((x * 4) + y))) {
|
|
receiveTile(tileNode, editor, action, &position);
|
|
tileNode->advance();
|
|
} else {
|
|
action->addChange(new Change(map.allocator(node->createTile(position.x, position.y, z))));
|
|
}
|
|
}
|
|
}
|
|
mapReader.close();
|
|
}
|
|
|
|
void LiveSocket::sendFloor(NetworkMessage &message, Floor* floor) {
|
|
uint16_t tileBits = 0;
|
|
for (uint_fast8_t x = 0; x < 4; ++x) {
|
|
for (uint_fast8_t y = 0; y < 4; ++y) {
|
|
uint_fast8_t index = (x * 4) + y;
|
|
|
|
Tile* tile = floor->locs[index].get();
|
|
if (tile && tile->size() > 0) {
|
|
tileBits |= (1 << index);
|
|
}
|
|
}
|
|
}
|
|
|
|
message.write<uint16_t>(tileBits);
|
|
if (tileBits == 0) {
|
|
return;
|
|
}
|
|
|
|
mapWriter.reset();
|
|
for (uint_fast8_t x = 0; x < 4; ++x) {
|
|
for (uint_fast8_t y = 0; y < 4; ++y) {
|
|
uint_fast8_t index = (x * 4) + y;
|
|
if (testFlags(tileBits, static_cast<uint64_t>(1) << index)) {
|
|
sendTile(mapWriter, floor->locs[index].get(), nullptr);
|
|
}
|
|
}
|
|
}
|
|
mapWriter.endNode();
|
|
|
|
std::string stream(
|
|
reinterpret_cast<char*>(mapWriter.getMemory()),
|
|
mapWriter.getSize()
|
|
);
|
|
message.write<std::string>(stream);
|
|
}
|
|
|
|
void LiveSocket::receiveTile(BinaryNode* node, Editor &editor, Action* action, const Position* position) {
|
|
ASSERT(node != nullptr);
|
|
|
|
Tile* tile = readTile(node, editor, position);
|
|
if (tile) {
|
|
action->addChange(newd Change(tile));
|
|
}
|
|
}
|
|
|
|
void LiveSocket::sendTile(MemoryNodeFileWriteHandle &writer, Tile* tile, const Position* position) {
|
|
writer.addNode(tile->isHouseTile() ? OTBM_HOUSETILE : OTBM_TILE);
|
|
if (position) {
|
|
writer.addU16(position->x);
|
|
writer.addU16(position->y);
|
|
writer.addU8(position->z);
|
|
}
|
|
|
|
if (tile->isHouseTile()) {
|
|
writer.addU32(tile->getHouseID());
|
|
}
|
|
|
|
if (tile->getMapFlags()) {
|
|
writer.addByte(OTBM_ATTR_TILE_FLAGS);
|
|
writer.addU32(tile->getMapFlags());
|
|
}
|
|
|
|
Item* ground = tile->ground;
|
|
if (ground) {
|
|
if (ground->isComplex()) {
|
|
ground->serializeItemNode_OTBM(mapVersion, writer);
|
|
} else {
|
|
writer.addByte(OTBM_ATTR_ITEM);
|
|
ground->serializeItemCompact_OTBM(mapVersion, writer);
|
|
}
|
|
}
|
|
|
|
for (Item* item : tile->items) {
|
|
item->serializeItemNode_OTBM(mapVersion, writer);
|
|
}
|
|
|
|
writer.endNode();
|
|
}
|
|
|
|
Tile* LiveSocket::readTile(BinaryNode* node, Editor &editor, const Position* position) {
|
|
ASSERT(node != nullptr);
|
|
|
|
Map &map = editor.getMap();
|
|
|
|
uint8_t tileType;
|
|
node->getByte(tileType);
|
|
|
|
if (tileType != OTBM_TILE && tileType != OTBM_HOUSETILE) {
|
|
return nullptr;
|
|
}
|
|
|
|
Position pos;
|
|
if (position) {
|
|
pos = *position;
|
|
} else {
|
|
uint16_t x = 0; // Inicializar com valor padrão
|
|
uint16_t y = 0; // Inicializar com valor padrão
|
|
uint8_t z = 0; // Inicializar com valor padrão
|
|
|
|
if (node->getU16(x) && node->getU16(y) && node->getU8(z)) {
|
|
pos.x = x;
|
|
pos.y = y;
|
|
pos.z = z;
|
|
} else {
|
|
// Tratar erro: os métodos getU16 ou getU8 falharam
|
|
}
|
|
}
|
|
|
|
Tile* tile = map.allocator(
|
|
map.createTileL(pos)
|
|
);
|
|
|
|
if (tileType == OTBM_HOUSETILE) {
|
|
uint32_t houseId;
|
|
if (!node->getU32(houseId)) {
|
|
// warning("House tile without house data, discarding tile");
|
|
delete tile;
|
|
return nullptr;
|
|
}
|
|
|
|
if (houseId) {
|
|
House* house = map.houses.getHouse(houseId);
|
|
if (house) {
|
|
tile->setHouse(house);
|
|
}
|
|
} else {
|
|
// warning("Invalid house id from tile %d:%d:%d", pos.x, pos.y, pos.z);
|
|
}
|
|
}
|
|
|
|
uint8_t attribute;
|
|
while (node->getU8(attribute)) {
|
|
switch (attribute) {
|
|
case OTBM_ATTR_TILE_FLAGS: {
|
|
uint32_t flags = 0;
|
|
if (!node->getU32(flags)) {
|
|
// warning("Invalid tile flags of tile on %d:%d:%d", pos.x, pos.y, pos.z);
|
|
}
|
|
tile->setMapFlags(flags);
|
|
break;
|
|
}
|
|
case OTBM_ATTR_ITEM: {
|
|
Item* item = Item::Create_OTBM(mapVersion, node);
|
|
if (!item) {
|
|
// warning("Invalid item at tile %d:%d:%d", pos.x, pos.y, pos.z);
|
|
}
|
|
tile->addItem(item);
|
|
break;
|
|
}
|
|
default:
|
|
// warning("Unknown tile attribute at %d:%d:%d", pos.x, pos.y, pos.z);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// for(BinaryNode* itemNode = node->getChild(); itemNode; itemNode->advance()) {
|
|
BinaryNode* itemNode = node->getChild();
|
|
if (itemNode) {
|
|
do {
|
|
uint8_t itemType;
|
|
if (!itemNode->getByte(itemType)) {
|
|
// warning("Unknown item type %d:%d:%d", pos.x, pos.y, pos.z);
|
|
delete tile;
|
|
return nullptr;
|
|
}
|
|
|
|
if (itemType == OTBM_ITEM) {
|
|
Item* item = Item::Create_OTBM(mapVersion, itemNode);
|
|
if (item) {
|
|
if (!item->unserializeItemNode_OTBM(mapVersion, itemNode)) {
|
|
// warning("Couldn't unserialize item attributes at %d:%d:%d", pos.x, pos.y, pos.z);
|
|
}
|
|
tile->addItem(item);
|
|
}
|
|
} else {
|
|
// warning("Unknown type of tile child node");
|
|
}
|
|
//}
|
|
} while (itemNode->advance());
|
|
}
|
|
|
|
return tile;
|
|
}
|
|
|
|
LiveCursor LiveSocket::readCursor(NetworkMessage &message) {
|
|
LiveCursor cursor;
|
|
cursor.id = message.read<uint32_t>();
|
|
|
|
uint8_t r = message.read<uint8_t>();
|
|
uint8_t g = message.read<uint8_t>();
|
|
uint8_t b = message.read<uint8_t>();
|
|
uint8_t a = message.read<uint8_t>();
|
|
cursor.color = wxColor(r, g, b, a);
|
|
|
|
cursor.pos = message.read<Position>();
|
|
return cursor;
|
|
}
|
|
|
|
void LiveSocket::writeCursor(NetworkMessage &message, const LiveCursor &cursor) {
|
|
message.write<uint32_t>(cursor.id);
|
|
message.write<uint8_t>(cursor.color.Red());
|
|
message.write<uint8_t>(cursor.color.Green());
|
|
message.write<uint8_t>(cursor.color.Blue());
|
|
message.write<uint8_t>(cursor.color.Alpha());
|
|
message.write<Position>(cursor.pos);
|
|
}
|