live_socket.cpp

main 435 lines · 11.1 KB Raw
Alex Alex Commit Map Editor 18/10/2026 23:40
1//////////////////////////////////////////////////////////////////////
2// This file is part of Remere's Map Editor
3//////////////////////////////////////////////////////////////////////
4// Remere's Map Editor is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Remere's Map Editor is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//////////////////////////////////////////////////////////////////////
17
18#include "main.h"
19#include "live_socket.h"
20#include "map_region.h"
21#include "iomap_otbm.h"
22#include "live_tab.h"
23#include "editor.h"
24
25LiveSocket::LiveSocket() :
26 cursors(), mapReader(nullptr, 0), mapWriter(),
27 mapVersion(MapVersion(MAP_OTBM_4, CLIENT_VERSION_NONE)), log(nullptr),
28 name("User"), password("") {
29 //
30}
31
32LiveSocket::~LiveSocket() {
33 //
34}
35
36wxString LiveSocket::getName() const {
37 return name;
38}
39
40bool LiveSocket::setName(const wxString& newName) {
41 if (newName.empty()) {
42 setLastError("Must provide a name.");
43 return false;
44 } else if (newName.length() > 32) {
45 setLastError("Name is too long.");
46 return false;
47 }
48 name = newName;
49 return true;
50}
51
52wxString LiveSocket::getPassword() const {
53 return password;
54}
55
56bool LiveSocket::setPassword(const wxString& newPassword) {
57 if (newPassword.length() > 32) {
58 setLastError("Password is too long.");
59 return false;
60 }
61 password = newPassword;
62 return true;
63}
64
65wxString LiveSocket::getLastError() const {
66 return lastError;
67}
68
69void LiveSocket::setLastError(const wxString& error) {
70 lastError = error;
71}
72
73std::string LiveSocket::getHostName() const {
74 return "?";
75}
76
77std::vector<LiveCursor> LiveSocket::getCursorList() const {
78 std::vector<LiveCursor> cursorList;
79 for (auto& cursorEntry : cursors) {
80 cursorList.push_back(cursorEntry.second);
81 }
82 return cursorList;
83}
84
85void LiveSocket::logMessage(const wxString& message) {
86 wxTheApp->CallAfter([this, message]() {
87 if (log) {
88 log->Message(message);
89 }
90 });
91}
92
93void LiveSocket::receiveNode(NetworkMessage& message, Editor& editor, Action* action, int32_t ndx, int32_t ndy, bool underground) {
94 QTreeNode* node = editor.map.getLeaf(ndx * 4, ndy * 4);
95 if (!node) {
96 log->Message("Warning: Received update for unknown tile (" + std::to_string(ndx * 4) + "/" + std::to_string(ndy * 4) + "/" + (underground ? "true" : "false") + ")");
97 return;
98 }
99
100 node->setRequested(underground, false);
101 node->setVisible(underground, true);
102
103 uint16_t floorBits = message.read<uint16_t>();
104 if (floorBits == 0) {
105 return;
106 }
107
108 for (uint_fast8_t z = 0; z < MAP_LAYERS; ++z) {
109 if (testFlags(floorBits, static_cast<uint64_t>(1) << z)) {
110 receiveFloor(message, editor, action, ndx, ndy, z, node, node->getFloor(z));
111 }
112 }
113}
114
115void LiveSocket::sendNode(uint32_t clientId, QTreeNode* node, int32_t ndx, int32_t ndy, uint32_t floorMask) {
116 // Safety check
117 if (!node) {
118 logMessage(wxString::Format("Warning: Attempted to send null node at %d,%d", ndx * 4, ndy * 4));
119 return;
120 }
121
122 bool underground;
123 if (floorMask & 0xFF00) {
124 if (floorMask & 0x00FF) {
125 underground = false;
126 } else {
127 underground = true;
128 }
129 } else {
130 underground = false;
131 }
132
133 // Mark the node as visible to this client
134 node->setVisible(clientId, underground, true);
135
136 try {
137 // Prepare the message
138 NetworkMessage message;
139 message.write<uint8_t>(PACKET_NODE);
140 message.write<uint32_t>((ndx << 18) | (ndy << 4) | ((floorMask & 0xFF00) ? 1 : 0));
141
142 // Check floors
143 Floor** floors = node->getFloors();
144 bool hasFloors = false;
145
146 uint16_t sendMask = 0;
147 for (uint32_t z = 0; z < MAP_LAYERS; ++z) {
148 uint32_t bit = 1 << z;
149 if (floors[z] && testFlags(floorMask, bit)) {
150 sendMask |= bit;
151 hasFloors = true;
152 }
153 }
154
155 // Add the floor mask to the message
156 message.write<uint16_t>(sendMask);
157
158 // If we have floors to send, add them to the message
159 if (hasFloors) {
160 for (uint32_t z = 0; z < MAP_LAYERS; ++z) {
161 if (testFlags(sendMask, static_cast<uint64_t>(1) << z)) {
162 sendFloor(message, floors[z]);
163 }
164 }
165 }
166
167 // Send the message
168 logMessage(wxString::Format("Sending node [%d,%d,%s] with floor mask 0x%04X",
169 ndx, ndy, underground ? "underground" : "surface", sendMask));
170 send(message);
171 } catch (std::exception& e) {
172 logMessage(wxString::Format("Error sending node [%d,%d]: %s", ndx, ndy, e.what()));
173 }
174}
175
176void LiveSocket::receiveFloor(NetworkMessage& message, Editor& editor, Action* action, int32_t ndx, int32_t ndy, int32_t z, QTreeNode* node, Floor* floor) {
177 Map& map = editor.map;
178
179 uint16_t tileBits = message.read<uint16_t>();
180 if (tileBits == 0) {
181 for (uint_fast8_t x = 0; x < 4; ++x) {
182 for (uint_fast8_t y = 0; y < 4; ++y) {
183 action->addChange(new Change(map.allocator(node->createTile(ndx * 4 + x, ndy * 4 + y, z))));
184 }
185 }
186 return;
187 }
188
189 // -1 on address since we skip the first START_NODE when sending
190 const std::string& data = message.read<std::string>();
191 mapReader.assign(reinterpret_cast<const uint8_t*>(data.c_str() - 1), data.size());
192
193 BinaryNode* rootNode = mapReader.getRootNode();
194 BinaryNode* tileNode = rootNode->getChild();
195
196 Position position(0, 0, z);
197 for (uint_fast8_t x = 0; x < 4; ++x) {
198 for (uint_fast8_t y = 0; y < 4; ++y) {
199 position.x = (ndx * 4) + x;
200 position.y = (ndy * 4) + y;
201
202 if (testFlags(tileBits, static_cast<uint64_t>(1) << ((x * 4) + y))) {
203 receiveTile(tileNode, editor, action, &position);
204 tileNode->advance();
205 } else {
206 action->addChange(new Change(map.allocator(node->createTile(position.x, position.y, z))));
207 }
208 }
209 }
210 mapReader.close();
211}
212
213void LiveSocket::sendFloor(NetworkMessage& message, Floor* floor) {
214 uint16_t tileBits = 0;
215 for (uint_fast8_t x = 0; x < 4; ++x) {
216 for (uint_fast8_t y = 0; y < 4; ++y) {
217 uint_fast8_t index = (x * 4) + y;
218
219 Tile* tile = floor->locs[index].get();
220 if (tile && tile->size() > 0) {
221 tileBits |= (1 << index);
222 }
223 }
224 }
225
226 message.write<uint16_t>(tileBits);
227 if (tileBits == 0) {
228 return;
229 }
230
231 mapWriter.reset();
232 for (uint_fast8_t x = 0; x < 4; ++x) {
233 for (uint_fast8_t y = 0; y < 4; ++y) {
234 uint_fast8_t index = (x * 4) + y;
235 if (testFlags(tileBits, static_cast<uint64_t>(1) << index)) {
236 sendTile(mapWriter, floor->locs[index].get(), nullptr);
237 }
238 }
239 }
240 mapWriter.endNode();
241
242 std::string stream(
243 reinterpret_cast<char*>(mapWriter.getMemory()),
244 mapWriter.getSize()
245 );
246 message.write<std::string>(stream);
247}
248
249void LiveSocket::receiveTile(BinaryNode* node, Editor& editor, Action* action, const Position* position) {
250 ASSERT(node != nullptr);
251
252 Tile* tile = readTile(node, editor, position);
253 if (tile) {
254 action->addChange(newd Change(tile));
255 }
256}
257
258void LiveSocket::sendTile(MemoryNodeFileWriteHandle& writer, Tile* tile, const Position* position) {
259 writer.addNode(tile->isHouseTile() ? OTBM_HOUSETILE : OTBM_TILE);
260 if (position) {
261 writer.addU16(position->x);
262 writer.addU16(position->y);
263 writer.addU8(position->z);
264 }
265
266 if (tile->isHouseTile()) {
267 writer.addU32(tile->getHouseID());
268 }
269
270 if (tile->getMapFlags()) {
271 writer.addByte(OTBM_ATTR_TILE_FLAGS);
272 writer.addU32(tile->getMapFlags());
273 if (tile->getMapFlags() & TILESTATE_ZONE_BRUSH) {
274 for (const auto& zoneId : tile->getZoneIds()) {
275 writer.addU16(zoneId);
276 }
277 writer.addU16(0);
278 }
279 }
280
281 Item* ground = tile->ground;
282 if (ground) {
283 if (ground->isComplex()) {
284 ground->serializeItemNode_OTBM(mapVersion, writer);
285 } else {
286 writer.addByte(OTBM_ATTR_ITEM);
287 ground->serializeItemCompact_OTBM(mapVersion, writer);
288 }
289 }
290
291 for (Item* item : tile->items) {
292 item->serializeItemNode_OTBM(mapVersion, writer);
293 }
294
295 writer.endNode();
296}
297
298Tile* LiveSocket::readTile(BinaryNode* node, Editor& editor, const Position* position) {
299 ASSERT(node != nullptr);
300
301 Map& map = editor.map;
302
303 uint8_t tileType;
304 node->getByte(tileType);
305
306 if (tileType != OTBM_TILE && tileType != OTBM_HOUSETILE) {
307 return nullptr;
308 }
309
310 Position pos;
311 if (position) {
312 pos = *position;
313 } else {
314 uint16_t x;
315 node->getU16(x);
316 pos.x = x;
317 uint16_t y;
318 node->getU16(y);
319 pos.y = y;
320 uint8_t z;
321 node->getU8(z);
322 pos.z = z;
323 }
324
325 Tile* tile = map.allocator(
326 map.createTileL(pos)
327 );
328
329 if (tileType == OTBM_HOUSETILE) {
330 uint32_t houseId;
331 if (!node->getU32(houseId)) {
332 // warning("House tile without house data, discarding tile");
333 delete tile;
334 return nullptr;
335 }
336
337 if (houseId) {
338 House* house = map.houses.getHouse(houseId);
339 if (house) {
340 tile->setHouse(house);
341 }
342 } else {
343 // warning("Invalid house id from tile %d:%d:%d", pos.x, pos.y, pos.z);
344 }
345 }
346
347 uint8_t attribute;
348 while (node->getU8(attribute)) {
349 switch (attribute) {
350 case OTBM_ATTR_TILE_FLAGS: {
351 uint32_t flags = 0;
352 if (!node->getU32(flags)) {
353 // warning("Invalid tile flags of tile on %d:%d:%d", pos.x, pos.y, pos.z);
354 }
355 tile->setMapFlags(flags);
356 if (flags & TILESTATE_ZONE_BRUSH) {
357 uint16_t zoneId = 0;
358 do {
359 if (!node->getU16(zoneId)) {
360 // warning("Invalid zone id of tile on %d:%d:%d", pos.x, pos.y, pos.z);
361 }
362
363 if (zoneId != 0) {
364 tile->addZoneId(zoneId);
365 }
366 } while (zoneId != 0);
367 }
368 break;
369 }
370 case OTBM_ATTR_ITEM: {
371 Item* item = Item::Create_OTBM(mapVersion, node);
372 if (!item) {
373 // warning("Invalid item at tile %d:%d:%d", pos.x, pos.y, pos.z);
374 }
375 tile->addItem(item);
376 break;
377 }
378 default:
379 // warning("Unknown tile attribute at %d:%d:%d", pos.x, pos.y, pos.z);
380 break;
381 }
382 }
383
384 // for(BinaryNode* itemNode = node->getChild(); itemNode; itemNode->advance()) {
385 BinaryNode* itemNode = node->getChild();
386 if (itemNode) {
387 do {
388 uint8_t itemType;
389 if (!itemNode->getByte(itemType)) {
390 // warning("Unknown item type %d:%d:%d", pos.x, pos.y, pos.z);
391 delete tile;
392 return nullptr;
393 }
394
395 if (itemType == OTBM_ITEM) {
396 Item* item = Item::Create_OTBM(mapVersion, itemNode);
397 if (item) {
398 if (!item->unserializeItemNode_OTBM(mapVersion, itemNode)) {
399 // warning("Couldn't unserialize item attributes at %d:%d:%d", pos.x, pos.y, pos.z);
400 }
401 tile->addItem(item);
402 }
403 } else {
404 // warning("Unknown type of tile child node");
405 }
406 //}
407 } while (itemNode->advance());
408 }
409
410 return tile;
411}
412
413LiveCursor LiveSocket::readCursor(NetworkMessage& message) {
414 LiveCursor cursor;
415 cursor.id = message.read<uint32_t>();
416
417 uint8_t r = message.read<uint8_t>();
418 uint8_t g = message.read<uint8_t>();
419 uint8_t b = message.read<uint8_t>();
420 uint8_t a = message.read<uint8_t>();
421 cursor.color = wxColor(r, g, b, a);
422
423 cursor.pos = message.read<Position>();
424 return cursor;
425}
426
427void LiveSocket::writeCursor(NetworkMessage& message, const LiveCursor& cursor) {
428 message.write<uint32_t>(cursor.id);
429 message.write<uint8_t>(cursor.color.Red());
430 message.write<uint8_t>(cursor.color.Green());
431 message.write<uint8_t>(cursor.color.Blue());
432 message.write<uint8_t>(cursor.color.Alpha());
433 message.write<Position>(cursor.pos);
434}
435
Top