Map Editor

RD Core / Commit #3

Commit Map Editor

Alex Alex committed 18/10/2026 23:40 main Patch
436 files +250,791 -0
A RD_Map_Editor/source/live_peer.h +94-0 View file
@@ -0,0 +1,94 @@
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+#ifndef _RME_LIVE_PEER_H_
19+#define _RME_LIVE_PEER_H_
20+
21+#include "live_socket.h"
22+#include "net_connection.h"
23+
24+class LiveServer;
25+class LivePeer : public LiveSocket {
26+public:
27+ LivePeer(LiveServer* server, boost::asio::ip::tcp::socket socket);
28+ ~LivePeer();
29+
30+ void close();
31+ bool handleError(const boost::system::error_code& error);
32+
33+ //
34+ uint32_t getId() const {
35+ return id;
36+ }
37+ uint32_t getClientId() const {
38+ return clientId;
39+ }
40+
41+ std::string getHostName() const;
42+
43+ wxColor getUsedColor() const {
44+ return color;
45+ }
46+ void setUsedColor(const wxColor& newColor) {
47+ color = newColor;
48+ }
49+
50+ //
51+ void receiveHeader() override;
52+ void receive(uint32_t packetSize) override;
53+ void send(NetworkMessage& message) override;
54+ void sendChat(const wxString& chatMessage) override;
55+
56+ //
57+ void updateCursor(const Position& position) override;
58+
59+protected:
60+ void parseLoginPacket(NetworkMessage message);
61+ void parseEditorPacket(NetworkMessage message);
62+
63+ // login packets
64+ void parseHello(NetworkMessage& message);
65+ void parseReady(NetworkMessage& message);
66+
67+ // editor packets
68+ void parseNodeRequest(NetworkMessage& message);
69+ void parseReceiveChanges(NetworkMessage& message);
70+ void parseAddHouse(NetworkMessage& message);
71+ void parseEditHouse(NetworkMessage& message);
72+ void parseRemoveHouse(NetworkMessage& message);
73+ void parseCursorUpdate(NetworkMessage& message);
74+ void parseChatMessage(NetworkMessage& message);
75+ void parseClientColorUpdate(NetworkMessage& message);
76+
77+ //
78+ NetworkMessage readMessage;
79+
80+ LiveServer* server;
81+ boost::asio::ip::tcp::socket socket;
82+
83+ wxColor color;
84+
85+ uint32_t id;
86+ uint32_t clientId;
87+
88+ bool connected;
89+
90+ friend class LiveLogTab;
91+ friend class LiveServer;
92+};
93+
94+#endif
A RD_Map_Editor/source/live_server.h +104-0 View file
@@ -0,0 +1,104 @@
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+#ifndef _RME_LIVE_SERVER_H_
19+#define _RME_LIVE_SERVER_H_
20+
21+#include "live_socket.h"
22+#include "net_connection.h"
23+#include "action.h"
24+
25+class LivePeer;
26+class LiveLogTab;
27+class QTreeNode;
28+
29+class LiveServer : public LiveSocket {
30+public:
31+ LiveServer(Editor& editor);
32+ ~LiveServer();
33+
34+ //
35+ bool bind();
36+ void close();
37+
38+ void acceptClient();
39+ void removeClient(uint32_t id);
40+
41+ //
42+ void receiveHeader() override { }
43+ void receive(uint32_t packetSize) override { }
44+ void send(NetworkMessage& message) override { }
45+ void sendChat(const wxString& chatMessage) override;
46+
47+ //
48+ void updateCursor(const Position& position) override;
49+ void updateClientList() const;
50+
51+ //
52+ LiveLogTab* createLogWindow(wxWindow* parent);
53+
54+ //
55+ uint16_t getPort() const;
56+ bool setPort(int32_t newPort);
57+
58+ Editor* getEditor() const {
59+ return editor;
60+ }
61+
62+ uint32_t getFreeClientId();
63+ std::string getHostName() const;
64+
65+ //
66+ void broadcastNodes(DirtyList& dirtyList);
67+ void broadcastChat(const wxString& speaker, const wxString& chatMessage);
68+ void broadcastCursor(const LiveCursor& cursor);
69+ void broadcastColorChange(uint32_t clientId, const wxColor& color);
70+
71+ void startOperation(const wxString& operationMessage);
72+ void updateOperation(int32_t percent);
73+
74+ // Override socket type check
75+ bool IsServer() const override { return true; }
76+
77+ void setUsedColor(const wxColor& color);
78+ wxColor getUsedColor() const { return usedColor; }
79+
80+ const std::unordered_map<uint32_t, LivePeer*>& getClients() const { return clients; }
81+
82+ // Helper method for writing cursor data to messages
83+ void writeCursorToMessage(NetworkMessage& message, const LiveCursor& cursor) {
84+ writeCursor(message, cursor);
85+ }
86+
87+protected:
88+ std::unordered_map<uint32_t, LivePeer*> clients;
89+
90+ std::shared_ptr<boost::asio::ip::tcp::acceptor> acceptor;
91+ std::shared_ptr<boost::asio::ip::tcp::socket> socket;
92+
93+ Editor* editor;
94+
95+ uint32_t clientIds;
96+ uint16_t port;
97+
98+ bool stopped;
99+ bool drawingReady; // Flag indicating server is ready for drawing operations
100+
101+ wxColor usedColor;
102+};
103+
104+#endif
A RD_Map_Editor/source/live_socket.cpp +434-0 View file
@@ -0,0 +1,434 @@
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+
25+LiveSocket::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+
32+LiveSocket::~LiveSocket() {
33+ //
34+}
35+
36+wxString LiveSocket::getName() const {
37+ return name;
38+}
39+
40+bool 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+
52+wxString LiveSocket::getPassword() const {
53+ return password;
54+}
55+
56+bool 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+
65+wxString LiveSocket::getLastError() const {
66+ return lastError;
67+}
68+
69+void LiveSocket::setLastError(const wxString& error) {
70+ lastError = error;
71+}
72+
73+std::string LiveSocket::getHostName() const {
74+ return "?";
75+}
76+
77+std::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+
85+void LiveSocket::logMessage(const wxString& message) {
86+ wxTheApp->CallAfter([this, message]() {
87+ if (log) {
88+ log->Message(message);
89+ }
90+ });
91+}
92+
93+void 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+
115+void 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+
176+void 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+
213+void 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+
249+void 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+
258+void 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+
298+Tile* 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+
413+LiveCursor 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+
427+void 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+}
A RD_Map_Editor/source/live_socket.h +121-0 View file
@@ -0,0 +1,121 @@
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+#ifndef _RME_LIVE_SOCKET_H_
19+#define _RME_LIVE_SOCKET_H_
20+
21+#include "position.h"
22+#include "net_connection.h"
23+#include "live_packets.h"
24+#include "filehandle.h"
25+#include "iomap.h"
26+
27+#include <memory>
28+#include <unordered_map>
29+
30+class LiveLogTab;
31+class Action;
32+
33+struct LiveCursor {
34+ uint32_t id;
35+ wxColor color;
36+ Position pos;
37+};
38+
39+class LiveSocket {
40+public:
41+ LiveSocket();
42+ virtual ~LiveSocket();
43+
44+ //
45+ wxString getName() const;
46+ bool setName(const wxString& newName);
47+
48+ wxString getPassword() const;
49+ bool setPassword(const wxString& newPassword);
50+
51+ wxString getLastError() const;
52+ void setLastError(const wxString& error);
53+
54+ std::string getHostName() const;
55+ std::vector<LiveCursor> getCursorList() const;
56+
57+ // Check socket type
58+ virtual bool IsServer() const { return false; }
59+ virtual bool IsClient() const { return false; }
60+
61+ //
62+ void logMessage(const wxString& message);
63+
64+ //
65+ virtual void receiveHeader() = 0;
66+ virtual void receive(uint32_t packetSize) = 0;
67+ virtual void send(NetworkMessage& message) = 0;
68+ virtual void sendChat(const wxString& chatMessage) = 0;
69+
70+ //
71+ virtual void updateCursor(const Position& position) = 0;
72+
73+ // Helper method to safely get app directory for logging
74+ static wxString GetAppDir() {
75+ wxString dir;
76+ try {
77+ dir = wxStandardPaths::Get().GetUserDataDir();
78+ if (!wxDirExists(dir)) {
79+ wxMkdir(dir);
80+ }
81+ }
82+ catch (...) {
83+ dir = "."; // Fallback to current directory
84+ }
85+ return dir;
86+ }
87+
88+protected:
89+ // receive / send methods
90+ void receiveNode(NetworkMessage& message, Editor& editor, Action* action, int32_t ndx, int32_t ndy, bool underground);
91+ void sendNode(uint32_t clientId, QTreeNode* node, int32_t ndx, int32_t ndy, uint32_t floorMask);
92+
93+ void receiveFloor(NetworkMessage& message, Editor& editor, Action* action, int32_t ndx, int32_t ndy, int32_t z, QTreeNode* node, Floor* floor);
94+ void sendFloor(NetworkMessage& message, Floor* floor);
95+
96+ void receiveTile(BinaryNode* node, Editor& editor, Action* action, const Position* position);
97+ void sendTile(MemoryNodeFileWriteHandle& writer, Tile* tile, const Position* position);
98+
99+ // read / write types
100+ Tile* readTile(BinaryNode* node, Editor& editor, const Position* position);
101+
102+ LiveCursor readCursor(NetworkMessage& message);
103+ void writeCursor(NetworkMessage& message, const LiveCursor& cursor);
104+
105+ //
106+ std::unordered_map<uint32_t, LiveCursor> cursors;
107+
108+ MemoryNodeFileReadHandle mapReader;
109+ MemoryNodeFileWriteHandle mapWriter;
110+ VirtualIOMap mapVersion;
111+
112+ LiveLogTab* log;
113+
114+ wxString name;
115+ wxString password;
116+ wxString lastError;
117+
118+ friend class LiveLogTab;
119+};
120+
121+#endif
A RD_Map_Editor/source/live_tab.cpp +505-0 View file
@@ -0,0 +1,505 @@
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+
20+#include <wx/splitter.h>
21+#include <wx/grid.h>
22+#include <wx/colordlg.h>
23+#include <fstream>
24+#include <wx/filename.h>
25+#include <wx/stdpaths.h>
26+
27+#include "live_tab.h"
28+#include "live_socket.h"
29+#include "live_peer.h"
30+#include "live_server.h"
31+#include "live_client.h"
32+
33+class myGrid : public wxGrid {
34+public:
35+ myGrid(wxWindow* parent, wxWindowID id, wxPoint pos, wxSize size) :
36+ wxGrid(parent, id, pos, size) { }
37+ virtual ~myGrid() { }
38+
39+ void DrawCellHighlight(wxDC& dc, const wxGridCellAttr* attr) {
40+ // wxGrid::DrawCellHighlight(dc, attr);
41+ }
42+
43+ DECLARE_CLASS(myGrid);
44+};
45+
46+IMPLEMENT_CLASS(myGrid, wxGrid)
47+
48+// Menu IDs and control IDs
49+enum {
50+ LIVE_LOG_COPY_SELECTED = 10001,
51+ LIVE_CHAT_INPUT = 10002
52+};
53+
54+BEGIN_EVENT_TABLE(LiveLogTab, wxPanel)
55+EVT_TEXT_ENTER(LIVE_CHAT_INPUT, LiveLogTab::OnChat)
56+EVT_RIGHT_DOWN(LiveLogTab::OnLogRightClick)
57+EVT_MENU(LIVE_LOG_COPY_SELECTED, LiveLogTab::OnCopySelectedLogText)
58+EVT_BOOKCTRL_PAGE_CHANGED(wxID_ANY, LiveLogTab::OnPageChanged)
59+EVT_GRID_CELL_LEFT_CLICK(LiveLogTab::OnGridCellLeftClick)
60+END_EVENT_TABLE()
61+
62+LiveLogTab::LiveLogTab(MapTabbook* aui, LiveSocket* server) :
63+ EditorTab(),
64+ wxPanel(aui),
65+ aui(aui),
66+ socket(server) {
67+ wxSizer* topsizer = newd wxBoxSizer(wxVERTICAL);
68+
69+ wxPanel* splitter = newd wxPanel(this);
70+ topsizer->Add(splitter, 1, wxEXPAND);
71+
72+ // Setup left panel
73+ wxPanel* left_pane = newd wxPanel(splitter);
74+ wxSizer* left_sizer = newd wxBoxSizer(wxVERTICAL);
75+
76+ wxFont time_font(*wxSWISS_FONT);
77+
78+ // Create a notebook for tabs
79+ notebook = newd wxNotebook(left_pane, wxID_ANY);
80+
81+ // Debug log tab
82+ debug_log = newd wxTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
83+ wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH | wxTE_AUTO_URL);
84+ debug_log->SetFont(time_font);
85+ debug_log->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(LiveLogTab::OnLogRightClick), nullptr, this);
86+ notebook->AddPage(debug_log, "Debug");
87+
88+ // Chat log tab
89+ chat_log = newd wxTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
90+ wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH | wxTE_AUTO_URL);
91+ chat_log->SetFont(time_font);
92+ chat_log->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(LiveLogTab::OnLogRightClick), nullptr, this);
93+ notebook->AddPage(chat_log, "Chat");
94+
95+ left_sizer->Add(notebook, 1, wxEXPAND);
96+
97+ input = newd wxTextCtrl(left_pane, LIVE_CHAT_INPUT, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
98+ left_sizer->Add(input, 0, wxEXPAND);
99+
100+ input->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(LiveLogTab::OnSelectChatbox), nullptr, this);
101+ input->Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(LiveLogTab::OnDeselectChatbox), nullptr, this);
102+ input->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(LiveLogTab::OnKeyDown), nullptr, this);
103+
104+ left_pane->SetSizerAndFit(left_sizer);
105+
106+ // Setup right panel
107+ user_list = newd myGrid(splitter, wxID_ANY, wxDefaultPosition, wxSize(280, 100));
108+ user_list->CreateGrid(5, 3);
109+ user_list->DisableDragRowSize();
110+ user_list->DisableDragColSize();
111+ user_list->SetSelectionMode(wxGrid::wxGridSelectRows);
112+ user_list->SetRowLabelSize(0);
113+
114+ user_list->SetColLabelValue(0, "");
115+ user_list->SetColSize(0, 24);
116+ user_list->SetColLabelValue(1, "#");
117+ user_list->SetColSize(1, 36);
118+ user_list->SetColLabelValue(2, "Name");
119+ user_list->SetColSize(2, 200);
120+
121+ // Finalize
122+ SetSizerAndFit(topsizer);
123+
124+ wxSizer* split_sizer = newd wxBoxSizer(wxHORIZONTAL);
125+ split_sizer->Add(left_pane, wxSizerFlags(1).Expand());
126+ split_sizer->Add(user_list, wxSizerFlags(0).Expand());
127+ splitter->SetSizerAndFit(split_sizer);
128+
129+ aui->AddTab(this, true);
130+}
131+
132+LiveLogTab::~LiveLogTab() {
133+}
134+
135+wxString LiveLogTab::GetTitle() const {
136+ if (socket) {
137+ return "Live Log - " + socket->getHostName();
138+ }
139+ return "Live Log - Disconnected";
140+}
141+
142+void LiveLogTab::Disconnect() {
143+ socket->log = nullptr;
144+ input->SetWindowStyle(input->GetWindowStyle() | wxTE_READONLY);
145+ socket = nullptr;
146+ Refresh();
147+}
148+
149+wxString format00(wxDateTime::wxDateTime_t t) {
150+ wxString str;
151+ if (t < 10) {
152+ str << "0";
153+ }
154+ str << t;
155+ return str;
156+}
157+
158+wxString LiveLogTab::GetLogFilePath(const wxString& logType) {
159+ // Create a logs directory in the user's documents folder
160+ wxString docsDir = wxStandardPaths::Get().GetDocumentsDir();
161+ wxString logsDir = docsDir + wxFileName::GetPathSeparator() + "RME_Logs";
162+
163+ // Create the directory if it doesn't exist
164+ if (!wxDirExists(logsDir)) {
165+ wxMkdir(logsDir);
166+ }
167+
168+ // Create a unique filename based on session timestamp
169+ static wxString sessionTimestamp;
170+ if (sessionTimestamp.IsEmpty()) {
171+ wxDateTime now = wxDateTime::Now();
172+ sessionTimestamp = now.Format("%Y%m%d_%H%M%S");
173+ }
174+
175+ // Return the full path
176+ return logsDir + wxFileName::GetPathSeparator() + "rmelog_" + logType + "_" + sessionTimestamp + ".txt";
177+}
178+
179+void LiveLogTab::Message(const wxString& str) {
180+ // Simply log to file - no UI interaction, no CallAfter
181+ wxDateTime t = wxDateTime::Now();
182+ wxString time, message;
183+ time << format00(t.GetHour()) << ":"
184+ << format00(t.GetMinute()) << ":"
185+ << format00(t.GetSecond());
186+
187+ // Format the message with timestamp
188+ message << time << " - " << str << "\n";
189+
190+ // Write to log file only
191+ static wxString logFilePath;
192+ if (logFilePath.IsEmpty()) {
193+ logFilePath = GetLogFilePath("debug");
194+ }
195+
196+ try {
197+ std::ofstream logFile(logFilePath.ToStdString(), std::ios::app);
198+ if (logFile.is_open()) {
199+ logFile << message;
200+ logFile.close();
201+ }
202+ }
203+ catch (...) {
204+ // Silent failure - don't propagate exceptions
205+ }
206+}
207+
208+void LiveLogTab::Chat(const wxString& speaker, const wxString& str) {
209+ // Format the chat message
210+ wxDateTime t = wxDateTime::Now();
211+ wxString time, message;
212+ time << format00(t.GetHour()) << ":"
213+ << format00(t.GetMinute()) << ":"
214+ << format00(t.GetSecond());
215+
216+ // Format the chat message with timestamp and speaker
217+ message << time << " [" << speaker << "]: " << str << "\n";
218+
219+ // Write to log file first - this is guaranteed not to crash
220+ static wxString logFilePath;
221+ if (logFilePath.IsEmpty()) {
222+ logFilePath = GetLogFilePath("chat");
223+ }
224+
225+ try {
226+ // Log to file
227+ std::ofstream logFile(logFilePath.ToStdString(), std::ios::app);
228+ if (logFile.is_open()) {
229+ logFile << message;
230+ logFile.close();
231+ }
232+
233+ // Only try to update UI if we're the main thread to avoid CallAfter issues
234+ if (wxThread::IsMain() && chat_log && chat_log->IsShownOnScreen()) {
235+ // Direct update without CallAfter
236+ chat_log->AppendText(message);
237+ }
238+ }
239+ catch (...) {
240+ // Silent failure - don't propagate exceptions
241+ }
242+}
243+
244+void LiveLogTab::OnChat(wxCommandEvent& evt) {
245+ wxString message = input->GetValue();
246+ if (message.IsEmpty()) {
247+ return;
248+ }
249+
250+ if (socket) {
251+ // Send the chat message
252+ if (socket->getName() == "HOST") {
253+ // If this is the host, the message appears with HOST prefix
254+ socket->sendChat(message);
255+ // Display the message locally too
256+ Chat("HOST", message);
257+ } else {
258+ socket->sendChat(message);
259+ }
260+
261+ // Clear the input field
262+ input->Clear();
263+ }
264+}
265+
266+void LiveLogTab::OnKeyDown(wxKeyEvent& evt) {
267+ if (evt.GetKeyCode() == WXK_RETURN && !evt.ShiftDown()) {
268+ // Send the message when Enter is pressed (without Shift)
269+ wxString message = input->GetValue();
270+ if (!message.IsEmpty() && socket) {
271+ if (socket->getName() == "HOST") {
272+ socket->sendChat(message);
273+ Chat("HOST", message);
274+ } else {
275+ socket->sendChat(message);
276+ }
277+ input->Clear();
278+ }
279+ } else {
280+ evt.Skip();
281+ }
282+}
283+
284+void LiveLogTab::OnResizeChat(wxSizeEvent& evt) {
285+ // No need to adjust anything for wxTextCtrl
286+ evt.Skip();
287+}
288+
289+void LiveLogTab::OnResizeClientList(wxSizeEvent& evt) {
290+}
291+
292+void LiveLogTab::OnSelectChatbox(wxFocusEvent& evt) {
293+ g_gui.DisableHotkeys();
294+}
295+
296+void LiveLogTab::OnDeselectChatbox(wxFocusEvent& evt) {
297+ g_gui.EnableHotkeys();
298+}
299+
300+void LiveLogTab::OnLogRightClick(wxMouseEvent& evt) {
301+ wxMenu menu;
302+ menu.Append(LIVE_LOG_COPY_SELECTED, "Copy Selected Text");
303+ PopupMenu(&menu);
304+}
305+
306+void LiveLogTab::OnCopySelectedLogText(wxCommandEvent& evt) {
307+ if(wxTheClipboard->Open()) {
308+ // Determine which log has focus and copy from that one
309+ wxTextCtrl* activeLog = nullptr;
310+
311+ if (wxWindow::FindFocus() == debug_log) {
312+ activeLog = debug_log;
313+ } else if (wxWindow::FindFocus() == chat_log) {
314+ activeLog = chat_log;
315+ } else {
316+ // Default to currently visible page
317+ int selection = notebook->GetSelection();
318+ activeLog = (selection == 0) ? debug_log : chat_log;
319+ }
320+
321+ wxTheClipboard->SetData(new wxTextDataObject(activeLog->GetStringSelection()));
322+ wxTheClipboard->Close();
323+ }
324+}
325+
326+void LiveLogTab::UpdateClientList(const std::unordered_map<uint32_t, LivePeer*>& updatedClients) {
327+ // Skip during critical operations to prevent crashes
328+ static bool insideOperation = false;
329+ if (insideOperation) return;
330+
331+ // Guard against reentrancy which might cause crashes
332+ insideOperation = true;
333+
334+ // Use CallAfter to ensure UI operations happen on the main thread
335+ // This prevents crashes during critical drawing operations
336+ wxTheApp->CallAfter([this, updatedClients]() {
337+ // Only proceed if grid is properly initialized and visible
338+ if (!user_list || !user_list->IsShownOnScreen()) {
339+ insideOperation = false;
340+ return;
341+ }
342+
343+ try {
344+ // Delete old rows
345+ if (user_list->GetNumberRows() > 0) {
346+ user_list->DeleteRows(0, user_list->GetNumberRows());
347+ }
348+
349+ clients = updatedClients;
350+
351+ // For server, use the provided client list
352+ if (socket && socket->IsServer()) {
353+ // Make sure we have data to display
354+ if (clients.empty()) {
355+ // At least show the host
356+ user_list->AppendRows(1);
357+ user_list->SetCellBackgroundColour(0, 0, dynamic_cast<LiveServer*>(socket)->getUsedColor());
358+ user_list->SetCellValue(0, 1, "Host");
359+ user_list->SetCellValue(0, 2, "HOST");
360+ } else {
361+ user_list->AppendRows(clients.size());
362+
363+ int32_t i = 0;
364+ for (auto& clientEntry : clients) {
365+ LivePeer* peer = clientEntry.second;
366+ if (peer) {
367+ user_list->SetCellBackgroundColour(i, 0, peer->getUsedColor());
368+ user_list->SetCellValue(i, 1, i2ws((peer->getClientId() >> 1) + 1));
369+ user_list->SetCellValue(i, 2, peer->getName());
370+ ++i;
371+ }
372+ }
373+ }
374+ }
375+ // For client, use cursor information
376+ else if (socket && socket->IsClient()) {
377+ // Get cursor list from the socket
378+ std::vector<LiveCursor> cursors = socket->getCursorList();
379+
380+ if (cursors.empty()) {
381+ // If no cursors, at least show the host
382+ user_list->AppendRows(1);
383+ user_list->SetCellBackgroundColour(0, 0, wxColor(255, 0, 0)); // Default red for host
384+ user_list->SetCellValue(0, 1, "Host");
385+ user_list->SetCellValue(0, 2, "HOST");
386+ } else {
387+ // Add a row for each cursor
388+ user_list->AppendRows(cursors.size());
389+
390+ int32_t i = 0;
391+ for (const auto& cursor : cursors) {
392+ user_list->SetCellBackgroundColour(i, 0, cursor.color);
393+
394+ // In display, clientId 0 is the host, others are +1 and shifted
395+ wxString displayId;
396+ wxString displayName;
397+
398+ if (cursor.id == 0) {
399+ displayId = "Host";
400+ displayName = "HOST";
401+ } else {
402+ displayId = i2ws((cursor.id >> 1) + 1);
403+ displayName = wxString::Format("Client %s", displayId);
404+ }
405+
406+ user_list->SetCellValue(i, 1, displayId);
407+ user_list->SetCellValue(i, 2, displayName);
408+ ++i;
409+ }
410+ }
411+ }
412+
413+ // Refresh the grid after changes
414+ user_list->AutoSize();
415+ user_list->Refresh();
416+ }
417+ catch (const std::exception& e) {
418+ wxLogError("Error updating client list: %s", e.what());
419+ }
420+
421+ insideOperation = false;
422+ });
423+}
424+
425+void LiveLogTab::OnPageChanged(wxBookCtrlEvent& evt) {
426+ // If switching to chat tab, set focus to the input box
427+ if (evt.GetSelection() == 1) { // Chat tab index
428+ input->SetFocus();
429+ }
430+ evt.Skip();
431+}
432+
433+void LiveLogTab::OnGridCellLeftClick(wxGridEvent& evt) {
434+ int row = evt.GetRow();
435+ int col = evt.GetCol();
436+
437+ // Column 0 is the color column
438+ if (col == 0) {
439+ // Get the current color from the background
440+ wxColor currentColor = user_list->GetCellBackgroundColour(row, 0);
441+
442+ // Show color picker dialog
443+ wxColourData colorData;
444+ colorData.SetColour(currentColor);
445+
446+ wxColourDialog dialog(this, &colorData);
447+ dialog.SetTitle("Choose Color");
448+
449+ if (dialog.ShowModal() == wxID_OK) {
450+ wxColour newColor = dialog.GetColourData().GetColour();
451+ ChangeUserColor(row, newColor);
452+ }
453+ }
454+
455+ evt.Skip();
456+}
457+
458+void LiveLogTab::ChangeUserColor(int row, const wxColor& color) {
459+ // Make sure we have a valid client selected
460+ if (row < 0 || row >= user_list->GetNumberRows()) {
461+ return;
462+ }
463+
464+ // Update the color in the UI grid
465+ user_list->SetCellBackgroundColour(row, 0, color);
466+ user_list->Refresh();
467+
468+ // Get the client ID from the row
469+ wxString clientIdStr = user_list->GetCellValue(row, 1);
470+ long displayId = 0;
471+ uint32_t clientId = 0;
472+
473+ if (clientIdStr.ToLong(&displayId)) {
474+ // Convert display ID to actual client ID
475+ clientId = (displayId - 1) << 1;
476+ }
477+
478+ // If we're the host, directly update and broadcast
479+ auto server = dynamic_cast<LiveServer*>(socket);
480+ if (server) {
481+ if (row == 0) {
482+ // Update the host's color
483+ server->setUsedColor(color);
484+ } else {
485+ // Update a client's color by broadcasting
486+ server->broadcastColorChange(clientId, color);
487+
488+ // Also update the local representation
489+ for (const auto& pair : clients) {
490+ LivePeer* peer = pair.second;
491+ if (peer && peer->getClientId() == clientId) {
492+ peer->setUsedColor(color);
493+ break;
494+ }
495+ }
496+ }
497+ } else if (socket && socket->IsClient()) {
498+ // We're a client - send a color update request to the server
499+ LiveClient* client = static_cast<LiveClient*>(socket);
500+
501+ // Row 0 is always the host with client ID 0
502+ uint32_t targetClientId = (row == 0) ? 0 : clientId;
503+ client->sendColorUpdate(targetClientId, color);
504+ }
505+}
A RD_Map_Editor/source/live_tab.h +88-0 View file
@@ -0,0 +1,88 @@
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+#ifndef _RME_LIVE_TAB_H_
19+#define _RME_LIVE_TAB_H_
20+
21+#include "main.h"
22+
23+#include "editor_tabs.h"
24+#include "application.h"
25+#include "live_server.h"
26+
27+class wxGrid;
28+class wxNotebook;
29+
30+class MapTabbook;
31+class LiveSocket;
32+class LiveServer;
33+
34+class LiveLogTab : public EditorTab, public wxPanel {
35+public:
36+ LiveLogTab(MapTabbook* aui, LiveSocket* socket);
37+ ~LiveLogTab();
38+
39+ void Message(const wxString& str);
40+ void Chat(const wxString& speaker, const wxString& str);
41+
42+ virtual wxWindow* GetWindow() const {
43+ return (wxPanel*)this;
44+ }
45+ virtual wxString GetTitle() const;
46+
47+ bool IsConnected() const {
48+ return socket != nullptr;
49+ }
50+ void Disconnect();
51+
52+ LiveSocket* GetSocket() {
53+ return socket;
54+ }
55+
56+ void UpdateClientList(const std::unordered_map<uint32_t, LivePeer*>& updatedClients);
57+
58+ void OnSelectChatbox(wxFocusEvent& evt);
59+ void OnDeselectChatbox(wxFocusEvent& evt);
60+
61+ void OnChat(wxCommandEvent& evt);
62+ void OnKeyDown(wxKeyEvent& evt);
63+ void OnResizeChat(wxSizeEvent& evt);
64+ void OnResizeClientList(wxSizeEvent& evt);
65+ void OnCopySelectedLogText(wxCommandEvent& evt);
66+ void OnLogRightClick(wxMouseEvent& evt);
67+ void OnPageChanged(wxBookCtrlEvent& evt);
68+ void OnGridCellLeftClick(wxGridEvent& evt);
69+ void ChangeUserColor(int row, const wxColor& color);
70+
71+ // Method to get the log file path
72+ wxString GetLogFilePath(const wxString& logType);
73+
74+protected:
75+ MapTabbook* aui;
76+ LiveSocket* socket;
77+ wxNotebook* notebook;
78+ wxTextCtrl* debug_log; // For debug messages
79+ wxTextCtrl* chat_log; // For player chat
80+ wxTextCtrl* input;
81+ wxGrid* user_list;
82+
83+ std::unordered_map<uint32_t, LivePeer*> clients;
84+
85+ DECLARE_EVENT_TABLE();
86+};
87+
88+#endif
A RD_Map_Editor/source/lod_manager.h +37-0 View file
@@ -0,0 +1,37 @@
1+
2+#ifndef RME_LOD_MANAGER_H
3+#define RME_LOD_MANAGER_H
4+
5+class LODManager {
6+public:
7+ enum LODLevel {
8+ FULL_DETAIL = 0, // Zoom 1-3
9+ MEDIUM_DETAIL = 1, // Zoom 4-7
10+ GROUND_ONLY = 2 // Zoom 8+
11+ };
12+
13+ LODManager() : current_level(FULL_DETAIL) {}
14+
15+ LODLevel getLevelForZoom(double zoom) {
16+ if(zoom >= 8.0) return GROUND_ONLY;
17+ if(zoom >= 4.0) return MEDIUM_DETAIL;
18+ return FULL_DETAIL;
19+ }
20+
21+ void updateRenderSettings(double zoom) {
22+ current_level = getLevelForZoom(zoom);
23+ }
24+
25+ bool isGroundOnly() const {
26+ return current_level == GROUND_ONLY;
27+ }
28+
29+ bool isMediumDetail() const {
30+ return current_level == MEDIUM_DETAIL;
31+ }
32+
33+private:
34+ LODLevel current_level;
35+};
36+
37+#endif // RME_LOD_MANAGER_H
A RD_Map_Editor/source/main.h +144-0 View file
@@ -0,0 +1,144 @@
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+#ifndef RME_MAIN_H_
19+#define RME_MAIN_H_
20+
21+#ifdef _WIN32
22+ #define WIN32_LEAN_AND_MEAN
23+ #ifdef _WIN32_WINNT
24+ #undef _WIN32_WINNT
25+ #endif
26+ #define _WIN32_WINNT 0x0501
27+#endif
28+
29+#ifdef DEBUG_MEM
30+
31+ #define _CRTDBG_MAP_ALLOC
32+
33+ #include <stdlib.h>
34+ #include <crtdbg.h>
35+
36+ #pragma warning(disable : 4291)
37+_Ret_bytecap_(_Size) inline void* __CRTDECL operator new(size_t _Size, const char* file, int line) {
38+ return ::operator new(_Size, _NORMAL_BLOCK, file, line);
39+}
40+_Ret_bytecap_(_Size) inline void* __CRTDECL operator new[](size_t _Size, const char* file, int line) {
41+ return ::operator new[](_Size, _NORMAL_BLOCK, file, line);
42+}
43+ #define newd new (__FILE__, __LINE__)
44+
45+#else
46+
47+ #define newd new
48+
49+#endif
50+
51+// Boost libraries
52+#include <boost/utility.hpp>
53+#include <boost/range/adaptor/reversed.hpp>
54+#include <boost/asio.hpp>
55+
56+#include <wx/defs.h>
57+#include "definitions.h"
58+
59+#include <wx/wxprec.h>
60+#ifndef WX_PRECOMP
61+ #include <wx/wx.h>
62+#endif
63+#include <wx/thread.h>
64+#include <wx/utils.h>
65+#include <wx/progdlg.h>
66+#include <wx/glcanvas.h>
67+#include <wx/debugrpt.h>
68+#include <wx/minifram.h>
69+#include <wx/gbsizer.h>
70+#include <wx/choicebk.h>
71+#include <wx/tglbtn.h>
72+#include <wx/dcbuffer.h>
73+#include <wx/aui/aui.h>
74+#include <wx/cmdline.h>
75+#include <wx/filename.h>
76+#include <wx/filepicker.h>
77+#include <wx/arrstr.h>
78+#include <wx/slider.h>
79+#include <wx/spinctrl.h>
80+#include <wx/wxhtml.h>
81+#include <wx/vlbox.h>
82+#include <wx/stdpaths.h>
83+#include <wx/url.h>
84+#include <wx/ipc.h>
85+#include <wx/grid.h>
86+#include <wx/clipbrd.h>
87+
88+// PugiXML
89+#include "ext/pugixml.hpp"
90+
91+// Libarchive, for OTGZ
92+#ifdef OTGZ_SUPPORT
93+ #include <archive.h>
94+ #include <archive_entry.h>
95+#endif
96+
97+// This has annoyed me one time too many
98+#define wxANY_ID (wxID_ANY)
99+
100+#include <assert.h>
101+#define _MSG(msg) !bool(msg)
102+#ifdef __DEBUG__
103+ #define ASSERT assert
104+#else
105+ #define ASSERT(...)
106+#endif
107+
108+// The complete STL ?, well, almost ;)
109+#include <math.h>
110+#include <list>
111+#include <vector>
112+#include <map>
113+#include <string>
114+#include <istream>
115+#include <ostream>
116+#include <sstream>
117+#include <iostream>
118+#include <iomanip>
119+#include <algorithm>
120+#include <limits>
121+#include <set>
122+#include <queue>
123+#include <stdexcept>
124+#include <time.h>
125+#include <fstream>
126+
127+typedef std::vector<std::string> StringVector;
128+typedef wxFileName FileName;
129+
130+#include "json.h"
131+
132+#include "con_vector.h"
133+#include "common.h"
134+#include "threads.h"
135+
136+#include "rme_forward_declarations.h"
137+
138+#if wxCHECK_VERSION(3, 1, 0)
139+ #define FROM_DIP(widget, size) widget->FromDIP(size)
140+#else
141+ #define FROM_DIP(widget, size) size
142+#endif
143+
144+#endif
Top