| @@ -0,0 +1,78 @@ | |||
| 1 | + | #ifndef RME_MONSTER_GENERATOR_DIALOG_H_ | |
| 2 | + | #define RME_MONSTER_GENERATOR_DIALOG_H_ | |
| 3 | + | ||
| 4 | + | #include <wx/wx.h> | |
| 5 | + | #include <wx/notebook.h> | |
| 6 | + | #include <wx/spinctrl.h> | |
| 7 | + | #include <wx/listctrl.h> | |
| 8 | + | ||
| 9 | + | class MonsterGeneratorDialog : public wxDialog { | |
| 10 | + | public: | |
| 11 | + | MonsterGeneratorDialog(wxWindow* parent); | |
| 12 | + | virtual ~MonsterGeneratorDialog(); | |
| 13 | + | ||
| 14 | + | protected: | |
| 15 | + | // Event handlers | |
| 16 | + | void OnSave(wxCommandEvent& event); | |
| 17 | + | void OnLoad(wxCommandEvent& event); | |
| 18 | + | void OnOutfitChange(wxSpinEvent& event); | |
| 19 | + | void OnAddAttack(wxCommandEvent& event); | |
| 20 | + | void OnAddDefense(wxCommandEvent& event); | |
| 21 | + | void OnAddLoot(wxCommandEvent& event); | |
| 22 | + | ||
| 23 | + | private: | |
| 24 | + | void CreateGUI(); | |
| 25 | + | void UpdatePreview(); | |
| 26 | + | void SaveMonsterFile(); | |
| 27 | + | void LoadMonsterFile(); | |
| 28 | + | ||
| 29 | + | // GUI elements | |
| 30 | + | wxNotebook* notebook; | |
| 31 | + | ||
| 32 | + | // Basic info page | |
| 33 | + | wxPanel* basicPanel; | |
| 34 | + | wxTextCtrl* nameCtrl; | |
| 35 | + | wxTextCtrl* descriptionCtrl; | |
| 36 | + | wxChoice* raceChoice; | |
| 37 | + | wxSpinCtrl* experienceCtrl; | |
| 38 | + | wxSpinCtrl* healthCtrl; | |
| 39 | + | wxSpinCtrl* speedCtrl; | |
| 40 | + | wxSpinCtrl* armorCtrl; | |
| 41 | + | wxSpinCtrl* defenseCtrl; | |
| 42 | + | ||
| 43 | + | // Outfit page | |
| 44 | + | wxPanel* outfitPanel; | |
| 45 | + | wxSpinCtrl* lookTypeCtrl; | |
| 46 | + | wxSpinCtrl* headCtrl; | |
| 47 | + | wxSpinCtrl* bodyCtrl; | |
| 48 | + | wxSpinCtrl* legsCtrl; | |
| 49 | + | wxSpinCtrl* feetCtrl; | |
| 50 | + | wxSpinCtrl* addonsCtrl; | |
| 51 | + | wxStaticBitmap* outfitPreview; | |
| 52 | + | ||
| 53 | + | // Attacks page | |
| 54 | + | wxPanel* attacksPanel; | |
| 55 | + | wxListCtrl* attacksList; | |
| 56 | + | ||
| 57 | + | // Defenses page | |
| 58 | + | wxPanel* defensesPanel; | |
| 59 | + | wxListCtrl* defensesList; | |
| 60 | + | ||
| 61 | + | // Immunities page | |
| 62 | + | wxPanel* immunitiesPanel; | |
| 63 | + | wxCheckBox* holyCheck; | |
| 64 | + | wxCheckBox* deathCheck; | |
| 65 | + | wxCheckBox* fireCheck; | |
| 66 | + | wxCheckBox* energyCheck; | |
| 67 | + | wxCheckBox* poisonCheck; | |
| 68 | + | wxCheckBox* iceCheck; | |
| 69 | + | wxCheckBox* physicalCheck; | |
| 70 | + | ||
| 71 | + | // Loot page | |
| 72 | + | wxPanel* lootPanel; | |
| 73 | + | wxListCtrl* lootList; | |
| 74 | + | ||
| 75 | + | DECLARE_EVENT_TABLE() | |
| 76 | + | }; | |
| 77 | + | ||
| 78 | + | #endif // RME_MONSTER_GENERATOR_DIALOG_H_ |
| @@ -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 | + | #include "main.h" | |
| 19 | + | ||
| 20 | + | static inline unsigned long int mt_get(void* vstate); | |
| 21 | + | static double mt_get_double(void* vstate); | |
| 22 | + | static void mt_set(void* state, unsigned long int s); | |
| 23 | + | ||
| 24 | + | #define N 624 /* Period parameters */ | |
| 25 | + | #define M 397 | |
| 26 | + | ||
| 27 | + | /* most significant w-r bits */ | |
| 28 | + | static const unsigned long UPPER_MASK = 0x80000000UL; | |
| 29 | + | ||
| 30 | + | /* least significant r bits */ | |
| 31 | + | static const unsigned long LOWER_MASK = 0x7fffffffUL; | |
| 32 | + | ||
| 33 | + | typedef struct | |
| 34 | + | { | |
| 35 | + | unsigned long mt[N]; | |
| 36 | + | int mti; | |
| 37 | + | } mt_state_t; | |
| 38 | + | ||
| 39 | + | static inline unsigned long | |
| 40 | + | mt_get(void* vstate) { | |
| 41 | + | mt_state_t* state = (mt_state_t*)vstate; | |
| 42 | + | ||
| 43 | + | unsigned long k; | |
| 44 | + | unsigned long int* const mt = state->mt; | |
| 45 | + | ||
| 46 | + | #define MAGIC(y) (((y) & 0x1) ? 0x9908b0dfUL : 0) | |
| 47 | + | ||
| 48 | + | if (state->mti >= N) { /* generate N words at one time */ | |
| 49 | + | int kk; | |
| 50 | + | ||
| 51 | + | for (kk = 0; kk < N - M; kk++) { | |
| 52 | + | unsigned long y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); | |
| 53 | + | mt[kk] = mt[kk + M] ^ (y >> 1) ^ MAGIC(y); | |
| 54 | + | } | |
| 55 | + | for (; kk < N - 1; kk++) { | |
| 56 | + | unsigned long y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); | |
| 57 | + | mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ MAGIC(y); | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | { | |
| 61 | + | unsigned long y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK); | |
| 62 | + | mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ MAGIC(y); | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | state->mti = 0; | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | /* Tempering */ | |
| 69 | + | ||
| 70 | + | k = mt[state->mti]; | |
| 71 | + | k ^= (k >> 11); | |
| 72 | + | k ^= (k << 7) & 0x9d2c5680UL; | |
| 73 | + | k ^= (k << 15) & 0xefc60000UL; | |
| 74 | + | k ^= (k >> 18); | |
| 75 | + | ||
| 76 | + | state->mti++; | |
| 77 | + | ||
| 78 | + | return k; | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | static double | |
| 82 | + | mt_get_double(void* vstate) { | |
| 83 | + | return mt_get(vstate) / 4294967296.0; | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | static void | |
| 87 | + | mt_set(void* vstate, unsigned long int s) { | |
| 88 | + | mt_state_t* state = (mt_state_t*)vstate; | |
| 89 | + | int i; | |
| 90 | + | ||
| 91 | + | if (s == 0) { | |
| 92 | + | s = 4357; /* the default seed is 4357 */ | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | state->mt[0] = s & 0xffffffffUL; | |
| 96 | + | ||
| 97 | + | for (i = 1; i < N; i++) { | |
| 98 | + | /* See Knuth's "Art of Computer Programming" Vol. 2, 3rd | |
| 99 | + | Ed. p.106 for multiplier. */ | |
| 100 | + | ||
| 101 | + | state->mt[i] = (1812433253UL * (state->mt[i - 1] ^ (state->mt[i - 1] >> 30)) + i); | |
| 102 | + | ||
| 103 | + | state->mt[i] &= 0xffffffffUL; | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | state->mti = i; | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | static mt_state_t mt_state; | |
| 110 | + | ||
| 111 | + | void mt_seed(unsigned long s) { | |
| 112 | + | mt_set(&mt_state, s); | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | unsigned long mt_randi() { | |
| 116 | + | return mt_get(&mt_state); | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | double mt_randd() { | |
| 120 | + | return mt_get_double(&mt_state); | |
| 121 | + | } |
| @@ -0,0 +1,25 @@ | |||
| 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_MT_RAND_H_ | |
| 19 | + | #define RME_MT_RAND_H_ | |
| 20 | + | ||
| 21 | + | void mt_seed(unsigned long s); | |
| 22 | + | unsigned long mt_randi(); | |
| 23 | + | double mt_randd(); | |
| 24 | + | ||
| 25 | + | #endif |
| @@ -0,0 +1,219 @@ | |||
| 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 "net_connection.h" | |
| 20 | + | ||
| 21 | + | /* | |
| 22 | + | RME Server-Client Split Roadmap: | |
| 23 | + | ||
| 24 | + | 1. Server Project (New Linux-based project) | |
| 25 | + | - Core Components: | |
| 26 | + | * OTBM file loader (port from RME) | |
| 27 | + | * SPR/DAT loaders | |
| 28 | + | * items.otb handler | |
| 29 | + | * Network message handler | |
| 30 | + | * Live editing protocol | |
| 31 | + | * Client session management | |
| 32 | + | * Map change broadcasting | |
| 33 | + | * Undo/Redo synchronization | |
| 34 | + | ||
| 35 | + | - Required Features: | |
| 36 | + | * Headless operation (no GUI) | |
| 37 | + | * Multi-client support | |
| 38 | + | * File change monitoring | |
| 39 | + | * Backup system | |
| 40 | + | * Change validation | |
| 41 | + | * Permission system | |
| 42 | + | * Map locking mechanism | |
| 43 | + | * Auto-save functionality | |
| 44 | + | ||
| 45 | + | 2. RME Client Modifications: | |
| 46 | + | - Remove: | |
| 47 | + | * LiveServer class | |
| 48 | + | * Server-side packet handlers | |
| 49 | + | * Host/server creation UI | |
| 50 | + | * Local map saving (server handles this) | |
| 51 | + | ||
| 52 | + | - Modify: | |
| 53 | + | * NetworkMessage class (client-only packets) | |
| 54 | + | * LiveClient to handle all server communication | |
| 55 | + | * Editor class to work in client-only mode | |
| 56 | + | * GUI to reflect client-only status | |
| 57 | + | * ActionQueue to forward all changes to server | |
| 58 | + | ||
| 59 | + | 3. Protocol Updates: | |
| 60 | + | - New Packets: | |
| 61 | + | * Client authentication | |
| 62 | + | * Map region requests | |
| 63 | + | * Change proposals | |
| 64 | + | * Lock requests | |
| 65 | + | * Client synchronization | |
| 66 | + | * Server commands | |
| 67 | + | ||
| 68 | + | - Security: | |
| 69 | + | * Implement proper encryption | |
| 70 | + | * Add checksum validation | |
| 71 | + | * Session management | |
| 72 | + | * Anti-tampering measures | |
| 73 | + | ||
| 74 | + | 4. Data Flow: | |
| 75 | + | Server: Client: | |
| 76 | + | [OTBM] <-> [Server] <---> [RME Client] <-> [GUI] | |
| 77 | + | [items.otb] ^ ^ | |
| 78 | + | [spr/dat] | | | |
| 79 | + | +----------------+ | |
| 80 | + | Network Protocol | |
| 81 | + | ||
| 82 | + | Implementation Priority: | |
| 83 | + | 1. Create basic server project structure | |
| 84 | + | 2. Port core file handlers to server | |
| 85 | + | 3. Implement basic client-server protocol | |
| 86 | + | 4. Remove server code from RME | |
| 87 | + | 5. Add security features | |
| 88 | + | 6. Implement advanced features | |
| 89 | + | 7. Testing and optimization | |
| 90 | + | ||
| 91 | + | Note: This requires significant restructuring of the RME codebase | |
| 92 | + | and careful handling of backwards compatibility for offline editing. | |
| 93 | + | */ | |
| 94 | + | ||
| 95 | + | NetworkMessage::NetworkMessage() { | |
| 96 | + | clear(); | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | void NetworkMessage::clear() { | |
| 100 | + | buffer.resize(4); | |
| 101 | + | position = 4; | |
| 102 | + | size = 0; | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | void NetworkMessage::expand(const size_t length) { | |
| 106 | + | if (position + length >= buffer.size()) { | |
| 107 | + | buffer.resize(position + length + 1); | |
| 108 | + | } | |
| 109 | + | size += length; | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | template <> | |
| 113 | + | std::string NetworkMessage::read<std::string>() { | |
| 114 | + | const uint16_t length = read<uint16_t>(); | |
| 115 | + | ||
| 116 | + | // Check if we have enough data for the string | |
| 117 | + | if (position + length > buffer.size()) { | |
| 118 | + | throw std::runtime_error("Buffer underflow - string length exceeds remaining buffer size"); | |
| 119 | + | } | |
| 120 | + | ||
| 121 | + | char* strBuffer = reinterpret_cast<char*>(&buffer[position]); | |
| 122 | + | position += length; | |
| 123 | + | return std::string(strBuffer, length); | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | template <> | |
| 127 | + | Position NetworkMessage::read<Position>() { | |
| 128 | + | Position position; | |
| 129 | + | position.x = read<uint16_t>(); | |
| 130 | + | position.y = read<uint16_t>(); | |
| 131 | + | position.z = read<uint8_t>(); | |
| 132 | + | return position; | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | template <> | |
| 136 | + | void NetworkMessage::write<std::string>(const std::string& value) { | |
| 137 | + | const size_t length = value.length(); | |
| 138 | + | write<uint16_t>(length); | |
| 139 | + | ||
| 140 | + | expand(length); | |
| 141 | + | memcpy(&buffer[position], &value[0], length); | |
| 142 | + | position += length; | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | template <> | |
| 146 | + | void NetworkMessage::write<Position>(const Position& value) { | |
| 147 | + | write<uint16_t>(value.x); | |
| 148 | + | write<uint16_t>(value.y); | |
| 149 | + | write<uint8_t>(value.z); | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | // NetworkConnection | |
| 153 | + | NetworkConnection::NetworkConnection() : | |
| 154 | + | service(nullptr), thread(), stopped(false) { | |
| 155 | + | // | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | NetworkConnection::~NetworkConnection() { | |
| 159 | + | stop(); | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | NetworkConnection& NetworkConnection::getInstance() { | |
| 163 | + | static NetworkConnection connection; | |
| 164 | + | return connection; | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | bool NetworkConnection::start() { | |
| 168 | + | if (thread.joinable()) { | |
| 169 | + | if (stopped) { | |
| 170 | + | return false; | |
| 171 | + | } | |
| 172 | + | return true; | |
| 173 | + | } | |
| 174 | + | ||
| 175 | + | stopped = false; | |
| 176 | + | if (!service) { | |
| 177 | + | service = new boost::asio::io_context; | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | thread = std::thread([this]() -> void { | |
| 181 | + | boost::asio::io_context& serviceRef = *service; | |
| 182 | + | // Create a work guard to keep the context alive even when there are no pending handlers | |
| 183 | + | boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work = | |
| 184 | + | boost::asio::make_work_guard(serviceRef); | |
| 185 | + | ||
| 186 | + | try { | |
| 187 | + | while (!stopped) { | |
| 188 | + | // Run one handler at a time | |
| 189 | + | serviceRef.run_one(); | |
| 190 | + | ||
| 191 | + | // Instead of reset, just check if we should continue | |
| 192 | + | if (serviceRef.stopped() && !stopped) { | |
| 193 | + | // If the service stopped but we didn't request it, restart it | |
| 194 | + | serviceRef.restart(); | |
| 195 | + | } | |
| 196 | + | } | |
| 197 | + | } catch (std::exception& e) { | |
| 198 | + | std::cout << e.what() << std::endl; | |
| 199 | + | } | |
| 200 | + | }); | |
| 201 | + | return true; | |
| 202 | + | } | |
| 203 | + | ||
| 204 | + | void NetworkConnection::stop() { | |
| 205 | + | if (!service) { | |
| 206 | + | return; | |
| 207 | + | } | |
| 208 | + | ||
| 209 | + | service->stop(); | |
| 210 | + | stopped = true; | |
| 211 | + | thread.join(); | |
| 212 | + | ||
| 213 | + | delete service; | |
| 214 | + | service = nullptr; | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | boost::asio::io_context& NetworkConnection::get_service() { | |
| 218 | + | return *service; | |
| 219 | + | } |
| @@ -0,0 +1,90 @@ | |||
| 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_NET_CONNECTION_H_ | |
| 19 | + | #define _RME_NET_CONNECTION_H_ | |
| 20 | + | ||
| 21 | + | #include "position.h" | |
| 22 | + | ||
| 23 | + | #include <string> | |
| 24 | + | #include <vector> | |
| 25 | + | #include <cstdint> | |
| 26 | + | #include <thread> | |
| 27 | + | #include <mutex> | |
| 28 | + | ||
| 29 | + | struct NetworkMessage { | |
| 30 | + | NetworkMessage(); | |
| 31 | + | ||
| 32 | + | void clear(); | |
| 33 | + | void expand(const size_t length); | |
| 34 | + | ||
| 35 | + | // | |
| 36 | + | template <typename T> | |
| 37 | + | T read() { | |
| 38 | + | // Check if we have enough data left to read | |
| 39 | + | if (position + sizeof(T) > buffer.size()) { | |
| 40 | + | throw std::runtime_error("Buffer underflow - attempted to read past buffer end"); | |
| 41 | + | } | |
| 42 | + | T& value = *reinterpret_cast<T*>(&buffer[position]); | |
| 43 | + | position += sizeof(T); | |
| 44 | + | return value; | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | template <typename T> | |
| 48 | + | void write(const T& value) { | |
| 49 | + | expand(sizeof(T)); | |
| 50 | + | memcpy(&buffer[position], &value, sizeof(T)); | |
| 51 | + | position += sizeof(T); | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | // | |
| 55 | + | std::vector<uint8_t> buffer; | |
| 56 | + | size_t position; | |
| 57 | + | size_t size; | |
| 58 | + | }; | |
| 59 | + | ||
| 60 | + | template <> | |
| 61 | + | std::string NetworkMessage::read<std::string>(); | |
| 62 | + | template <> | |
| 63 | + | Position NetworkMessage::read<Position>(); | |
| 64 | + | template <> | |
| 65 | + | void NetworkMessage::write<std::string>(const std::string& value); | |
| 66 | + | template <> | |
| 67 | + | void NetworkMessage::write<Position>(const Position& value); | |
| 68 | + | ||
| 69 | + | class NetworkConnection { | |
| 70 | + | private: | |
| 71 | + | NetworkConnection(); | |
| 72 | + | NetworkConnection(const NetworkConnection& copy) = delete; | |
| 73 | + | ||
| 74 | + | public: | |
| 75 | + | ~NetworkConnection(); | |
| 76 | + | ||
| 77 | + | static NetworkConnection& getInstance(); | |
| 78 | + | ||
| 79 | + | bool start(); | |
| 80 | + | void stop(); | |
| 81 | + | ||
| 82 | + | boost::asio::io_context& get_service(); | |
| 83 | + | ||
| 84 | + | private: | |
| 85 | + | boost::asio::io_context* service; | |
| 86 | + | std::thread thread; | |
| 87 | + | bool stopped; | |
| 88 | + | }; | |
| 89 | + | ||
| 90 | + | #endif |
| @@ -0,0 +1,114 @@ | |||
| 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 "numbertextctrl.h" | |
| 20 | + | ||
| 21 | + | BEGIN_EVENT_TABLE(NumberTextCtrl, wxTextCtrl) | |
| 22 | + | EVT_KILL_FOCUS(NumberTextCtrl::OnKillFocus) | |
| 23 | + | EVT_TEXT_ENTER(wxID_ANY, NumberTextCtrl::OnTextEnter) | |
| 24 | + | END_EVENT_TABLE() | |
| 25 | + | ||
| 26 | + | NumberTextCtrl::NumberTextCtrl(wxWindow* parent, wxWindowID id, long value, long minvalue, long maxvalue, const wxPoint& pos, const wxSize& sz, long style, const wxString& name) : | |
| 27 | + | wxTextCtrl(parent, id, (wxString() << value), pos, sz, style, wxTextValidator(wxFILTER_NUMERIC), name), | |
| 28 | + | minval(minvalue), maxval(maxvalue), lastval(value) { | |
| 29 | + | //// | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | NumberTextCtrl::NumberTextCtrl(wxWindow* parent, wxWindowID id, long value, long minvalue, long maxvalue, long style, const wxString& name, const wxPoint& pos, const wxSize& sz) : | |
| 33 | + | wxTextCtrl(parent, id, (wxString() << value), pos, sz, style, wxTextValidator(wxFILTER_NUMERIC), name), | |
| 34 | + | minval(minvalue), maxval(maxvalue), lastval(value) { | |
| 35 | + | //// | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | NumberTextCtrl::~NumberTextCtrl() { | |
| 39 | + | //// | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | void NumberTextCtrl::OnKillFocus(wxFocusEvent& evt) { | |
| 43 | + | CheckRange(); | |
| 44 | + | evt.Skip(); | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | void NumberTextCtrl::OnTextEnter(wxCommandEvent& evt) { | |
| 48 | + | CheckRange(); | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | void NumberTextCtrl::SetIntValue(long value) { | |
| 52 | + | wxString sv; | |
| 53 | + | sv << value; | |
| 54 | + | // Will generate events | |
| 55 | + | SetValue(sv); | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | long NumberTextCtrl::GetIntValue() { | |
| 59 | + | long l; | |
| 60 | + | if (GetValue().ToLong(&l)) { | |
| 61 | + | return l; | |
| 62 | + | } | |
| 63 | + | return 0; | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | void NumberTextCtrl::SetMinValue(long value) { | |
| 67 | + | if (value == minval) { | |
| 68 | + | return; | |
| 69 | + | } | |
| 70 | + | minval = value; | |
| 71 | + | CheckRange(); | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | void NumberTextCtrl::SetMaxValue(long value) { | |
| 75 | + | if (value == maxval) { | |
| 76 | + | return; | |
| 77 | + | } | |
| 78 | + | maxval = value; | |
| 79 | + | CheckRange(); | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | void NumberTextCtrl::CheckRange() { | |
| 83 | + | wxString text = GetValue(); | |
| 84 | + | wxString ntext; | |
| 85 | + | ||
| 86 | + | for (size_t s = 0; s < text.size(); ++s) { | |
| 87 | + | if (text[s] >= '0' && text[s] <= '9') { | |
| 88 | + | ntext.Append(text[s]); | |
| 89 | + | } | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | // Check that value is in range | |
| 93 | + | long v; | |
| 94 | + | if (ntext.size() != 0 && ntext.ToLong(&v)) { | |
| 95 | + | if (v < minval) { | |
| 96 | + | v = minval; | |
| 97 | + | } else if (v > maxval) { | |
| 98 | + | v = maxval; | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | ntext.clear(); | |
| 102 | + | ntext << v; | |
| 103 | + | lastval = v; | |
| 104 | + | } else { | |
| 105 | + | ntext.clear(); | |
| 106 | + | ntext << lastval; | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | // Check if there was any change | |
| 110 | + | if (ntext != text) { | |
| 111 | + | // ChangeValue doesn't generate events | |
| 112 | + | ChangeValue(ntext); | |
| 113 | + | } | |
| 114 | + | } |
| @@ -0,0 +1,44 @@ | |||
| 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_NUMBER_TEXT_CTRL_H_ | |
| 19 | + | #define _RME_NUMBER_TEXT_CTRL_H_ | |
| 20 | + | ||
| 21 | + | // Text ctrl that only allows number input | |
| 22 | + | class NumberTextCtrl : public wxTextCtrl { | |
| 23 | + | public: | |
| 24 | + | NumberTextCtrl(wxWindow* parent, wxWindowID id = wxID_ANY, long value = 0, long minvalue = 0, long maxvalue = 100, const wxPoint& pos = wxDefaultPosition, const wxSize& sz = wxDefaultSize, long style = 0, const wxString& name = wxTextCtrlNameStr); | |
| 25 | + | NumberTextCtrl(wxWindow* parent, wxWindowID id = wxID_ANY, long value = 0, long minvalue = 0, long maxvalue = 100, long style = 0, const wxString& name = wxTextCtrlNameStr, const wxPoint& pos = wxDefaultPosition, const wxSize& sz = wxDefaultSize); | |
| 26 | + | ~NumberTextCtrl(); | |
| 27 | + | ||
| 28 | + | void OnKillFocus(wxFocusEvent&); | |
| 29 | + | void OnTextEnter(wxCommandEvent&); | |
| 30 | + | ||
| 31 | + | long GetIntValue(); | |
| 32 | + | void SetIntValue(long value); | |
| 33 | + | ||
| 34 | + | void SetMinValue(long value); | |
| 35 | + | void SetMaxValue(long value); | |
| 36 | + | ||
| 37 | + | protected: | |
| 38 | + | void CheckRange(); | |
| 39 | + | ||
| 40 | + | long minval, maxval, lastval; | |
| 41 | + | DECLARE_EVENT_TABLE(); | |
| 42 | + | }; | |
| 43 | + | ||
| 44 | + | #endif |
| @@ -0,0 +1,913 @@ | |||
| 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/grid.h> | |
| 21 | + | ||
| 22 | + | #include "tile.h" | |
| 23 | + | #include "item.h" | |
| 24 | + | #include "complexitem.h" | |
| 25 | + | #include "town.h" | |
| 26 | + | #include "house.h" | |
| 27 | + | #include "map.h" | |
| 28 | + | #include "editor.h" | |
| 29 | + | #include "creature.h" | |
| 30 | + | ||
| 31 | + | #include "gui.h" | |
| 32 | + | #include "application.h" | |
| 33 | + | #include "old_properties_window.h" | |
| 34 | + | #include "container_properties_window.h" | |
| 35 | + | ||
| 36 | + | // ============================================================================ | |
| 37 | + | // Old Properties Window | |
| 38 | + | ||
| 39 | + | ||
| 40 | + | /* | |
| 41 | + | Current Task - Old Properties Window Improvements: | |
| 42 | + | ||
| 43 | + | 1. Main Goal: | |
| 44 | + | - Make the properties window non-blocking like replace_items_window.cpp | |
| 45 | + | - Allow brush selection while properties window is open | |
| 46 | + | - Fix crashes related to container and item handling (when we achieve it as a non main window) | |
| 47 | + | ||
| 48 | + | 2. Issues Being Addressed: | |
| 49 | + | - Access violation when checking canHoldText() on null items | |
| 50 | + | - Container handling crashes | |
| 51 | + | - Creature direction field not updating properly | |
| 52 | + | - Modal dialog blocking brush selection | |
| 53 | + | ||
| 54 | + | 3. Required Changes: | |
| 55 | + | - Add proper null checks for item/creature/spawn pointers | |
| 56 | + | - Improve container handling safety | |
| 57 | + | - Fix creature direction field updates | |
| 58 | + | - Make window non-modal while maintaining functionality all the bugs above are when we try to implement a non modal window | |
| 59 | + | - Ensure proper cleanup of resources | |
| 60 | + | ||
| 61 | + | 4. Implementation Strategy: | |
| 62 | + | - Add comprehensive null checks before accessing properties | |
| 63 | + | - Improve error handling for container operations | |
| 64 | + | - Fix creature direction field saving mechanism | |
| 65 | + | - Modify window behavior to allow background interaction | |
| 66 | + | - Maintain data integrity during property updates | |
| 67 | + | ||
| 68 | + | 5. Note: | |
| 69 | + | NEVER REMOVE ANY PREVIOUS FUNCTIONALITY UNLESS SPECIFICALLY REQUESTED! | |
| 70 | + | Keep all existing features while adding improvements. | |
| 71 | + | */ | |
| 72 | + | ||
| 73 | + | BEGIN_EVENT_TABLE(OldPropertiesWindow, wxDialog) | |
| 74 | + | EVT_SET_FOCUS(OldPropertiesWindow::OnFocusChange) | |
| 75 | + | EVT_BUTTON(wxID_OK, OldPropertiesWindow::OnClickOK) | |
| 76 | + | EVT_BUTTON(wxID_CANCEL, OldPropertiesWindow::OnClickCancel) | |
| 77 | + | END_EVENT_TABLE() | |
| 78 | + | ||
| 79 | + | static constexpr int OUTFIT_COLOR_MAX = 133; | |
| 80 | + | ||
| 81 | + | OldPropertiesWindow::OldPropertiesWindow(wxWindow* win_parent, const Map* map, const Tile* tile_parent, Item* item, wxPoint pos) : | |
| 82 | + | ObjectPropertiesWindowBase(win_parent, "Item Properties", map, tile_parent, item, pos), | |
| 83 | + | count_field(nullptr), | |
| 84 | + | direction_field(nullptr), | |
| 85 | + | action_id_field(nullptr), | |
| 86 | + | unique_id_field(nullptr), | |
| 87 | + | door_id_field(nullptr), | |
| 88 | + | tier_field(nullptr), | |
| 89 | + | depot_id_field(nullptr), | |
| 90 | + | splash_type_field(nullptr), | |
| 91 | + | text_field(nullptr), | |
| 92 | + | description_field(nullptr) { | |
| 93 | + | ASSERT(edit_item); | |
| 94 | + | ||
| 95 | + | wxSizer* topsizer = newd wxBoxSizer(wxVERTICAL); | |
| 96 | + | if (Container* container = dynamic_cast<Container*>(edit_item)) { | |
| 97 | + | // Container | |
| 98 | + | wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Container Properties"); | |
| 99 | + | ||
| 100 | + | wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10); | |
| 101 | + | subsizer->AddGrowableCol(1); | |
| 102 | + | ||
| 103 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "ID " + i2ws(item->getID()))); | |
| 104 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "\"" + wxstr(item->getName()) + "\"")); | |
| 105 | + | ||
| 106 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Action ID")); | |
| 107 | + | action_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getActionID()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 0xFFFF, edit_item->getActionID()); | |
| 108 | + | subsizer->Add(action_id_field, wxSizerFlags(1).Expand()); | |
| 109 | + | ||
| 110 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Unique ID")); | |
| 111 | + | unique_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getUniqueID()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, 0xFFFF, edit_item->getUniqueID()); | |
| 112 | + | subsizer->Add(unique_id_field, wxSizerFlags(1).Expand()); | |
| 113 | + | ||
| 114 | + | boxsizer->Add(subsizer, wxSizerFlags(0).Expand()); | |
| 115 | + | ||
| 116 | + | // Now we add the subitems! | |
| 117 | + | wxSizer* contents_sizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Contents"); | |
| 118 | + | ||
| 119 | + | bool use_large_sprites = g_settings.getBoolean(Config::USE_LARGE_CONTAINER_ICONS); | |
| 120 | + | wxSizer* horizontal_sizer = nullptr; | |
| 121 | + | const int additional_height_increment = (use_large_sprites ? 40 : 24); | |
| 122 | + | int additional_height = 0; | |
| 123 | + | ||
| 124 | + | int32_t maxColumns; | |
| 125 | + | if (use_large_sprites) { | |
| 126 | + | maxColumns = 6; | |
| 127 | + | } else { | |
| 128 | + | maxColumns = 12; | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | for (uint32_t index = 0; index < container->getVolume(); ++index) { | |
| 132 | + | if (!horizontal_sizer) { | |
| 133 | + | horizontal_sizer = newd wxBoxSizer(wxHORIZONTAL); | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | Item* item = container->getItem(index); | |
| 137 | + | ContainerItemButton* containerItemButton = newd ContainerItemButton(this, use_large_sprites, index, map, item); | |
| 138 | + | ||
| 139 | + | container_items.push_back(containerItemButton); | |
| 140 | + | horizontal_sizer->Add(containerItemButton); | |
| 141 | + | ||
| 142 | + | if (((index + 1) % maxColumns) == 0) { | |
| 143 | + | contents_sizer->Add(horizontal_sizer); | |
| 144 | + | horizontal_sizer = nullptr; | |
| 145 | + | additional_height += additional_height_increment; | |
| 146 | + | } | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | if (horizontal_sizer != nullptr) { | |
| 150 | + | contents_sizer->Add(horizontal_sizer); | |
| 151 | + | additional_height += additional_height_increment; | |
| 152 | + | } | |
| 153 | + | ||
| 154 | + | boxsizer->Add(contents_sizer, wxSizerFlags(2).Expand()); | |
| 155 | + | ||
| 156 | + | topsizer->Add(boxsizer, wxSizerFlags(0).Expand().Border(wxALL, 20)); | |
| 157 | + | ||
| 158 | + | // SetSize(260, 150 + additional_height); | |
| 159 | + | } else if (edit_item->canHoldText() || edit_item->canHoldDescription()) { | |
| 160 | + | // Book | |
| 161 | + | wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Writeable Properties"); | |
| 162 | + | ||
| 163 | + | wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10); | |
| 164 | + | subsizer->AddGrowableCol(1); | |
| 165 | + | ||
| 166 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "ID " + i2ws(item->getID()))); | |
| 167 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "\"" + wxstr(item->getName()) + "\"")); | |
| 168 | + | ||
| 169 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Action ID")); | |
| 170 | + | action_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getActionID()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 0xFFFF, edit_item->getActionID()); | |
| 171 | + | action_id_field->SetSelection(-1, -1); | |
| 172 | + | subsizer->Add(action_id_field, wxSizerFlags(1).Expand()); | |
| 173 | + | ||
| 174 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Unique ID")); | |
| 175 | + | unique_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getUniqueID()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, 0xFFFF, edit_item->getUniqueID()); | |
| 176 | + | subsizer->Add(unique_id_field, wxSizerFlags(1).Expand()); | |
| 177 | + | ||
| 178 | + | boxsizer->Add(subsizer, wxSizerFlags(1).Expand()); | |
| 179 | + | ||
| 180 | + | wxSizer* textsizer = newd wxBoxSizer(wxVERTICAL); | |
| 181 | + | textsizer->Add(newd wxStaticText(this, wxID_ANY, "Text"), wxSizerFlags(1).Center()); | |
| 182 | + | text_field = newd wxTextCtrl(this, wxID_ANY, wxstr(item->getText()), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE); | |
| 183 | + | textsizer->Add(text_field, wxSizerFlags(7).Expand()); | |
| 184 | + | ||
| 185 | + | boxsizer->Add(textsizer, wxSizerFlags(2).Expand()); | |
| 186 | + | ||
| 187 | + | topsizer->Add(boxsizer, wxSizerFlags(0).Expand().Border(wxALL, 20)); | |
| 188 | + | ||
| 189 | + | // SetSize(220, 310); | |
| 190 | + | } else if (edit_item->isSplash() || edit_item->isFluidContainer()) { | |
| 191 | + | // Splash | |
| 192 | + | wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Splash Properties"); | |
| 193 | + | ||
| 194 | + | wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10); | |
| 195 | + | subsizer->AddGrowableCol(1); | |
| 196 | + | ||
| 197 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "ID " + i2ws(item->getID()))); | |
| 198 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "\"" + wxstr(item->getName()) + "\"")); | |
| 199 | + | ||
| 200 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Type")); | |
| 201 | + | ||
| 202 | + | // Splash types | |
| 203 | + | splash_type_field = newd wxChoice(this, wxID_ANY); | |
| 204 | + | if (edit_item->isFluidContainer()) { | |
| 205 | + | splash_type_field->Append(wxstr(Item::LiquidID2Name(LIQUID_NONE)), newd int32_t(LIQUID_NONE)); | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | for (SplashType splashType = LIQUID_FIRST; splashType != LIQUID_LAST; ++splashType) { | |
| 209 | + | splash_type_field->Append(wxstr(Item::LiquidID2Name(splashType)), newd int32_t(splashType)); | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | if (item->getSubtype()) { | |
| 213 | + | const std::string& what = Item::LiquidID2Name(item->getSubtype()); | |
| 214 | + | if (what == "Unknown") { | |
| 215 | + | splash_type_field->Append(wxstr(Item::LiquidID2Name(LIQUID_NONE)), newd int32_t(LIQUID_NONE)); | |
| 216 | + | } | |
| 217 | + | splash_type_field->SetStringSelection(wxstr(what)); | |
| 218 | + | } else { | |
| 219 | + | splash_type_field->SetSelection(0); | |
| 220 | + | } | |
| 221 | + | ||
| 222 | + | subsizer->Add(splash_type_field, wxSizerFlags(1).Expand()); | |
| 223 | + | ||
| 224 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Action ID")); | |
| 225 | + | action_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getActionID()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 0xFFFF, edit_item->getActionID()); | |
| 226 | + | subsizer->Add(action_id_field, wxSizerFlags(1).Expand()); | |
| 227 | + | ||
| 228 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Unique ID")); | |
| 229 | + | unique_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getUniqueID()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, 0xFFFF, edit_item->getUniqueID()); | |
| 230 | + | subsizer->Add(unique_id_field, wxSizerFlags(1).Expand()); | |
| 231 | + | ||
| 232 | + | boxsizer->Add(subsizer, wxSizerFlags(1).Expand()); | |
| 233 | + | ||
| 234 | + | topsizer->Add(boxsizer, wxSizerFlags(0).Expand().Border(wxALL, 20)); | |
| 235 | + | ||
| 236 | + | // SetSize(220, 190); | |
| 237 | + | } else if (Depot* depot = dynamic_cast<Depot*>(edit_item)) { | |
| 238 | + | // Depot | |
| 239 | + | wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Depot Properties"); | |
| 240 | + | wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10); | |
| 241 | + | ||
| 242 | + | subsizer->AddGrowableCol(1); | |
| 243 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "ID " + i2ws(item->getID()))); | |
| 244 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "\"" + wxstr(item->getName()) + "\"")); | |
| 245 | + | ||
| 246 | + | const Towns& towns = map->towns; | |
| 247 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Depot ID")); | |
| 248 | + | depot_id_field = newd wxChoice(this, wxID_ANY); | |
| 249 | + | int to_select_index = 0; | |
| 250 | + | if (towns.count() > 0) { | |
| 251 | + | bool found = false; | |
| 252 | + | for (TownMap::const_iterator town_iter = towns.begin(); | |
| 253 | + | town_iter != towns.end(); | |
| 254 | + | ++town_iter) { | |
| 255 | + | if (town_iter->second->getID() == depot->getDepotID()) { | |
| 256 | + | found = true; | |
| 257 | + | } | |
| 258 | + | depot_id_field->Append(wxstr(town_iter->second->getName()), newd int(town_iter->second->getID())); | |
| 259 | + | if (!found) { | |
| 260 | + | ++to_select_index; | |
| 261 | + | } | |
| 262 | + | } | |
| 263 | + | if (!found) { | |
| 264 | + | if (depot->getDepotID() != 0) { | |
| 265 | + | depot_id_field->Append("Undefined Town (id:" + i2ws(depot->getDepotID()) + ")", newd int(depot->getDepotID())); | |
| 266 | + | } | |
| 267 | + | } | |
| 268 | + | } | |
| 269 | + | depot_id_field->Append("No Town", newd int(0)); | |
| 270 | + | if (depot->getDepotID() == 0) { | |
| 271 | + | to_select_index = depot_id_field->GetCount() - 1; | |
| 272 | + | } | |
| 273 | + | depot_id_field->SetSelection(to_select_index); | |
| 274 | + | ||
| 275 | + | subsizer->Add(depot_id_field, wxSizerFlags(1).Expand()); | |
| 276 | + | ||
| 277 | + | boxsizer->Add(subsizer, wxSizerFlags(5).Expand()); | |
| 278 | + | topsizer->Add(boxsizer, wxSizerFlags(0).Expand().Border(wxALL, 20)); | |
| 279 | + | // SetSize(220, 140); | |
| 280 | + | } else { | |
| 281 | + | // Normal item | |
| 282 | + | Door* door = dynamic_cast<Door*>(edit_item); | |
| 283 | + | Teleport* teleport = dynamic_cast<Teleport*>(edit_item); | |
| 284 | + | Podium* podium = dynamic_cast<Podium*>(edit_item); | |
| 285 | + | ||
| 286 | + | wxString description; | |
| 287 | + | if (door) { | |
| 288 | + | ASSERT(tile_parent); | |
| 289 | + | description = "Door Properties"; | |
| 290 | + | } else if (teleport) { | |
| 291 | + | description = "Teleport Properties"; | |
| 292 | + | } else if (podium) { | |
| 293 | + | description = "Podium Properties"; | |
| 294 | + | } else { | |
| 295 | + | description = "Item Properties"; | |
| 296 | + | } | |
| 297 | + | ||
| 298 | + | wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, description); | |
| 299 | + | ||
| 300 | + | // unused(?) | |
| 301 | + | /* | |
| 302 | + | int num_items = 4; | |
| 303 | + | //if(item->canHoldDescription()) num_items += 1; | |
| 304 | + | if(door) num_items += 1; | |
| 305 | + | if(teleport) num_items += 1; | |
| 306 | + | */ | |
| 307 | + | ||
| 308 | + | wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10); | |
| 309 | + | subsizer->AddGrowableCol(1); | |
| 310 | + | ||
| 311 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "ID " + i2ws(item->getID()))); | |
| 312 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "\"" + wxstr(item->getName()) + "\"")); | |
| 313 | + | ||
| 314 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, (item->isCharged() ? "Charges" : "Count"))); | |
| 315 | + | int max_count = 100; | |
| 316 | + | if (item->isClientCharged()) { | |
| 317 | + | max_count = 250; | |
| 318 | + | } | |
| 319 | + | if (item->isExtraCharged()) { | |
| 320 | + | max_count = 65500; | |
| 321 | + | } | |
| 322 | + | count_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getCount()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, max_count, edit_item->getCount()); | |
| 323 | + | if (!item->isStackable() && !item->isCharged()) { | |
| 324 | + | count_field->Enable(false); | |
| 325 | + | } | |
| 326 | + | subsizer->Add(count_field, wxSizerFlags(1).Expand()); | |
| 327 | + | ||
| 328 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Action ID")); | |
| 329 | + | action_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getActionID()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 0xFFFF, edit_item->getActionID()); | |
| 330 | + | subsizer->Add(action_id_field, wxSizerFlags(1).Expand()); | |
| 331 | + | ||
| 332 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Unique ID")); | |
| 333 | + | unique_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getUniqueID()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, 0xFFFF, edit_item->getUniqueID()); | |
| 334 | + | subsizer->Add(unique_id_field, wxSizerFlags(1).Expand()); | |
| 335 | + | ||
| 336 | + | // item classification (12.81+) | |
| 337 | + | if (g_items.MajorVersion >= 3 && g_items.MinorVersion >= 60 && (edit_item->getClassification() > 0 || edit_item->isWeapon() || edit_item->isWearableEquipment())) { | |
| 338 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Classification")); | |
| 339 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, i2ws(item->getClassification()))); | |
| 340 | + | ||
| 341 | + | // item iter | |
| 342 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Tier")); | |
| 343 | + | tier_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_item->getTier()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, 0xFF, edit_item->getTier()); | |
| 344 | + | subsizer->Add(tier_field, wxSizerFlags(1).Expand()); | |
| 345 | + | } | |
| 346 | + | ||
| 347 | + | /* | |
| 348 | + | if(item->canHoldDescription()) { | |
| 349 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Description")); | |
| 350 | + | description_field = newd wxTextCtrl(this, wxID_ANY, edit_item->getText(), wxDefaultPosition, wxSize(-1, 20)); | |
| 351 | + | subsizer->Add(description_field, wxSizerFlags(1).Expand()); | |
| 352 | + | } | |
| 353 | + | */ | |
| 354 | + | ||
| 355 | + | if (door) { | |
| 356 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Door ID")); | |
| 357 | + | door_id_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(door->getDoorID()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, 0xFF, door->getDoorID()); | |
| 358 | + | if (!edit_tile || !edit_tile->isHouseTile() || !door->isRealDoor()) { | |
| 359 | + | door_id_field->Disable(); | |
| 360 | + | } | |
| 361 | + | subsizer->Add(door_id_field, wxSizerFlags(1).Expand()); | |
| 362 | + | } | |
| 363 | + | ||
| 364 | + | if (teleport) { | |
| 365 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Destination")); | |
| 366 | + | ||
| 367 | + | wxSizer* possizer = newd wxBoxSizer(wxHORIZONTAL); | |
| 368 | + | x_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(teleport->getX()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, map->getWidth(), teleport->getX()); | |
| 369 | + | x_field->Bind(wxEVT_CHAR, &OldPropertiesWindow::OnChar, this); | |
| 370 | + | possizer->Add(x_field, wxSizerFlags(3).Expand()); | |
| 371 | + | y_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(teleport->getY()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, map->getHeight(), teleport->getY()); | |
| 372 | + | y_field->Bind(wxEVT_CHAR, &OldPropertiesWindow::OnChar, this); | |
| 373 | + | possizer->Add(y_field, wxSizerFlags(3).Expand()); | |
| 374 | + | z_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(teleport->getZ()), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, MAP_MAX_LAYER, teleport->getZ()); | |
| 375 | + | z_field->Bind(wxEVT_CHAR, &OldPropertiesWindow::OnChar, this); | |
| 376 | + | possizer->Add(z_field, wxSizerFlags(2).Expand()); | |
| 377 | + | ||
| 378 | + | subsizer->Add(possizer, wxSizerFlags(1).Expand()); | |
| 379 | + | } | |
| 380 | + | ||
| 381 | + | if (podium) { | |
| 382 | + | // direction | |
| 383 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Direction")); | |
| 384 | + | direction_field = newd wxChoice(this, wxID_ANY); | |
| 385 | + | ||
| 386 | + | for (Direction dir = DIRECTION_FIRST; dir <= DIRECTION_LAST; ++dir) { | |
| 387 | + | direction_field->Append(wxstr(Creature::DirID2Name(dir)), newd int32_t(dir)); | |
| 388 | + | } | |
| 389 | + | direction_field->SetSelection(static_cast<Direction>(podium->getDirection())); | |
| 390 | + | subsizer->Add(direction_field, wxSizerFlags(1).Expand()); | |
| 391 | + | ||
| 392 | + | // checkboxes | |
| 393 | + | show_outfit = newd wxCheckBox(this, wxID_ANY, "Show outfit"); | |
| 394 | + | show_outfit->SetValue(podium->hasShowOutfit()); | |
| 395 | + | show_outfit->SetToolTip("Display outfit on the podium."); | |
| 396 | + | subsizer->Add(show_outfit, 0, wxLEFT | wxTOP, 5); | |
| 397 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "")); // filler for checkboxes | |
| 398 | + | ||
| 399 | + | show_mount = newd wxCheckBox(this, wxID_ANY, "Show mount"); | |
| 400 | + | show_mount->SetValue(podium->hasShowMount()); | |
| 401 | + | show_mount->SetToolTip("Display mount on the podium."); | |
| 402 | + | subsizer->Add(show_mount, 0, wxLEFT | wxTOP, 5); | |
| 403 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "")); | |
| 404 | + | ||
| 405 | + | show_platform = newd wxCheckBox(this, wxID_ANY, "Show platform"); | |
| 406 | + | show_platform->SetValue(podium->hasShowPlatform()); | |
| 407 | + | show_platform->SetToolTip("Display the podium platform."); | |
| 408 | + | subsizer->Add(show_platform, 0, wxLEFT | wxTOP, 5); | |
| 409 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "")); | |
| 410 | + | ||
| 411 | + | // outfit container | |
| 412 | + | wxFlexGridSizer* outfitContainer = newd wxFlexGridSizer(2, 10, 10); | |
| 413 | + | const Outfit& outfit = podium->getOutfit(); | |
| 414 | + | ||
| 415 | + | outfitContainer->Add(newd wxStaticText(this, wxID_ANY, "Outfit")); | |
| 416 | + | outfitContainer->Add(newd wxStaticText(this, wxID_ANY, "")); | |
| 417 | + | ||
| 418 | + | outfitContainer->Add(newd wxStaticText(this, wxID_ANY, "LookType")); | |
| 419 | + | look_type = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookType), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, std::numeric_limits<uint16_t>::max(), outfit.lookType); | |
| 420 | + | outfitContainer->Add(look_type, wxSizerFlags(3).Expand()); | |
| 421 | + | ||
| 422 | + | outfitContainer->Add(newd wxStaticText(this, wxID_ANY, "Head")); | |
| 423 | + | look_head = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookHead), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, OUTFIT_COLOR_MAX, outfit.lookHead); | |
| 424 | + | outfitContainer->Add(look_head, wxSizerFlags(3).Expand()); | |
| 425 | + | ||
| 426 | + | outfitContainer->Add(newd wxStaticText(this, wxID_ANY, "Body")); | |
| 427 | + | look_body = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookBody), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, OUTFIT_COLOR_MAX, outfit.lookBody); | |
| 428 | + | outfitContainer->Add(look_body, wxSizerFlags(3).Expand()); | |
| 429 | + | ||
| 430 | + | outfitContainer->Add(newd wxStaticText(this, wxID_ANY, "Legs")); | |
| 431 | + | look_legs = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookLegs), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, OUTFIT_COLOR_MAX, outfit.lookLegs); | |
| 432 | + | outfitContainer->Add(look_legs, wxSizerFlags(3).Expand()); | |
| 433 | + | ||
| 434 | + | outfitContainer->Add(newd wxStaticText(this, wxID_ANY, "Feet")); | |
| 435 | + | look_feet = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookFeet), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, OUTFIT_COLOR_MAX, outfit.lookFeet); | |
| 436 | + | outfitContainer->Add(look_feet, wxSizerFlags(3).Expand()); | |
| 437 | + | ||
| 438 | + | outfitContainer->Add(newd wxStaticText(this, wxID_ANY, "Addons")); | |
| 439 | + | look_addon = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookAddon), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, 3, outfit.lookAddon); | |
| 440 | + | outfitContainer->Add(look_addon, wxSizerFlags(3).Expand()); | |
| 441 | + | ||
| 442 | + | // mount container | |
| 443 | + | wxFlexGridSizer* mountContainer = newd wxFlexGridSizer(2, 10, 10); | |
| 444 | + | ||
| 445 | + | mountContainer->Add(newd wxStaticText(this, wxID_ANY, "Mount")); | |
| 446 | + | mountContainer->Add(newd wxStaticText(this, wxID_ANY, "")); | |
| 447 | + | ||
| 448 | + | mountContainer->Add(newd wxStaticText(this, wxID_ANY, "LookMount")); | |
| 449 | + | look_mount = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookMount), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, std::numeric_limits<uint16_t>::max(), outfit.lookMount); | |
| 450 | + | mountContainer->Add(look_mount, wxSizerFlags(3).Expand()); | |
| 451 | + | ||
| 452 | + | mountContainer->Add(newd wxStaticText(this, wxID_ANY, "Head")); | |
| 453 | + | look_mounthead = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookMountHead), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, OUTFIT_COLOR_MAX, outfit.lookMountHead); | |
| 454 | + | mountContainer->Add(look_mounthead, wxSizerFlags(3).Expand()); | |
| 455 | + | ||
| 456 | + | mountContainer->Add(newd wxStaticText(this, wxID_ANY, "Body")); | |
| 457 | + | look_mountbody = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookMountBody), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, OUTFIT_COLOR_MAX, outfit.lookMountBody); | |
| 458 | + | mountContainer->Add(look_mountbody, wxSizerFlags(3).Expand()); | |
| 459 | + | ||
| 460 | + | mountContainer->Add(newd wxStaticText(this, wxID_ANY, "Legs")); | |
| 461 | + | look_mountlegs = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookMountLegs), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, OUTFIT_COLOR_MAX, outfit.lookMountLegs); | |
| 462 | + | mountContainer->Add(look_mountlegs, wxSizerFlags(3).Expand()); | |
| 463 | + | ||
| 464 | + | mountContainer->Add(newd wxStaticText(this, wxID_ANY, "Feet")); | |
| 465 | + | look_mountfeet = newd wxSpinCtrl(this, wxID_ANY, i2ws(outfit.lookMountFeet), wxDefaultPosition, wxSize(-1, 20), wxSP_ARROW_KEYS, 0, OUTFIT_COLOR_MAX, outfit.lookMountFeet); | |
| 466 | + | mountContainer->Add(look_mountfeet, wxSizerFlags(3).Expand()); | |
| 467 | + | ||
| 468 | + | wxFlexGridSizer* propertiesContainer = newd wxFlexGridSizer(3, 10, 10); | |
| 469 | + | propertiesContainer->Add(subsizer, wxSizerFlags(1).Expand()); | |
| 470 | + | propertiesContainer->Add(outfitContainer, wxSizerFlags(1).Expand()); | |
| 471 | + | propertiesContainer->Add(mountContainer, wxSizerFlags(1).Expand()); | |
| 472 | + | boxsizer->Add(propertiesContainer, wxSizerFlags(1).Expand()); | |
| 473 | + | } else { | |
| 474 | + | boxsizer->Add(subsizer, wxSizerFlags(1).Expand()); | |
| 475 | + | } | |
| 476 | + | ||
| 477 | + | topsizer->Add(boxsizer, wxSizerFlags(0).Expand().Border(wxLEFT | wxRIGHT, 20)); | |
| 478 | + | } | |
| 479 | + | ||
| 480 | + | // Others attributes | |
| 481 | + | // should have an option to turn it off | |
| 482 | + | /* | |
| 483 | + | const ItemType& type = g_items.getItemType(edit_item->getID()); | |
| 484 | + | wxStaticBoxSizer* others_sizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Others"); | |
| 485 | + | wxFlexGridSizer* others_subsizer = newd wxFlexGridSizer(2, 5, 10); | |
| 486 | + | others_subsizer->AddGrowableCol(1); | |
| 487 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, "Stackable")); | |
| 488 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, b2yn(type.stackable))); | |
| 489 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, "Movable")); | |
| 490 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, b2yn(type.moveable))); | |
| 491 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, "Pickupable")); | |
| 492 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, b2yn(type.pickupable))); | |
| 493 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, "Hangable")); | |
| 494 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, b2yn(type.isHangable))); | |
| 495 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, "Block Missiles")); | |
| 496 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, b2yn(type.blockMissiles))); | |
| 497 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, "Block Pathfinder")); | |
| 498 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, b2yn(type.blockPathfinder))); | |
| 499 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, "Has Elevation")); | |
| 500 | + | others_subsizer->Add(newd wxStaticText(this, wxID_ANY, b2yn(type.hasElevation))); | |
| 501 | + | others_sizer->Add(others_subsizer, wxSizerFlags(1).Expand()); | |
| 502 | + | topsizer->Add(others_sizer, wxSizerFlags(0).Expand().Border(wxLEFT | wxRIGHT | wxBOTTOM, 20)); | |
| 503 | + | */ | |
| 504 | + | ||
| 505 | + | wxSizer* subsizer = newd wxBoxSizer(wxHORIZONTAL); | |
| 506 | + | subsizer->Add(newd wxButton(this, wxID_OK, "OK"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10)); | |
| 507 | + | subsizer->Add(newd wxButton(this, wxID_CANCEL, "Cancel"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10)); | |
| 508 | + | topsizer->Add(subsizer, wxSizerFlags(0).Center().Border(wxLEFT | wxRIGHT, 20)); | |
| 509 | + | ||
| 510 | + | SetSizerAndFit(topsizer); | |
| 511 | + | Centre(wxBOTH); | |
| 512 | + | } | |
| 513 | + | ||
| 514 | + | OldPropertiesWindow::OldPropertiesWindow(wxWindow* win_parent, const Map* map, const Tile* tile_parent, Creature* creature, wxPoint pos) : | |
| 515 | + | ObjectPropertiesWindowBase(win_parent, "Creature Properties", map, tile_parent, creature, pos), | |
| 516 | + | count_field(nullptr), | |
| 517 | + | direction_field(nullptr), | |
| 518 | + | action_id_field(nullptr), | |
| 519 | + | unique_id_field(nullptr), | |
| 520 | + | door_id_field(nullptr), | |
| 521 | + | tier_field(nullptr), | |
| 522 | + | depot_id_field(nullptr), | |
| 523 | + | splash_type_field(nullptr), | |
| 524 | + | text_field(nullptr), | |
| 525 | + | description_field(nullptr) { | |
| 526 | + | ASSERT(edit_creature); | |
| 527 | + | ||
| 528 | + | wxSizer* topsizer = newd wxBoxSizer(wxVERTICAL); | |
| 529 | + | wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Creature Properties"); | |
| 530 | + | ||
| 531 | + | wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10); | |
| 532 | + | subsizer->AddGrowableCol(1); | |
| 533 | + | ||
| 534 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Creature ")); | |
| 535 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "\"" + wxstr(edit_creature->getName()) + "\""), wxSizerFlags(1).Expand()); | |
| 536 | + | ||
| 537 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Spawn interval")); | |
| 538 | + | count_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_creature->getSpawnTime()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 10, 86400, edit_creature->getSpawnTime()); | |
| 539 | + | // count_field->SetSelection(-1, -1); | |
| 540 | + | subsizer->Add(count_field, wxSizerFlags(1).Expand()); | |
| 541 | + | ||
| 542 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Direction")); | |
| 543 | + | direction_field = newd wxChoice(this, wxID_ANY); | |
| 544 | + | ||
| 545 | + | for (Direction dir = DIRECTION_FIRST; dir <= DIRECTION_LAST; ++dir) { | |
| 546 | + | direction_field->Append(wxstr(Creature::DirID2Name(dir)), newd int32_t(dir)); | |
| 547 | + | } | |
| 548 | + | direction_field->SetSelection(edit_creature->getDirection()); | |
| 549 | + | subsizer->Add(direction_field, wxSizerFlags(1).Expand()); | |
| 550 | + | ||
| 551 | + | boxsizer->Add(subsizer, wxSizerFlags(1).Expand()); | |
| 552 | + | ||
| 553 | + | topsizer->Add(boxsizer, wxSizerFlags(3).Expand().Border(wxALL, 20)); | |
| 554 | + | // SetSize(220, 0); | |
| 555 | + | ||
| 556 | + | wxSizer* std_sizer = newd wxBoxSizer(wxHORIZONTAL); | |
| 557 | + | std_sizer->Add(newd wxButton(this, wxID_OK, "OK"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10)); | |
| 558 | + | std_sizer->Add(newd wxButton(this, wxID_CANCEL, "Cancel"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10)); | |
| 559 | + | topsizer->Add(std_sizer, wxSizerFlags(0).Center().Border(wxLEFT | wxRIGHT, 20)); | |
| 560 | + | ||
| 561 | + | SetSizerAndFit(topsizer); | |
| 562 | + | Centre(wxBOTH); | |
| 563 | + | } | |
| 564 | + | ||
| 565 | + | OldPropertiesWindow::OldPropertiesWindow(wxWindow* win_parent, const Map* map, const Tile* tile_parent, Spawn* spawn, wxPoint pos) : | |
| 566 | + | ObjectPropertiesWindowBase(win_parent, "Spawn Properties", map, tile_parent, spawn, pos), | |
| 567 | + | count_field(nullptr), | |
| 568 | + | direction_field(nullptr), | |
| 569 | + | action_id_field(nullptr), | |
| 570 | + | unique_id_field(nullptr), | |
| 571 | + | door_id_field(nullptr), | |
| 572 | + | tier_field(nullptr), | |
| 573 | + | depot_id_field(nullptr), | |
| 574 | + | splash_type_field(nullptr), | |
| 575 | + | text_field(nullptr), | |
| 576 | + | description_field(nullptr) { | |
| 577 | + | ASSERT(edit_spawn); | |
| 578 | + | ||
| 579 | + | wxSizer* topsizer = newd wxBoxSizer(wxVERTICAL); | |
| 580 | + | wxSizer* boxsizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Spawn Properties"); | |
| 581 | + | ||
| 582 | + | // if(item->canHoldDescription()) num_items += 1; | |
| 583 | + | ||
| 584 | + | wxFlexGridSizer* subsizer = newd wxFlexGridSizer(2, 10, 10); | |
| 585 | + | subsizer->AddGrowableCol(1); | |
| 586 | + | ||
| 587 | + | subsizer->Add(newd wxStaticText(this, wxID_ANY, "Spawn size")); | |
| 588 | + | count_field = newd wxSpinCtrl(this, wxID_ANY, i2ws(edit_spawn->getSize()), wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, g_settings.getInteger(Config::MAX_SPAWN_RADIUS), edit_spawn->getSize()); | |
| 589 | + | // count_field->SetSelection(-1, -1); | |
| 590 | + | subsizer->Add(count_field, wxSizerFlags(1).Expand()); | |
| 591 | + | ||
| 592 | + | boxsizer->Add(subsizer, wxSizerFlags(1).Expand()); | |
| 593 | + | ||
| 594 | + | topsizer->Add(boxsizer, wxSizerFlags(3).Expand().Border(wxALL, 20)); | |
| 595 | + | ||
| 596 | + | wxSizer* std_sizer = newd wxBoxSizer(wxHORIZONTAL); | |
| 597 | + | std_sizer->Add(newd wxButton(this, wxID_OK, "OK"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10)); | |
| 598 | + | std_sizer->Add(newd wxButton(this, wxID_CANCEL, "Cancel"), wxSizerFlags(1).Center().Border(wxTOP | wxBOTTOM, 10)); | |
| 599 | + | topsizer->Add(std_sizer, wxSizerFlags(0).Center().Border(wxLEFT | wxRIGHT, 20)); | |
| 600 | + | ||
| 601 | + | SetSizerAndFit(topsizer); | |
| 602 | + | Centre(wxBOTH); | |
| 603 | + | } | |
| 604 | + | ||
| 605 | + | OldPropertiesWindow::~OldPropertiesWindow() { | |
| 606 | + | // Warning: edit_item may no longer be valid, DONT USE IT! | |
| 607 | + | if (splash_type_field) { | |
| 608 | + | for (uint32_t i = 0; i < splash_type_field->GetCount(); ++i) { | |
| 609 | + | delete reinterpret_cast<int*>(splash_type_field->GetClientData(i)); | |
| 610 | + | } | |
| 611 | + | } | |
| 612 | + | if (direction_field) { | |
| 613 | + | for (uint32_t i = 0; i < direction_field->GetCount(); ++i) { | |
| 614 | + | delete reinterpret_cast<int*>(direction_field->GetClientData(i)); | |
| 615 | + | } | |
| 616 | + | } | |
| 617 | + | if (depot_id_field) { | |
| 618 | + | for (uint32_t i = 0; i < depot_id_field->GetCount(); ++i) { | |
| 619 | + | delete reinterpret_cast<int*>(depot_id_field->GetClientData(i)); | |
| 620 | + | } | |
| 621 | + | } | |
| 622 | + | } | |
| 623 | + | ||
| 624 | + | void OldPropertiesWindow::OnFocusChange(wxFocusEvent& event) { | |
| 625 | + | wxWindow* win = event.GetWindow(); | |
| 626 | + | if (wxSpinCtrl* spin = dynamic_cast<wxSpinCtrl*>(win)) { | |
| 627 | + | spin->SetSelection(-1, -1); | |
| 628 | + | } else if (wxTextCtrl* text = dynamic_cast<wxTextCtrl*>(win)) { | |
| 629 | + | text->SetSelection(-1, -1); | |
| 630 | + | } | |
| 631 | + | } | |
| 632 | + | ||
| 633 | + | void OldPropertiesWindow::OnChar(wxKeyEvent& evt) { | |
| 634 | + | if (evt.GetKeyCode() == WXK_CONTROL_V) { | |
| 635 | + | Position position; | |
| 636 | + | const Editor* const editor = g_gui.GetCurrentEditor(); | |
| 637 | + | if (posFromClipboard(position, editor->getMapWidth(), editor->getMapHeight())) { | |
| 638 | + | x_field->SetValue(position.x); | |
| 639 | + | y_field->SetValue(position.y); | |
| 640 | + | z_field->SetValue(position.z); | |
| 641 | + | return; | |
| 642 | + | } | |
| 643 | + | } | |
| 644 | + | ||
| 645 | + | evt.Skip(); | |
| 646 | + | } | |
| 647 | + | ||
| 648 | + | void OldPropertiesWindow::OnClickOK(wxCommandEvent& WXUNUSED(event)) { | |
| 649 | + | if (edit_item) { | |
| 650 | + | if (dynamic_cast<Container*>(edit_item)) { | |
| 651 | + | // Container | |
| 652 | + | int new_uid = unique_id_field->GetValue(); | |
| 653 | + | int new_aid = action_id_field->GetValue(); | |
| 654 | + | ||
| 655 | + | if ((new_uid < 1000 || new_uid > 0xFFFF) && new_uid != 0) { | |
| 656 | + | g_gui.PopupDialog(this, "Error", "Unique ID must be between 1000 and 65535.", wxOK); | |
| 657 | + | return; | |
| 658 | + | } | |
| 659 | + | if (/* there is no item with the same UID */ false) { | |
| 660 | + | g_gui.PopupDialog(this, "Error", "Unique ID must be unique, this UID is already taken.", wxOK); | |
| 661 | + | return; | |
| 662 | + | } | |
| 663 | + | if ((new_aid < 100 || new_aid > 0xFFFF) && new_aid != 0) { | |
| 664 | + | g_gui.PopupDialog(this, "Error", "Action ID must be between 100 and 65535.", wxOK); | |
| 665 | + | return; | |
| 666 | + | } | |
| 667 | + | ||
| 668 | + | edit_item->setUniqueID(new_uid); | |
| 669 | + | edit_item->setActionID(new_aid); | |
| 670 | + | } else if (edit_item->canHoldText() || edit_item->canHoldDescription()) { | |
| 671 | + | // Book | |
| 672 | + | int new_uid = unique_id_field->GetValue(); | |
| 673 | + | int new_aid = action_id_field->GetValue(); | |
| 674 | + | std::string text = nstr(text_field->GetValue()); | |
| 675 | + | ||
| 676 | + | if ((new_uid < 1000 || new_uid > 0xFFFF) && new_uid != 0) { | |
| 677 | + | g_gui.PopupDialog(this, "Error", "Unique ID must be between 1000 and 65535.", wxOK); | |
| 678 | + | return; | |
| 679 | + | } | |
| 680 | + | if (/* there is no item with the same UID */ false) { | |
| 681 | + | g_gui.PopupDialog(this, "Error", "Unique ID must be unique, this UID is already taken.", wxOK); | |
| 682 | + | return; | |
| 683 | + | } | |
| 684 | + | if ((new_aid < 100 || new_aid > 0xFFFF) && new_aid != 0) { | |
| 685 | + | g_gui.PopupDialog(this, "Error", "Action ID must be between 100 and 65535.", wxOK); | |
| 686 | + | return; | |
| 687 | + | } | |
| 688 | + | if (text.length() >= 0xFFFF) { | |
| 689 | + | g_gui.PopupDialog(this, "Error", "Text is longer than 65535 characters, this is not supported by OpenTibia. Reduce the length of the text.", wxOK); | |
| 690 | + | return; | |
| 691 | + | } | |
| 692 | + | if (edit_item->canHoldText() && text.length() > edit_item->getMaxWriteLength()) { | |
| 693 | + | int ret = g_gui.PopupDialog(this, "Error", "Text is longer than the maximum supported length of this book type, do you still want to change it?", wxYES | wxNO); | |
| 694 | + | if (ret != wxID_YES) { | |
| 695 | + | return; | |
| 696 | + | } | |
| 697 | + | } | |
| 698 | + | ||
| 699 | + | edit_item->setUniqueID(new_uid); | |
| 700 | + | edit_item->setActionID(new_aid); | |
| 701 | + | edit_item->setText(text); | |
| 702 | + | } else if (edit_item->isSplash() || edit_item->isFluidContainer()) { | |
| 703 | + | // Splash | |
| 704 | + | int new_uid = unique_id_field->GetValue(); | |
| 705 | + | int new_aid = action_id_field->GetValue(); | |
| 706 | + | int* new_type = reinterpret_cast<int*>(splash_type_field->GetClientData(splash_type_field->GetSelection())); | |
| 707 | + | ||
| 708 | + | if ((new_uid < 1000 || new_uid > 0xFFFF) && new_uid != 0) { | |
| 709 | + | g_gui.PopupDialog(this, "Error", "Unique ID must be between 1000 and 65535.", wxOK); | |
| 710 | + | return; | |
| 711 | + | } | |
| 712 | + | if (/* there is no item with the same UID */ false) { | |
| 713 | + | g_gui.PopupDialog(this, "Error", "Unique ID must be unique, this UID is already taken.", wxOK); | |
| 714 | + | return; | |
| 715 | + | } | |
| 716 | + | if ((new_aid < 100 || new_aid > 0xFFFF) && new_aid != 0) { | |
| 717 | + | g_gui.PopupDialog(this, "Error", "Action ID must be between 100 and 65535.", wxOK); | |
| 718 | + | return; | |
| 719 | + | } | |
| 720 | + | if (new_type) { | |
| 721 | + | edit_item->setSubtype(*new_type); | |
| 722 | + | } | |
| 723 | + | edit_item->setUniqueID(new_uid); | |
| 724 | + | edit_item->setActionID(new_aid); | |
| 725 | + | ||
| 726 | + | // Clean up client data | |
| 727 | + | } else if (Depot* depot = dynamic_cast<Depot*>(edit_item)) { | |
| 728 | + | // Depot | |
| 729 | + | int* new_depotid = reinterpret_cast<int*>(depot_id_field->GetClientData(depot_id_field->GetSelection())); | |
| 730 | + | ||
| 731 | + | depot->setDepotID(*new_depotid); | |
| 732 | + | } else { | |
| 733 | + | // Normal item | |
| 734 | + | Door* door = dynamic_cast<Door*>(edit_item); | |
| 735 | + | Teleport* teleport = dynamic_cast<Teleport*>(edit_item); | |
| 736 | + | Podium* podium = dynamic_cast<Podium*>(edit_item); | |
| 737 | + | ||
| 738 | + | int new_uid = unique_id_field->GetValue(); | |
| 739 | + | int new_aid = action_id_field->GetValue(); | |
| 740 | + | int new_count = count_field ? count_field->GetValue() : 1; | |
| 741 | + | int new_tier = tier_field ? tier_field->GetValue() : 0; | |
| 742 | + | ||
| 743 | + | std::string new_desc; | |
| 744 | + | if (edit_item->canHoldDescription() && description_field) { | |
| 745 | + | description_field->GetValue(); | |
| 746 | + | } | |
| 747 | + | Position new_dest; | |
| 748 | + | if (teleport) { | |
| 749 | + | new_dest = Position(x_field->GetValue(), y_field->GetValue(), z_field->GetValue()); | |
| 750 | + | } | |
| 751 | + | uint8_t new_door_id = 0; | |
| 752 | + | if (door) { | |
| 753 | + | new_door_id = door_id_field->GetValue(); | |
| 754 | + | } | |
| 755 | + | ||
| 756 | + | if ((new_uid < 1000 || new_uid > 0xFFFF) && new_uid != 0) { | |
| 757 | + | g_gui.PopupDialog(this, "Error", "Unique ID must be between 1000 and 65535.", wxOK); | |
| 758 | + | return; | |
| 759 | + | } | |
| 760 | + | if (/* there is no item with the same UID */ false) { | |
| 761 | + | g_gui.PopupDialog(this, "Error", "Unique ID must be unique, this UID is already taken.", wxOK); | |
| 762 | + | return; | |
| 763 | + | } | |
| 764 | + | if ((new_aid < 100 || new_aid > 0xFFFF) && new_aid != 0) { | |
| 765 | + | g_gui.PopupDialog(this, "Error", "Action ID must be between 100 and 65535.", wxOK); | |
| 766 | + | return; | |
| 767 | + | } | |
| 768 | + | if (new_tier < 0 || new_tier > 0xFF) { | |
| 769 | + | g_gui.PopupDialog(this, "Error", "Item tier must be between 0 and 255.", wxOK); | |
| 770 | + | return; | |
| 771 | + | } | |
| 772 | + | ||
| 773 | + | /* | |
| 774 | + | if(edit_item->canHoldDescription()) { | |
| 775 | + | if(new_desc.length() > 127) { | |
| 776 | + | g_gui.PopupDialog("Error", "Description must be shorter than 127 characters.", wxOK); | |
| 777 | + | return; | |
| 778 | + | } | |
| 779 | + | } | |
| 780 | + | */ | |
| 781 | + | ||
| 782 | + | if (door && g_settings.getInteger(Config::WARN_FOR_DUPLICATE_ID)) { | |
| 783 | + | if (edit_tile && edit_tile->isHouseTile()) { | |
| 784 | + | const House* house = edit_map->houses.getHouse(edit_tile->getHouseID()); | |
| 785 | + | if (house) { | |
| 786 | + | Position pos = house->getDoorPositionByID(new_door_id); | |
| 787 | + | if (pos == Position()) { | |
| 788 | + | // Do nothing | |
| 789 | + | } else if (pos != edit_tile->getPosition()) { | |
| 790 | + | int ret = g_gui.PopupDialog(this, "Warning", "This doorid conflicts with another one in this house, are you sure you want to continue?", wxYES | wxNO); | |
| 791 | + | if (ret == wxID_NO) { | |
| 792 | + | return; | |
| 793 | + | } | |
| 794 | + | } | |
| 795 | + | } | |
| 796 | + | } | |
| 797 | + | } | |
| 798 | + | ||
| 799 | + | if (teleport) { | |
| 800 | + | if (edit_map->getTile(new_dest) == nullptr || edit_map->getTile(new_dest)->isBlocking()) { | |
| 801 | + | int ret = g_gui.PopupDialog(this, "Warning", "This teleport leads nowhere, or to an invalid location. Do you want to change the destination?", wxYES | wxNO); | |
| 802 | + | if (ret == wxID_YES) { | |
| 803 | + | return; | |
| 804 | + | } | |
| 805 | + | } | |
| 806 | + | } | |
| 807 | + | ||
| 808 | + | Outfit& newOutfit = Outfit(); | |
| 809 | + | if (podium) { | |
| 810 | + | int newLookType = look_type->GetValue(); | |
| 811 | + | int newMount = look_mount->GetValue(); | |
| 812 | + | ||
| 813 | + | if (newLookType < 0 || newLookType > 0xFFFF || newMount < 0 || newMount > 0xFFFF) { | |
| 814 | + | g_gui.PopupDialog(this, "Error", "LookType and Mount must be between 0 and 65535.", wxOK); | |
| 815 | + | return; | |
| 816 | + | } | |
| 817 | + | ||
| 818 | + | int newHead = look_head->GetValue(); | |
| 819 | + | int newBody = look_body->GetValue(); | |
| 820 | + | int newLegs = look_legs->GetValue(); | |
| 821 | + | int newFeet = look_feet->GetValue(); | |
| 822 | + | int newMountHead = look_mounthead->GetValue(); | |
| 823 | + | int newMountBody = look_mountbody->GetValue(); | |
| 824 | + | int newMountLegs = look_mountlegs->GetValue(); | |
| 825 | + | int newMountFeet = look_mountfeet->GetValue(); | |
| 826 | + | ||
| 827 | + | if (newHead < 0 || newHead > OUTFIT_COLOR_MAX || newBody < 0 || newBody > OUTFIT_COLOR_MAX || newLegs < 0 || newLegs > OUTFIT_COLOR_MAX || newFeet < 0 || newFeet > OUTFIT_COLOR_MAX || newMountHead < 0 || newMountHead > OUTFIT_COLOR_MAX || newMountBody < 0 || newMountBody > OUTFIT_COLOR_MAX || newMountLegs < 0 || newMountLegs > OUTFIT_COLOR_MAX || newMountFeet < 0 || newMountFeet > OUTFIT_COLOR_MAX) { | |
| 828 | + | wxString response = "Outfit and mount colors must be between 0 and "; | |
| 829 | + | response << i2ws(OUTFIT_COLOR_MAX) << "."; | |
| 830 | + | g_gui.PopupDialog(this, "Error", response, wxOK); | |
| 831 | + | return; | |
| 832 | + | } | |
| 833 | + | ||
| 834 | + | int newAddon = look_addon->GetValue(); | |
| 835 | + | if (newAddon < 0 || newAddon > 3) { | |
| 836 | + | g_gui.PopupDialog(this, "Error", "Addons value must be between 0 and 3.", wxOK); | |
| 837 | + | return; | |
| 838 | + | } | |
| 839 | + | ||
| 840 | + | newOutfit.lookType = newLookType; | |
| 841 | + | newOutfit.lookHead = newHead; | |
| 842 | + | newOutfit.lookBody = newBody; | |
| 843 | + | newOutfit.lookLegs = newLegs; | |
| 844 | + | newOutfit.lookFeet = newFeet; | |
| 845 | + | newOutfit.lookAddon = newAddon; | |
| 846 | + | newOutfit.lookMount = newMount; | |
| 847 | + | newOutfit.lookMountHead = newMountHead; | |
| 848 | + | newOutfit.lookMountBody = newMountBody; | |
| 849 | + | newOutfit.lookMountLegs = newMountLegs; | |
| 850 | + | newOutfit.lookMountFeet = newMountFeet; | |
| 851 | + | } | |
| 852 | + | ||
| 853 | + | // Done validating, set the values. | |
| 854 | + | if (edit_item->canHoldDescription()) { | |
| 855 | + | edit_item->setText(new_desc); | |
| 856 | + | } | |
| 857 | + | if (edit_item->isStackable() || edit_item->isCharged()) { | |
| 858 | + | edit_item->setSubtype(new_count); | |
| 859 | + | } | |
| 860 | + | if (door) { | |
| 861 | + | door->setDoorID(new_door_id); | |
| 862 | + | } | |
| 863 | + | if (teleport) { | |
| 864 | + | teleport->setDestination(new_dest); | |
| 865 | + | } | |
| 866 | + | if (podium) { | |
| 867 | + | podium->setShowOutfit(show_outfit->GetValue()); | |
| 868 | + | podium->setShowMount(show_mount->GetValue()); | |
| 869 | + | podium->setShowPlatform(show_platform->GetValue()); | |
| 870 | + | ||
| 871 | + | int* new_dir = reinterpret_cast<int*>(direction_field->GetClientData(direction_field->GetSelection())); | |
| 872 | + | if (new_dir) { | |
| 873 | + | podium->setDirection((Direction)*new_dir); | |
| 874 | + | } | |
| 875 | + | ||
| 876 | + | podium->setOutfit(newOutfit); | |
| 877 | + | } | |
| 878 | + | edit_item->setUniqueID(new_uid); | |
| 879 | + | edit_item->setActionID(new_aid); | |
| 880 | + | edit_item->setTier(new_tier); | |
| 881 | + | } | |
| 882 | + | } else if (edit_creature) { | |
| 883 | + | int new_spawntime = count_field->GetValue(); | |
| 884 | + | edit_creature->setSpawnTime(new_spawntime); | |
| 885 | + | ||
| 886 | + | int* new_dir = reinterpret_cast<int*>(direction_field->GetClientData( | |
| 887 | + | direction_field->GetSelection() | |
| 888 | + | )); | |
| 889 | + | ||
| 890 | + | if (new_dir) { | |
| 891 | + | edit_creature->setDirection((Direction)*new_dir); | |
| 892 | + | } | |
| 893 | + | } else if (edit_spawn) { | |
| 894 | + | int new_spawnsize = count_field->GetValue(); | |
| 895 | + | edit_spawn->setSize(new_spawnsize); | |
| 896 | + | } | |
| 897 | + | EndModal(1); | |
| 898 | + | } | |
| 899 | + | ||
| 900 | + | void OldPropertiesWindow::OnClickCancel(wxCommandEvent& WXUNUSED(event)) { | |
| 901 | + | // Just close this window | |
| 902 | + | EndModal(0); | |
| 903 | + | } | |
| 904 | + | ||
| 905 | + | void OldPropertiesWindow::Update() { | |
| 906 | + | Container* container = dynamic_cast<Container*>(edit_item); | |
| 907 | + | if (container) { | |
| 908 | + | for (uint32_t i = 0; i < container->getVolume(); ++i) { | |
| 909 | + | container_items[i]->setItem(container->getItem(i)); | |
| 910 | + | } | |
| 911 | + | } | |
| 912 | + | wxDialog::Update(); | |
| 913 | + | } |
| @@ -0,0 +1,107 @@ | |||
| 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_OLD_PROPERTIES_WINDOW_H_ | |
| 19 | + | #define RME_OLD_PROPERTIES_WINDOW_H_ | |
| 20 | + | ||
| 21 | + | #include "main.h" | |
| 22 | + | ||
| 23 | + | #include "common_windows.h" | |
| 24 | + | ||
| 25 | + | class ContainerItemButton; | |
| 26 | + | class ContainerItemPopupMenu; | |
| 27 | + | class MapWindow; | |
| 28 | + | ||
| 29 | + | class OldPropertiesWindow : public ObjectPropertiesWindowBase { | |
| 30 | + | public: | |
| 31 | + | OldPropertiesWindow(wxWindow* parent, const Map* map, const Tile* tile, Item* item, wxPoint = wxDefaultPosition); | |
| 32 | + | OldPropertiesWindow(wxWindow* parent, const Map* map, const Tile* tile, Creature* creature, wxPoint = wxDefaultPosition); | |
| 33 | + | OldPropertiesWindow(wxWindow* parent, const Map* map, const Tile* tile, Spawn* spawn, wxPoint = wxDefaultPosition); | |
| 34 | + | virtual ~OldPropertiesWindow(); | |
| 35 | + | ||
| 36 | + | void OnFocusChange(wxFocusEvent&); | |
| 37 | + | void OnChar(wxKeyEvent& evt); | |
| 38 | + | void OnKeyDown(wxKeyEvent& evt); | |
| 39 | + | void OnTextEnter(wxCommandEvent& evt); | |
| 40 | + | ||
| 41 | + | void OnClickOK(wxCommandEvent&); | |
| 42 | + | void OnClickCancel(wxCommandEvent&); | |
| 43 | + | void OnClose(wxCloseEvent& evt); | |
| 44 | + | ||
| 45 | + | void Update(); | |
| 46 | + | ||
| 47 | + | // Non-modal methods | |
| 48 | + | static OldPropertiesWindow* getInstance() { return instance; } | |
| 49 | + | static void destroyInstance() { | |
| 50 | + | if (instance) { | |
| 51 | + | instance->Destroy(); | |
| 52 | + | instance = nullptr; | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | static bool isActive() { return instance != nullptr; } | |
| 56 | + | ||
| 57 | + | void CommitChanges(); | |
| 58 | + | ||
| 59 | + | protected: | |
| 60 | + | // Singleton instance for non-modal use | |
| 61 | + | static OldPropertiesWindow* instance; | |
| 62 | + | ||
| 63 | + | // item | |
| 64 | + | wxSpinCtrl* count_field; | |
| 65 | + | wxSpinCtrl* action_id_field; | |
| 66 | + | wxSpinCtrl* unique_id_field; | |
| 67 | + | wxSpinCtrl* door_id_field; | |
| 68 | + | wxSpinCtrl* tier_field; | |
| 69 | + | wxChoice* depot_id_field; | |
| 70 | + | wxChoice* splash_type_field; | |
| 71 | + | wxTextCtrl* text_field; | |
| 72 | + | wxTextCtrl* description_field; | |
| 73 | + | ||
| 74 | + | // teleport | |
| 75 | + | wxSpinCtrl* x_field; | |
| 76 | + | wxSpinCtrl* y_field; | |
| 77 | + | wxSpinCtrl* z_field; | |
| 78 | + | ||
| 79 | + | // podium | |
| 80 | + | wxCheckBox* show_outfit; | |
| 81 | + | wxCheckBox* show_mount; | |
| 82 | + | wxCheckBox* show_platform; | |
| 83 | + | wxSpinCtrl* look_type; | |
| 84 | + | wxSpinCtrl* look_head; | |
| 85 | + | wxSpinCtrl* look_body; | |
| 86 | + | wxSpinCtrl* look_legs; | |
| 87 | + | wxSpinCtrl* look_feet; | |
| 88 | + | wxSpinCtrl* look_addon; | |
| 89 | + | wxSpinCtrl* look_mount; | |
| 90 | + | wxSpinCtrl* look_mounthead; | |
| 91 | + | wxSpinCtrl* look_mountbody; | |
| 92 | + | wxSpinCtrl* look_mountlegs; | |
| 93 | + | wxSpinCtrl* look_mountfeet; | |
| 94 | + | ||
| 95 | + | // podium and creature | |
| 96 | + | wxChoice* direction_field; | |
| 97 | + | ||
| 98 | + | // container | |
| 99 | + | std::vector<ContainerItemButton*> container_items; | |
| 100 | + | ||
| 101 | + | friend class ContainerItemButton; | |
| 102 | + | friend class ContainerItemPopupMenu; | |
| 103 | + | ||
| 104 | + | DECLARE_EVENT_TABLE(); | |
| 105 | + | }; | |
| 106 | + | ||
| 107 | + | #endif |
| @@ -0,0 +1,868 @@ | |||
| 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 "otmapgen.h" | |
| 20 | + | #include "basemap.h" | |
| 21 | + | #include "tile.h" | |
| 22 | + | #include "item.h" | |
| 23 | + | #include "items.h" | |
| 24 | + | #include "map.h" | |
| 25 | + | ||
| 26 | + | #include <cmath> | |
| 27 | + | #include <algorithm> | |
| 28 | + | #include <functional> | |
| 29 | + | #include <iostream> | |
| 30 | + | ||
| 31 | + | // SimplexNoise implementation | |
| 32 | + | const double SimplexNoise::F2 = 0.5 * (sqrt(3.0) - 1.0); | |
| 33 | + | const double SimplexNoise::G2 = (3.0 - sqrt(3.0)) / 6.0; | |
| 34 | + | ||
| 35 | + | const int SimplexNoise::SIMPLEX[64][4] = { | |
| 36 | + | {0,1,2,3},{0,1,3,2},{0,0,0,0},{0,2,3,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,2,3,0}, | |
| 37 | + | {0,2,1,3},{0,0,0,0},{0,3,1,2},{0,3,2,1},{0,0,0,0},{0,0,0,0},{0,0,0,0},{1,3,2,0}, | |
| 38 | + | {0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}, | |
| 39 | + | {1,2,0,3},{0,0,0,0},{1,3,0,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,3,0,1},{2,3,1,0}, | |
| 40 | + | {1,0,2,3},{1,0,3,2},{0,0,0,0},{0,0,0,0},{0,0,0,0},{2,0,3,1},{0,0,0,0},{2,1,3,0}, | |
| 41 | + | {0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0},{0,0,0,0}, | |
| 42 | + | {2,0,1,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,0,1,2},{3,0,2,1},{0,0,0,0},{3,1,2,0}, | |
| 43 | + | {2,1,0,3},{0,0,0,0},{0,0,0,0},{0,0,0,0},{3,1,0,2},{0,0,0,0},{3,2,0,1},{3,2,1,0} | |
| 44 | + | }; | |
| 45 | + | ||
| 46 | + | SimplexNoise::SimplexNoise(unsigned int seed) { | |
| 47 | + | initializePermutation(seed); | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | void SimplexNoise::initializePermutation(unsigned int seed) { | |
| 51 | + | // Initialize permutation table based on seed | |
| 52 | + | std::mt19937 rng(seed); | |
| 53 | + | ||
| 54 | + | // Initialize with sequential values | |
| 55 | + | for (int i = 0; i < 256; i++) { | |
| 56 | + | perm[i] = i; | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | // Shuffle the permutation table | |
| 60 | + | for (int i = 255; i > 0; i--) { | |
| 61 | + | int j = rng() % (i + 1); | |
| 62 | + | std::swap(perm[i], perm[j]); | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | // Duplicate the permutation table | |
| 66 | + | for (int i = 0; i < 256; i++) { | |
| 67 | + | perm[256 + i] = perm[i]; | |
| 68 | + | permMod12[i] = perm[i] % 12; | |
| 69 | + | permMod12[256 + i] = perm[i] % 12; | |
| 70 | + | } | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | int SimplexNoise::fastfloor(double x) { | |
| 74 | + | int xi = (int)x; | |
| 75 | + | return x < xi ? xi - 1 : xi; | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | double SimplexNoise::dot(const int g[], double x, double y) { | |
| 79 | + | return g[0] * x + g[1] * y; | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | double SimplexNoise::noise(double xin, double yin) { | |
| 83 | + | static const int grad3[12][3] = { | |
| 84 | + | {1,1,0},{-1,1,0},{1,-1,0},{-1,-1,0}, | |
| 85 | + | {1,0,1},{-1,0,1},{1,0,-1},{-1,0,-1}, | |
| 86 | + | {0,1,1},{0,-1,1},{0,1,-1},{0,-1,-1} | |
| 87 | + | }; | |
| 88 | + | ||
| 89 | + | double n0, n1, n2; // Noise contributions from the three corners | |
| 90 | + | ||
| 91 | + | // Skew the input space to determine which simplex cell we're in | |
| 92 | + | double s = (xin + yin) * F2; // Hairy factor for 2D | |
| 93 | + | int i = fastfloor(xin + s); | |
| 94 | + | int j = fastfloor(yin + s); | |
| 95 | + | double t = (i + j) * G2; | |
| 96 | + | double X0 = i - t; // Unskew the cell origin back to (x,y) space | |
| 97 | + | double Y0 = j - t; | |
| 98 | + | double x0 = xin - X0; // The x,y distances from the cell origin | |
| 99 | + | double y0 = yin - Y0; | |
| 100 | + | ||
| 101 | + | // For the 2D case, the simplex shape is an equilateral triangle | |
| 102 | + | // Determine which simplex we are in | |
| 103 | + | int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords | |
| 104 | + | if (x0 > y0) { | |
| 105 | + | i1 = 1; j1 = 0; // lower triangle, XY order: (0,0)->(1,0)->(1,1) | |
| 106 | + | } else { | |
| 107 | + | i1 = 0; j1 = 1; // upper triangle, YX order: (0,0)->(0,1)->(1,1) | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and | |
| 111 | + | // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where | |
| 112 | + | // c = (3-sqrt(3))/6 | |
| 113 | + | double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords | |
| 114 | + | double y1 = y0 - j1 + G2; | |
| 115 | + | double x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords | |
| 116 | + | double y2 = y0 - 1.0 + 2.0 * G2; | |
| 117 | + | ||
| 118 | + | // Work out the hashed gradient indices of the three simplex corners | |
| 119 | + | int ii = i & 255; | |
| 120 | + | int jj = j & 255; | |
| 121 | + | int gi0 = permMod12[ii + perm[jj]]; | |
| 122 | + | int gi1 = permMod12[ii + i1 + perm[jj + j1]]; | |
| 123 | + | int gi2 = permMod12[ii + 1 + perm[jj + 1]]; | |
| 124 | + | ||
| 125 | + | // Calculate the contribution from the three corners | |
| 126 | + | double t0 = 0.5 - x0 * x0 - y0 * y0; | |
| 127 | + | if (t0 < 0) { | |
| 128 | + | n0 = 0.0; | |
| 129 | + | } else { | |
| 130 | + | t0 *= t0; | |
| 131 | + | n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | double t1 = 0.5 - x1 * x1 - y1 * y1; | |
| 135 | + | if (t1 < 0) { | |
| 136 | + | n1 = 0.0; | |
| 137 | + | } else { | |
| 138 | + | t1 *= t1; | |
| 139 | + | n1 = t1 * t1 * dot(grad3[gi1], x1, y1); | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | double t2 = 0.5 - x2 * x2 - y2 * y2; | |
| 143 | + | if (t2 < 0) { | |
| 144 | + | n2 = 0.0; | |
| 145 | + | } else { | |
| 146 | + | t2 *= t2; | |
| 147 | + | n2 = t2 * t2 * dot(grad3[gi2], x2, y2); | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | // Add contributions from each corner to get the final noise value | |
| 151 | + | // The result is scaled to return values in the interval [-1,1] | |
| 152 | + | return 70.0 * (n0 + n1 + n2); | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | double SimplexNoise::fractal(double x, double y, const std::vector<GenerationConfig::FrequencyWeight>& frequencies) { | |
| 156 | + | double value = 0.0; | |
| 157 | + | double totalWeight = 0.0; | |
| 158 | + | ||
| 159 | + | for (const auto& freq : frequencies) { | |
| 160 | + | value += noise(x * freq.frequency, y * freq.frequency) * freq.weight; | |
| 161 | + | totalWeight += freq.weight; | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | return totalWeight > 0 ? value / totalWeight : 0.0; | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | // OTMapGenerator implementation | |
| 168 | + | OTMapGenerator::OTMapGenerator() : noise_generator(nullptr) { | |
| 169 | + | seedRandom("default"); | |
| 170 | + | } | |
| 171 | + | ||
| 172 | + | OTMapGenerator::~OTMapGenerator() { | |
| 173 | + | if (noise_generator) { | |
| 174 | + | delete noise_generator; | |
| 175 | + | noise_generator = nullptr; | |
| 176 | + | } | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | void OTMapGenerator::seedRandom(const std::string& seed) { | |
| 180 | + | // Try to parse as 64-bit integer first, like original OTMapGen | |
| 181 | + | unsigned long long numeric_seed = 0; | |
| 182 | + | ||
| 183 | + | try { | |
| 184 | + | // Try to parse as number | |
| 185 | + | numeric_seed = std::stoull(seed); | |
| 186 | + | } catch (...) { | |
| 187 | + | // If parsing fails, fall back to string hash | |
| 188 | + | std::hash<std::string> hasher; | |
| 189 | + | numeric_seed = hasher(seed); | |
| 190 | + | } | |
| 191 | + | ||
| 192 | + | // Initialize noise generator and RNG with 64-bit seed | |
| 193 | + | delete noise_generator; | |
| 194 | + | noise_generator = new SimplexNoise(static_cast<unsigned int>(numeric_seed & 0xFFFFFFFF)); | |
| 195 | + | rng.seed(static_cast<unsigned int>(numeric_seed >> 32) ^ static_cast<unsigned int>(numeric_seed & 0xFFFFFFFF)); | |
| 196 | + | } | |
| 197 | + | ||
| 198 | + | bool OTMapGenerator::generateMap(BaseMap* map, const GenerationConfig& config) { | |
| 199 | + | if (!map) { | |
| 200 | + | return false; | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | // Cast BaseMap to Map to access the editor's action system | |
| 204 | + | Map* editorMap = static_cast<Map*>(map); | |
| 205 | + | ||
| 206 | + | // Initialize random seed | |
| 207 | + | seedRandom(config.seed); | |
| 208 | + | ||
| 209 | + | // Generate height map and moisture map | |
| 210 | + | auto heightMap = generateHeightMap(config); | |
| 211 | + | auto moistureMap = generateMoistureMap(config); | |
| 212 | + | ||
| 213 | + | // Generate terrain layer | |
| 214 | + | auto terrainLayer = generateTerrainLayer(heightMap, moistureMap, config); | |
| 215 | + | ||
| 216 | + | // Apply terrain to map using Actions like the editor does | |
| 217 | + | std::vector<Position> tilesToGenerate; | |
| 218 | + | ||
| 219 | + | // First pass: collect all positions that need tiles | |
| 220 | + | for (int y = 0; y < config.height; ++y) { | |
| 221 | + | for (int x = 0; x < config.width; ++x) { | |
| 222 | + | uint16_t tileId = terrainLayer[y][x]; | |
| 223 | + | if (tileId != 0) { | |
| 224 | + | tilesToGenerate.push_back(Position(x, y, config.water_level)); | |
| 225 | + | } | |
| 226 | + | } | |
| 227 | + | } | |
| 228 | + | ||
| 229 | + | // Process tiles in batches to avoid memory issues | |
| 230 | + | const int BATCH_SIZE = 1000; | |
| 231 | + | ||
| 232 | + | for (size_t start = 0; start < tilesToGenerate.size(); start += BATCH_SIZE) { | |
| 233 | + | size_t end = std::min(start + BATCH_SIZE, tilesToGenerate.size()); | |
| 234 | + | ||
| 235 | + | // Create a batch of tiles | |
| 236 | + | for (size_t i = start; i < end; ++i) { | |
| 237 | + | Position pos = tilesToGenerate[i]; | |
| 238 | + | uint16_t tileId = terrainLayer[pos.y][pos.x]; | |
| 239 | + | ||
| 240 | + | // Create tile location and allocate tile properly | |
| 241 | + | TileLocation* location = editorMap->createTileL(pos); | |
| 242 | + | Tile* existing_tile = location->get(); | |
| 243 | + | Tile* new_tile = nullptr; | |
| 244 | + | ||
| 245 | + | if (existing_tile) { | |
| 246 | + | // Copy existing tile and modify it | |
| 247 | + | new_tile = existing_tile->deepCopy(*editorMap); | |
| 248 | + | } else { | |
| 249 | + | // Create new tile | |
| 250 | + | new_tile = editorMap->allocator(location); | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // Set the ground using proper API | |
| 254 | + | if (new_tile) { | |
| 255 | + | // Remove existing ground if any | |
| 256 | + | if (new_tile->ground) { | |
| 257 | + | delete new_tile->ground; | |
| 258 | + | new_tile->ground = nullptr; | |
| 259 | + | } | |
| 260 | + | ||
| 261 | + | // Create new ground item | |
| 262 | + | Item* groundItem = Item::Create(tileId); | |
| 263 | + | if (groundItem) { | |
| 264 | + | new_tile->ground = groundItem; | |
| 265 | + | } | |
| 266 | + | ||
| 267 | + | // Set the tile back to the map | |
| 268 | + | editorMap->setTile(pos, new_tile); | |
| 269 | + | } | |
| 270 | + | } | |
| 271 | + | } | |
| 272 | + | ||
| 273 | + | // Generate caves if enabled | |
| 274 | + | if (config.add_caves) { | |
| 275 | + | auto caveLayer = generateCaveLayer(config); | |
| 276 | + | ||
| 277 | + | std::vector<Position> caveTilesToGenerate; | |
| 278 | + | ||
| 279 | + | for (int y = 0; y < config.height; ++y) { | |
| 280 | + | for (int x = 0; x < config.width; ++x) { | |
| 281 | + | uint16_t caveId = caveLayer[y][x]; | |
| 282 | + | if (caveId != 0) { | |
| 283 | + | // Place cave tiles below the surface (floors 8+) | |
| 284 | + | // Since map editor uses positive Z for underground, use water_level + offset | |
| 285 | + | for (int z = config.water_level + 1; z <= config.water_level + config.cave_depth && z <= 15; ++z) { | |
| 286 | + | caveTilesToGenerate.push_back(Position(x, y, z)); | |
| 287 | + | } | |
| 288 | + | } | |
| 289 | + | } | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | // Process cave tiles in batches | |
| 293 | + | for (size_t start = 0; start < caveTilesToGenerate.size(); start += BATCH_SIZE) { | |
| 294 | + | size_t end = std::min(start + BATCH_SIZE, caveTilesToGenerate.size()); | |
| 295 | + | ||
| 296 | + | for (size_t i = start; i < end; ++i) { | |
| 297 | + | Position pos = caveTilesToGenerate[i]; | |
| 298 | + | uint16_t caveId = caveLayer[pos.y][pos.x]; | |
| 299 | + | ||
| 300 | + | TileLocation* location = editorMap->createTileL(pos); | |
| 301 | + | Tile* existing_tile = location->get(); | |
| 302 | + | Tile* new_tile = nullptr; | |
| 303 | + | ||
| 304 | + | if (existing_tile) { | |
| 305 | + | new_tile = existing_tile->deepCopy(*editorMap); | |
| 306 | + | } else { | |
| 307 | + | new_tile = editorMap->allocator(location); | |
| 308 | + | } | |
| 309 | + | ||
| 310 | + | if (new_tile) { | |
| 311 | + | // Remove existing ground if any | |
| 312 | + | if (new_tile->ground) { | |
| 313 | + | delete new_tile->ground; | |
| 314 | + | new_tile->ground = nullptr; | |
| 315 | + | } | |
| 316 | + | ||
| 317 | + | // Create new ground item | |
| 318 | + | Item* groundItem = Item::Create(caveId); | |
| 319 | + | if (groundItem) { | |
| 320 | + | new_tile->ground = groundItem; | |
| 321 | + | } | |
| 322 | + | ||
| 323 | + | // Set the tile back to the map | |
| 324 | + | editorMap->setTile(pos, new_tile); | |
| 325 | + | } | |
| 326 | + | } | |
| 327 | + | } | |
| 328 | + | } | |
| 329 | + | ||
| 330 | + | // Add decorations if not terrain only (simplified for now) | |
| 331 | + | if (!config.terrain_only) { | |
| 332 | + | // Find grass layer for decoration placement | |
| 333 | + | const TerrainLayer* grassLayer = nullptr; | |
| 334 | + | for (const auto& layer : config.terrain_layers) { | |
| 335 | + | if (layer.name == "Grass" && layer.enabled) { | |
| 336 | + | grassLayer = &layer; | |
| 337 | + | break; | |
| 338 | + | } | |
| 339 | + | } | |
| 340 | + | ||
| 341 | + | if (grassLayer) { | |
| 342 | + | for (int y = 0; y < config.height; ++y) { | |
| 343 | + | for (int x = 0; x < config.width; ++x) { | |
| 344 | + | Tile* tile = editorMap->getTile(x, y, config.water_level); | |
| 345 | + | if (tile && tile->ground) { | |
| 346 | + | uint16_t groundId = tile->ground->getID(); | |
| 347 | + | ||
| 348 | + | // Add vegetation to grass tiles | |
| 349 | + | if (groundId == grassLayer->item_id) { | |
| 350 | + | std::uniform_real_distribution<double> dist(0.0, 1.0); | |
| 351 | + | if (dist(rng) < 0.05) { // 5% chance | |
| 352 | + | Tile* new_tile = tile->deepCopy(*editorMap); | |
| 353 | + | ||
| 354 | + | // Use configurable decoration items (could be made configurable too) | |
| 355 | + | uint16_t decorationId = 2700; // Default tree ID | |
| 356 | + | double rand_val = dist(rng); | |
| 357 | + | if (rand_val < 0.6) { | |
| 358 | + | decorationId = 2700; // Tree | |
| 359 | + | } else if (rand_val < 0.8) { | |
| 360 | + | decorationId = 2785; // Bush | |
| 361 | + | } else { | |
| 362 | + | decorationId = 2782; // Flower | |
| 363 | + | } | |
| 364 | + | ||
| 365 | + | Item* decoration = Item::Create(decorationId); | |
| 366 | + | if (decoration) { | |
| 367 | + | new_tile->addItem(decoration); | |
| 368 | + | editorMap->setTile(Position(x, y, config.water_level), new_tile); | |
| 369 | + | } | |
| 370 | + | } | |
| 371 | + | } | |
| 372 | + | } | |
| 373 | + | } | |
| 374 | + | } | |
| 375 | + | } | |
| 376 | + | } | |
| 377 | + | ||
| 378 | + | return true; | |
| 379 | + | } | |
| 380 | + | ||
| 381 | + | std::vector<std::vector<double>> OTMapGenerator::generateHeightMap(const GenerationConfig& config) { | |
| 382 | + | std::vector<std::vector<double>> heightMap(config.height, std::vector<double>(config.width, 0.0)); | |
| 383 | + | ||
| 384 | + | double centerX = config.width / 2.0; | |
| 385 | + | double centerY = config.height / 2.0; | |
| 386 | + | double maxDistance = std::min(config.width, config.height) / 2.0; | |
| 387 | + | ||
| 388 | + | for (int y = 0; y < config.height; ++y) { | |
| 389 | + | for (int x = 0; x < config.width; ++x) { | |
| 390 | + | // Enhanced noise for better island shapes | |
| 391 | + | double nx = x * config.noise_increment / config.width; | |
| 392 | + | double ny = y * config.noise_increment / config.height; | |
| 393 | + | ||
| 394 | + | // Multi-octave noise for more interesting terrain | |
| 395 | + | double noiseValue = 0.0; | |
| 396 | + | double amplitude = 1.0; | |
| 397 | + | double frequency = 1.0; | |
| 398 | + | double maxValue = 0.0; | |
| 399 | + | ||
| 400 | + | // Generate 4 octaves of noise for complex island shapes | |
| 401 | + | for (int i = 0; i < 4; i++) { | |
| 402 | + | noiseValue += noise_generator->noise(nx * frequency, ny * frequency) * amplitude; | |
| 403 | + | maxValue += amplitude; | |
| 404 | + | amplitude *= 0.5; | |
| 405 | + | frequency *= 2.0; | |
| 406 | + | } | |
| 407 | + | noiseValue /= maxValue; // Normalize | |
| 408 | + | ||
| 409 | + | // Apply island distance effect with better shaping | |
| 410 | + | double distance = getDistance(x, y, (int)centerX, (int)centerY, config.euclidean); | |
| 411 | + | double normalizedDistance = distance / maxDistance; | |
| 412 | + | ||
| 413 | + | // Create more interesting island shapes with noise-based distortion | |
| 414 | + | double distortionNoise = noise_generator->noise(x * 0.01, y * 0.01) * 0.3; | |
| 415 | + | normalizedDistance += distortionNoise; | |
| 416 | + | ||
| 417 | + | // Improved island falloff for organic shapes | |
| 418 | + | double distanceEffect = 1.0 - pow(std::max(0.0, normalizedDistance), config.island_distance_exponent); | |
| 419 | + | distanceEffect = std::max(0.0, distanceEffect * config.island_distance_decrement); | |
| 420 | + | ||
| 421 | + | // Apply sharper transitions for distinct land/water boundaries | |
| 422 | + | if (distanceEffect < 0.3) { | |
| 423 | + | distanceEffect *= distanceEffect; // Square it to create sharper dropoff | |
| 424 | + | } | |
| 425 | + | ||
| 426 | + | // Combine noise with distance effect | |
| 427 | + | double height = (noiseValue + 1.0) * 0.5; // Normalize to [0,1] | |
| 428 | + | height = pow(height, config.exponent) * config.linear; | |
| 429 | + | height *= distanceEffect; | |
| 430 | + | ||
| 431 | + | // Add some randomness to break up regular patterns | |
| 432 | + | height += (noise_generator->noise(x * 0.1, y * 0.1) * 0.05); | |
| 433 | + | height = std::max(0.0, std::min(1.0, height)); | |
| 434 | + | ||
| 435 | + | heightMap[y][x] = height; | |
| 436 | + | } | |
| 437 | + | } | |
| 438 | + | ||
| 439 | + | return heightMap; | |
| 440 | + | } | |
| 441 | + | ||
| 442 | + | std::vector<std::vector<double>> OTMapGenerator::generateMoistureMap(const GenerationConfig& config) { | |
| 443 | + | std::vector<std::vector<double>> moistureMap(config.height, std::vector<double>(config.width, 0.0)); | |
| 444 | + | ||
| 445 | + | for (int y = 0; y < config.height; ++y) { | |
| 446 | + | for (int x = 0; x < config.width; ++x) { | |
| 447 | + | // Generate moisture noise with different scale than height | |
| 448 | + | double nx = x * 0.01; // Moisture varies more slowly | |
| 449 | + | double ny = y * 0.01; | |
| 450 | + | double moistureValue = noise_generator->noise(nx, ny); | |
| 451 | + | ||
| 452 | + | moistureMap[y][x] = moistureValue; | |
| 453 | + | } | |
| 454 | + | } | |
| 455 | + | ||
| 456 | + | return moistureMap; | |
| 457 | + | } | |
| 458 | + | ||
| 459 | + | std::vector<std::vector<uint16_t>> OTMapGenerator::generateTerrainLayer(const std::vector<std::vector<double>>& heightMap, | |
| 460 | + | const std::vector<std::vector<double>>& moistureMap, | |
| 461 | + | const GenerationConfig& config) { | |
| 462 | + | std::vector<std::vector<uint16_t>> terrainLayer(config.height, std::vector<uint16_t>(config.width, 0)); | |
| 463 | + | ||
| 464 | + | for (int y = 0; y < config.height; ++y) { | |
| 465 | + | for (int x = 0; x < config.width; ++x) { | |
| 466 | + | double height = heightMap[y][x]; | |
| 467 | + | double moisture = moistureMap[y][x]; | |
| 468 | + | ||
| 469 | + | // Reduce moisture influence for more consistent terrain generation | |
| 470 | + | // This helps prevent strange terrain combinations on upper floors | |
| 471 | + | uint16_t tileId = getTerrainTileId(height, moisture * 0.7, config); | |
| 472 | + | terrainLayer[y][x] = tileId; | |
| 473 | + | } | |
| 474 | + | } | |
| 475 | + | ||
| 476 | + | return terrainLayer; | |
| 477 | + | } | |
| 478 | + | ||
| 479 | + | uint16_t OTMapGenerator::getTerrainTileId(double height, double moisture, const GenerationConfig& config) { | |
| 480 | + | // Select terrain layer based on height and reduced moisture influence | |
| 481 | + | const TerrainLayer* selectedLayer = selectTerrainLayer(height, moisture, config); | |
| 482 | + | ||
| 483 | + | if (selectedLayer) { | |
| 484 | + | return selectedLayer->item_id; | |
| 485 | + | } | |
| 486 | + | ||
| 487 | + | // Fallback to water if no layer matches | |
| 488 | + | return config.water_item_id; | |
| 489 | + | } | |
| 490 | + | ||
| 491 | + | const TerrainLayer* OTMapGenerator::selectTerrainLayer(double height, double moisture, const GenerationConfig& config) { | |
| 492 | + | // Sort layers by z-order (higher z-order = higher priority) | |
| 493 | + | std::vector<const TerrainLayer*> sortedLayers; | |
| 494 | + | for (const auto& layer : config.terrain_layers) { | |
| 495 | + | if (layer.enabled) { | |
| 496 | + | sortedLayers.push_back(&layer); | |
| 497 | + | } | |
| 498 | + | } | |
| 499 | + | ||
| 500 | + | std::sort(sortedLayers.begin(), sortedLayers.end(), | |
| 501 | + | [](const TerrainLayer* a, const TerrainLayer* b) { | |
| 502 | + | return a->z_order > b->z_order; // Higher z-order first | |
| 503 | + | }); | |
| 504 | + | ||
| 505 | + | // Find the first layer that matches the height and moisture criteria | |
| 506 | + | for (const TerrainLayer* layer : sortedLayers) { | |
| 507 | + | if (height >= layer->height_min && height <= layer->height_max && | |
| 508 | + | moisture >= layer->moisture_min && moisture <= layer->moisture_max) { | |
| 509 | + | ||
| 510 | + | // Check coverage probability | |
| 511 | + | if (layer->coverage >= 1.0) { | |
| 512 | + | return layer; | |
| 513 | + | } else { | |
| 514 | + | std::uniform_real_distribution<double> dist(0.0, 1.0); | |
| 515 | + | if (dist(rng) < layer->coverage) { | |
| 516 | + | return layer; | |
| 517 | + | } | |
| 518 | + | } | |
| 519 | + | } | |
| 520 | + | } | |
| 521 | + | ||
| 522 | + | return nullptr; // No matching layer found | |
| 523 | + | } | |
| 524 | + | ||
| 525 | + | std::vector<std::vector<uint16_t>> OTMapGenerator::generateCaveLayer(const GenerationConfig& config) { | |
| 526 | + | std::vector<std::vector<uint16_t>> caveLayer(config.height, std::vector<uint16_t>(config.width, 0)); | |
| 527 | + | ||
| 528 | + | for (int y = 0; y < config.height; ++y) { | |
| 529 | + | for (int x = 0; x < config.width; ++x) { | |
| 530 | + | double caveNoise = noise_generator->noise(x * config.cave_roughness, y * config.cave_roughness); | |
| 531 | + | ||
| 532 | + | // Random chance for cave generation | |
| 533 | + | std::uniform_real_distribution<double> dist(0.0, 1.0); | |
| 534 | + | if (dist(rng) < config.cave_chance && caveNoise > 0.1) { | |
| 535 | + | caveLayer[y][x] = config.cave_item_id; // Use configurable cave item ID | |
| 536 | + | } | |
| 537 | + | } | |
| 538 | + | } | |
| 539 | + | ||
| 540 | + | return caveLayer; | |
| 541 | + | } | |
| 542 | + | ||
| 543 | + | double OTMapGenerator::getDistance(int x, int y, int centerX, int centerY, bool euclidean) { | |
| 544 | + | if (euclidean) { | |
| 545 | + | double dx = x - centerX; | |
| 546 | + | double dy = y - centerY; | |
| 547 | + | return sqrt(dx * dx + dy * dy); | |
| 548 | + | } else { | |
| 549 | + | return std::max(abs(x - centerX), abs(y - centerY)); | |
| 550 | + | } | |
| 551 | + | } | |
| 552 | + | ||
| 553 | + | double OTMapGenerator::smoothstep(double edge0, double edge1, double x) { | |
| 554 | + | x = std::clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); | |
| 555 | + | return x * x * (3 - 2 * x); | |
| 556 | + | } | |
| 557 | + | ||
| 558 | + | void OTMapGenerator::generateBorders(BaseMap* map, const GenerationConfig& config) { | |
| 559 | + | // This would integrate with your existing border system | |
| 560 | + | // For now, we'll leave this as a placeholder since you already have border generation | |
| 561 | + | } | |
| 562 | + | ||
| 563 | + | void OTMapGenerator::addBordersToTile(BaseMap* map, Tile* tile, int x, int y, int z) { | |
| 564 | + | // Placeholder - integrate with your existing border system | |
| 565 | + | } | |
| 566 | + | ||
| 567 | + | void OTMapGenerator::addClutter(BaseMap* map, const GenerationConfig& config) { | |
| 568 | + | // Add decorative items to terrain | |
| 569 | + | for (int y = 0; y < config.height; ++y) { | |
| 570 | + | for (int x = 0; x < config.width; ++x) { | |
| 571 | + | Tile* tile = map->getTile(x, y, config.water_level); | |
| 572 | + | if (tile && tile->ground) { | |
| 573 | + | uint16_t groundId = tile->ground->getID(); | |
| 574 | + | ||
| 575 | + | // Find the terrain layer this ground belongs to | |
| 576 | + | const TerrainLayer* terrainLayer = nullptr; | |
| 577 | + | for (const auto& layer : config.terrain_layers) { | |
| 578 | + | if (layer.item_id == groundId && layer.enabled) { | |
| 579 | + | terrainLayer = &layer; | |
| 580 | + | break; | |
| 581 | + | } | |
| 582 | + | } | |
| 583 | + | ||
| 584 | + | if (terrainLayer) { | |
| 585 | + | // Add decorations based on terrain type | |
| 586 | + | if (terrainLayer->name == "Grass") { | |
| 587 | + | placeTreesAndVegetation(map, tile, groundId); | |
| 588 | + | } else if (terrainLayer->name == "Mountain" || terrainLayer->brush_name.find("stone") != std::string::npos) { | |
| 589 | + | placeStones(map, tile, groundId); | |
| 590 | + | } | |
| 591 | + | } | |
| 592 | + | } | |
| 593 | + | } | |
| 594 | + | } | |
| 595 | + | } | |
| 596 | + | ||
| 597 | + | void OTMapGenerator::placeTreesAndVegetation(BaseMap* map, Tile* tile, uint16_t groundId) { | |
| 598 | + | std::uniform_real_distribution<double> dist(0.0, 1.0); | |
| 599 | + | ||
| 600 | + | // Random chance for vegetation | |
| 601 | + | if (dist(rng) < 0.1) { // 10% chance | |
| 602 | + | uint16_t decorationId; | |
| 603 | + | double rand_val = dist(rng); | |
| 604 | + | ||
| 605 | + | if (rand_val < 0.6) { | |
| 606 | + | decorationId = 2700; // Tree | |
| 607 | + | } else if (rand_val < 0.8) { | |
| 608 | + | decorationId = 2785; // Bush | |
| 609 | + | } else { | |
| 610 | + | decorationId = 2782; // Flower | |
| 611 | + | } | |
| 612 | + | ||
| 613 | + | OTMapGenUtils::addDecoration(tile, decorationId); | |
| 614 | + | } | |
| 615 | + | } | |
| 616 | + | ||
| 617 | + | void OTMapGenerator::placeStones(BaseMap* map, Tile* tile, uint16_t groundId) { | |
| 618 | + | std::uniform_real_distribution<double> dist(0.0, 1.0); | |
| 619 | + | ||
| 620 | + | // Random chance for stones | |
| 621 | + | if (dist(rng) < 0.05) { // 5% chance | |
| 622 | + | uint16_t stoneId = (dist(rng) < 0.7) ? 1770 : 1771; // Small or large stone | |
| 623 | + | OTMapGenUtils::addDecoration(tile, stoneId); | |
| 624 | + | } | |
| 625 | + | } | |
| 626 | + | ||
| 627 | + | void OTMapGenerator::placeCaveDecorations(BaseMap* map, Tile* tile) { | |
| 628 | + | std::uniform_real_distribution<double> dist(0.0, 1.0); | |
| 629 | + | ||
| 630 | + | // Random chance for cave decorations | |
| 631 | + | if (dist(rng) < 0.15) { // 15% chance | |
| 632 | + | OTMapGenUtils::addDecoration(tile, 1785); // Stalagmite | |
| 633 | + | } | |
| 634 | + | } | |
| 635 | + | ||
| 636 | + | std::vector<std::vector<uint16_t>> OTMapGenerator::generateLayers(const GenerationConfig& config) { | |
| 637 | + | // Initialize random seed | |
| 638 | + | seedRandom(config.seed); | |
| 639 | + | ||
| 640 | + | // Generate height map and moisture map | |
| 641 | + | auto heightMap = generateHeightMap(config); | |
| 642 | + | auto moistureMap = generateMoistureMap(config); | |
| 643 | + | ||
| 644 | + | // Create 8 layers (floors) like the original OTMapGen | |
| 645 | + | std::vector<std::vector<std::vector<uint16_t>>> layers(8); | |
| 646 | + | for (int z = 0; z < 8; ++z) { | |
| 647 | + | layers[z] = std::vector<std::vector<uint16_t>>(config.height, std::vector<uint16_t>(config.width, 0)); | |
| 648 | + | } | |
| 649 | + | ||
| 650 | + | // Fill terrain using the fillColumn approach like the original | |
| 651 | + | for (int y = 0; y < config.height; ++y) { | |
| 652 | + | for (int x = 0; x < config.width; ++x) { | |
| 653 | + | double height = heightMap[y][x]; | |
| 654 | + | double moisture = moistureMap[y][x]; | |
| 655 | + | ||
| 656 | + | // Get terrain tile ID using the new configurable system | |
| 657 | + | uint16_t tileId = getTerrainTileId(height, moisture, config); | |
| 658 | + | ||
| 659 | + | // Calculate elevation for determining elevated content | |
| 660 | + | // Scale height to 0-7 range for determining what gets elevated content | |
| 661 | + | int elevation = static_cast<int>(std::max(0.0, std::min(7.0, height * 8.0))); | |
| 662 | + | ||
| 663 | + | // Fill column - this will put main terrain on layers[0] (Floor 7) | |
| 664 | + | // and add elevated content based on elevation value | |
| 665 | + | fillColumn(layers, x, y, elevation, tileId, config); | |
| 666 | + | } | |
| 667 | + | } | |
| 668 | + | ||
| 669 | + | // Add caves if enabled (now using configurable cave generation) | |
| 670 | + | if (config.add_caves) { | |
| 671 | + | auto caveLayer = generateCaveLayer(config); | |
| 672 | + | ||
| 673 | + | // Place caves on underground floors (below the main surface) | |
| 674 | + | // In Tibia coordinates: floors 8+ are underground | |
| 675 | + | // But since we only have 8 layers (0-7), we'll place caves on upper floors sparsely | |
| 676 | + | for (int y = 0; y < config.height; ++y) { | |
| 677 | + | for (int x = 0; x < config.width; ++x) { | |
| 678 | + | uint16_t caveId = caveLayer[y][x]; | |
| 679 | + | if (caveId != 0) { | |
| 680 | + | // Place caves on layers 1-3 (floors 6-4) with decreasing probability | |
| 681 | + | std::uniform_real_distribution<double> dist(0.0, 1.0); | |
| 682 | + | ||
| 683 | + | if (dist(rng) < 0.8) { // 80% chance on floor 6 | |
| 684 | + | layers[1][y][x] = caveId; | |
| 685 | + | } | |
| 686 | + | if (dist(rng) < 0.5) { // 50% chance on floor 5 | |
| 687 | + | layers[2][y][x] = caveId; | |
| 688 | + | } | |
| 689 | + | if (dist(rng) < 0.2) { // 20% chance on floor 4 | |
| 690 | + | layers[3][y][x] = caveId; | |
| 691 | + | } | |
| 692 | + | } | |
| 693 | + | } | |
| 694 | + | } | |
| 695 | + | } | |
| 696 | + | ||
| 697 | + | // Convert 3D layers to the format expected by the dialog (single layer for each floor) | |
| 698 | + | // Return layers in the order they map to Tibia floors: | |
| 699 | + | // layers[0] → Floor 7 (ground level) | |
| 700 | + | // layers[1] → Floor 6 (+1 above) | |
| 701 | + | // layers[7] → Floor 0 (+7 above) | |
| 702 | + | std::vector<std::vector<uint16_t>> result; | |
| 703 | + | for (int z = 0; z < 8; ++z) { | |
| 704 | + | std::vector<uint16_t> floorData; | |
| 705 | + | floorData.reserve(config.width * config.height); | |
| 706 | + | ||
| 707 | + | for (int y = 0; y < config.height; ++y) { | |
| 708 | + | for (int x = 0; x < config.width; ++x) { | |
| 709 | + | floorData.push_back(layers[z][y][x]); | |
| 710 | + | } | |
| 711 | + | } | |
| 712 | + | result.push_back(floorData); | |
| 713 | + | } | |
| 714 | + | ||
| 715 | + | return result; | |
| 716 | + | } | |
| 717 | + | ||
| 718 | + | void OTMapGenerator::fillColumn(std::vector<std::vector<std::vector<uint16_t>>>& layers, | |
| 719 | + | int x, int y, int elevation, uint16_t surfaceTileId, | |
| 720 | + | const GenerationConfig& config) { | |
| 721 | + | // Always place the surface tile on the ground level (layers[0] → Floor 7) | |
| 722 | + | layers[0][y][x] = surfaceTileId; | |
| 723 | + | ||
| 724 | + | // For upper floors, we need different logic than just elevation | |
| 725 | + | // Upper floors should create proper mountain formations with grass/ground between rock layers | |
| 726 | + | ||
| 727 | + | if (elevation > 4) { // Start considering upper floors at medium elevation | |
| 728 | + | // Check if this is mountain terrain by item ID or brush name | |
| 729 | + | bool isMountainTerrain = false; | |
| 730 | + | for (const auto& layer : config.terrain_layers) { | |
| 731 | + | if (layer.item_id == surfaceTileId && layer.enabled) { | |
| 732 | + | isMountainTerrain = (layer.name == "Mountain" || layer.brush_name == "mountain" || | |
| 733 | + | layer.brush_name == "snow" || layer.brush_name.find("stone") != std::string::npos); | |
| 734 | + | break; | |
| 735 | + | } | |
| 736 | + | } | |
| 737 | + | ||
| 738 | + | if (isMountainTerrain) { | |
| 739 | + | // Generate mountain formations with proper layering | |
| 740 | + | std::uniform_real_distribution<double> dist(0.0, 1.0); | |
| 741 | + | ||
| 742 | + | // Generate additional noise for upper floor variation | |
| 743 | + | double upperNoise = noise_generator->noise(x * 0.05, y * 0.05); | |
| 744 | + | double verticalNoise = noise_generator->noise(x * 0.02, y * 0.02); | |
| 745 | + | ||
| 746 | + | // Calculate probability based on elevation and noise | |
| 747 | + | double mountainChance = (elevation - 4) / 4.0; // 0-1 based on elevation above threshold | |
| 748 | + | mountainChance *= (upperNoise + 1.0) * 0.5; // Modulate with noise | |
| 749 | + | ||
| 750 | + | // Layer 1 (Floor 6): More frequent mountain/rock | |
| 751 | + | if (dist(rng) < mountainChance * 0.7) { | |
| 752 | + | layers[1][y][x] = surfaceTileId; | |
| 753 | + | ||
| 754 | + | // Layer 2 (Floor 5): Sometimes grass/ground between rock layers | |
| 755 | + | if (elevation > 6 && dist(rng) < 0.4) { | |
| 756 | + | if (verticalNoise > 0.2) { | |
| 757 | + | layers[2][y][x] = surfaceTileId; // Continue mountain | |
| 758 | + | } else { | |
| 759 | + | // Create ground/grass layer between rock formations | |
| 760 | + | const TerrainLayer* grassLayer = nullptr; | |
| 761 | + | for (const auto& layer : config.terrain_layers) { | |
| 762 | + | if ((layer.name == "Grass" || layer.brush_name == "grass") && layer.enabled) { | |
| 763 | + | grassLayer = &layer; | |
| 764 | + | break; | |
| 765 | + | } | |
| 766 | + | } | |
| 767 | + | if (grassLayer) { | |
| 768 | + | layers[2][y][x] = grassLayer->item_id; | |
| 769 | + | } | |
| 770 | + | } | |
| 771 | + | ||
| 772 | + | // Layer 3 (Floor 4): Final rock cap if very high elevation | |
| 773 | + | if (elevation > 7 && verticalNoise > 0.5 && dist(rng) < 0.3) { | |
| 774 | + | layers[3][y][x] = surfaceTileId; | |
| 775 | + | } | |
| 776 | + | } | |
| 777 | + | } | |
| 778 | + | } | |
| 779 | + | } | |
| 780 | + | ||
| 781 | + | // Note: Underground content (caves) is handled separately in generateLayers | |
| 782 | + | } | |
| 783 | + | ||
| 784 | + | // Utility functions | |
| 785 | + | namespace OTMapGenUtils { | |
| 786 | + | Tile* getOrCreateTile(BaseMap* map, int x, int y, int z) { | |
| 787 | + | Position pos(x, y, z); | |
| 788 | + | Tile* tile = map->getTile(pos); | |
| 789 | + | if (!tile) { | |
| 790 | + | tile = map->allocator(map->createTileL(pos)); | |
| 791 | + | map->setTile(pos, tile); | |
| 792 | + | } | |
| 793 | + | return tile; | |
| 794 | + | } | |
| 795 | + | ||
| 796 | + | void setGroundTile(Tile* tile, uint16_t itemId) { | |
| 797 | + | if (!tile) return; | |
| 798 | + | ||
| 799 | + | // Remove existing ground | |
| 800 | + | if (tile->ground) { | |
| 801 | + | delete tile->ground; | |
| 802 | + | tile->ground = nullptr; | |
| 803 | + | } | |
| 804 | + | ||
| 805 | + | // Create new ground item | |
| 806 | + | Item* groundItem = Item::Create(itemId); | |
| 807 | + | if (groundItem) { | |
| 808 | + | tile->ground = groundItem; | |
| 809 | + | } | |
| 810 | + | } | |
| 811 | + | ||
| 812 | + | void addDecoration(Tile* tile, uint16_t itemId) { | |
| 813 | + | if (!tile) return; | |
| 814 | + | ||
| 815 | + | Item* decoration = Item::Create(itemId); | |
| 816 | + | if (decoration) { | |
| 817 | + | tile->addItem(decoration); | |
| 818 | + | } | |
| 819 | + | } | |
| 820 | + | ||
| 821 | + | bool isWaterTile(uint16_t itemId) { | |
| 822 | + | // This could be made configurable by checking against water item IDs | |
| 823 | + | return itemId == 4608 || itemId == 4609 || itemId == 4610 || itemId == 4611; | |
| 824 | + | } | |
| 825 | + | ||
| 826 | + | bool isLandTile(uint16_t itemId) { | |
| 827 | + | // This could be made configurable by checking against land item IDs | |
| 828 | + | return itemId == 4526 || itemId == 231 || itemId == 1284 || itemId == 4597; | |
| 829 | + | } | |
| 830 | + | ||
| 831 | + | bool isMountainTile(uint16_t itemId) { | |
| 832 | + | // This could be made configurable by checking against mountain item IDs | |
| 833 | + | return itemId == 919 || itemId == 4611 || itemId == 4621 || itemId == 4616; | |
| 834 | + | } | |
| 835 | + | ||
| 836 | + | std::vector<std::string> getAvailableBrushes() { | |
| 837 | + | // This should parse the actual grounds.xml files | |
| 838 | + | // For now, return a basic list | |
| 839 | + | return { | |
| 840 | + | "grass", "sea", "sand", "mountain", "cave", "snow", | |
| 841 | + | "stone floor", "wooden floor", "lawn", "ice" | |
| 842 | + | }; | |
| 843 | + | } | |
| 844 | + | ||
| 845 | + | uint16_t getPrimaryItemFromBrush(const std::string& brushName) { | |
| 846 | + | // This should parse the grounds.xml to get the primary item ID | |
| 847 | + | // For now, use basic mappings | |
| 848 | + | if (brushName == "grass") return 4526; | |
| 849 | + | else if (brushName == "sea") return 4608; | |
| 850 | + | else if (brushName == "sand") return 231; | |
| 851 | + | else if (brushName == "mountain") return 919; | |
| 852 | + | else if (brushName == "cave") return 351; | |
| 853 | + | else if (brushName == "snow") return 670; | |
| 854 | + | else if (brushName == "stone floor") return 431; | |
| 855 | + | else if (brushName == "wooden floor") return 405; | |
| 856 | + | else if (brushName == "lawn") return 106; | |
| 857 | + | else if (brushName == "ice") return 671; | |
| 858 | + | return 100; // Default | |
| 859 | + | } | |
| 860 | + | ||
| 861 | + | bool applyBrushToTile(BaseMap* map, Tile* tile, const std::string& brushName, int x, int y, int z) { | |
| 862 | + | // This should integrate with the actual brush system | |
| 863 | + | // For now, just set the ground tile | |
| 864 | + | uint16_t itemId = getPrimaryItemFromBrush(brushName); | |
| 865 | + | setGroundTile(tile, itemId); | |
| 866 | + | return true; | |
| 867 | + | } | |
| 868 | + | } |
| @@ -0,0 +1,241 @@ | |||
| 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_OTMAPGEN_H_ | |
| 19 | + | #define RME_OTMAPGEN_H_ | |
| 20 | + | ||
| 21 | + | #include "main.h" | |
| 22 | + | #include <vector> | |
| 23 | + | #include <string> | |
| 24 | + | #include <map> | |
| 25 | + | #include <random> | |
| 26 | + | ||
| 27 | + | // Forward declarations | |
| 28 | + | class BaseMap; | |
| 29 | + | class Tile; | |
| 30 | + | ||
| 31 | + | // Terrain layer configuration for multiple ground types | |
| 32 | + | struct TerrainLayer { | |
| 33 | + | std::string name; | |
| 34 | + | std::string brush_name; // Name of brush from grounds.xml | |
| 35 | + | uint16_t item_id; // Primary item ID for this layer | |
| 36 | + | double height_min = 0.0; // Minimum height for this terrain | |
| 37 | + | double height_max = 1.0; // Maximum height for this terrain | |
| 38 | + | double moisture_min = -1.0; // Minimum moisture for this terrain | |
| 39 | + | double moisture_max = 1.0; // Maximum moisture for this terrain | |
| 40 | + | double noise_scale = 1.0; // Scale factor for noise sampling | |
| 41 | + | double coverage = 1.0; // Coverage probability (0.0 to 1.0) | |
| 42 | + | bool use_borders = true; // Whether to apply borders from brush | |
| 43 | + | bool enabled = true; // Whether this layer is active | |
| 44 | + | int z_order = 1000; // Rendering order (higher = on top) | |
| 45 | + | }; | |
| 46 | + | ||
| 47 | + | struct GenerationConfig { | |
| 48 | + | std::string seed; | |
| 49 | + | int width = 256; | |
| 50 | + | int height = 256; | |
| 51 | + | std::string version = "10.98"; | |
| 52 | + | bool terrain_only = false; | |
| 53 | + | ||
| 54 | + | // Generation parameters | |
| 55 | + | double noise_increment = 1.0; | |
| 56 | + | double island_distance_decrement = 0.92; | |
| 57 | + | double island_distance_exponent = 0.25; | |
| 58 | + | int cave_depth = 20; | |
| 59 | + | double cave_roughness = 0.45; | |
| 60 | + | double cave_chance = 0.09; | |
| 61 | + | bool sand_biome = true; | |
| 62 | + | bool euclidean = false; | |
| 63 | + | bool smooth_coastline = true; | |
| 64 | + | bool add_caves = true; | |
| 65 | + | int water_level = 7; // Changed to match Tibia coordinates (7 = ground level) | |
| 66 | + | double exponent = 1.4; | |
| 67 | + | double linear = 6.0; | |
| 68 | + | std::string mountain_type = "MOUNTAIN"; | |
| 69 | + | ||
| 70 | + | // Configurable terrain layers instead of hardcoded items | |
| 71 | + | std::vector<TerrainLayer> terrain_layers; | |
| 72 | + | ||
| 73 | + | // Cave configuration | |
| 74 | + | std::string cave_brush_name = "cave"; | |
| 75 | + | uint16_t cave_item_id = 351; // Default cave floor from grounds.xml | |
| 76 | + | ||
| 77 | + | // Water configuration | |
| 78 | + | std::string water_brush_name = "sea"; | |
| 79 | + | uint16_t water_item_id = 4608; // Default water from grounds.xml | |
| 80 | + | ||
| 81 | + | // Frequency weights for noise generation | |
| 82 | + | struct FrequencyWeight { | |
| 83 | + | double frequency; | |
| 84 | + | double weight; | |
| 85 | + | }; | |
| 86 | + | ||
| 87 | + | std::vector<FrequencyWeight> frequencies = { | |
| 88 | + | {1.0, 0.3}, {2.0, 0.2}, {4.0, 0.2}, {8.0, 0.125}, | |
| 89 | + | {16.0, 0.1}, {32.0, 0.05}, {64.0, 0.0025} | |
| 90 | + | }; | |
| 91 | + | ||
| 92 | + | // Initialize with default terrain layers | |
| 93 | + | void initializeDefaultLayers() { | |
| 94 | + | terrain_layers.clear(); | |
| 95 | + | ||
| 96 | + | // Water layer (lowest) | |
| 97 | + | TerrainLayer water; | |
| 98 | + | water.name = "Water"; | |
| 99 | + | water.brush_name = "sea"; | |
| 100 | + | water.item_id = 4608; | |
| 101 | + | water.height_min = -1.0; | |
| 102 | + | water.height_max = 0.0; | |
| 103 | + | water.moisture_min = -1.0; | |
| 104 | + | water.moisture_max = 1.0; | |
| 105 | + | water.noise_scale = 1.0; | |
| 106 | + | water.coverage = 1.0; | |
| 107 | + | water.use_borders = true; | |
| 108 | + | water.z_order = 6000; | |
| 109 | + | terrain_layers.push_back(water); | |
| 110 | + | ||
| 111 | + | // Grass layer (main land) | |
| 112 | + | TerrainLayer grass; | |
| 113 | + | grass.name = "Grass"; | |
| 114 | + | grass.brush_name = "grass"; | |
| 115 | + | grass.item_id = 4526; | |
| 116 | + | grass.height_min = 0.0; | |
| 117 | + | grass.height_max = 0.7; | |
| 118 | + | grass.moisture_min = -0.5; | |
| 119 | + | grass.moisture_max = 1.0; | |
| 120 | + | grass.noise_scale = 1.0; | |
| 121 | + | grass.coverage = 1.0; | |
| 122 | + | grass.use_borders = true; | |
| 123 | + | grass.z_order = 3500; | |
| 124 | + | terrain_layers.push_back(grass); | |
| 125 | + | ||
| 126 | + | // Sand layer (dry areas) | |
| 127 | + | TerrainLayer sand; | |
| 128 | + | sand.name = "Sand"; | |
| 129 | + | sand.brush_name = "sand"; | |
| 130 | + | sand.item_id = 231; | |
| 131 | + | sand.height_min = 0.0; | |
| 132 | + | sand.height_max = 0.6; | |
| 133 | + | sand.moisture_min = -1.0; | |
| 134 | + | sand.moisture_max = -0.6; | |
| 135 | + | sand.noise_scale = 1.5; | |
| 136 | + | sand.coverage = 1.0; | |
| 137 | + | sand.use_borders = true; | |
| 138 | + | sand.z_order = 3400; | |
| 139 | + | sand.enabled = sand_biome; | |
| 140 | + | terrain_layers.push_back(sand); | |
| 141 | + | ||
| 142 | + | // Mountain layer (high elevation) | |
| 143 | + | TerrainLayer mountain; | |
| 144 | + | mountain.name = "Mountain"; | |
| 145 | + | mountain.brush_name = "mountain"; | |
| 146 | + | mountain.item_id = 919; // From grounds.xml mountain brush | |
| 147 | + | mountain.height_min = 0.7; | |
| 148 | + | mountain.height_max = 1.0; | |
| 149 | + | mountain.moisture_min = -1.0; | |
| 150 | + | mountain.moisture_max = 1.0; | |
| 151 | + | mountain.noise_scale = 0.8; | |
| 152 | + | mountain.coverage = 1.0; | |
| 153 | + | mountain.use_borders = true; | |
| 154 | + | mountain.z_order = 9900; | |
| 155 | + | terrain_layers.push_back(mountain); | |
| 156 | + | } | |
| 157 | + | }; | |
| 158 | + | ||
| 159 | + | class SimplexNoise { | |
| 160 | + | public: | |
| 161 | + | SimplexNoise(unsigned int seed = 0); | |
| 162 | + | ||
| 163 | + | double noise(double x, double y); | |
| 164 | + | double fractal(double x, double y, const std::vector<GenerationConfig::FrequencyWeight>& frequencies); | |
| 165 | + | ||
| 166 | + | private: | |
| 167 | + | static const int SIMPLEX[64][4]; | |
| 168 | + | static const double F2, G2; | |
| 169 | + | ||
| 170 | + | int perm[512]; | |
| 171 | + | int permMod12[512]; | |
| 172 | + | ||
| 173 | + | void initializePermutation(unsigned int seed); | |
| 174 | + | double dot(const int g[], double x, double y); | |
| 175 | + | int fastfloor(double x); | |
| 176 | + | }; | |
| 177 | + | ||
| 178 | + | class OTMapGenerator { | |
| 179 | + | public: | |
| 180 | + | OTMapGenerator(); | |
| 181 | + | ~OTMapGenerator(); | |
| 182 | + | ||
| 183 | + | // Main generation function | |
| 184 | + | bool generateMap(BaseMap* map, const GenerationConfig& config); | |
| 185 | + | ||
| 186 | + | // Preview generation | |
| 187 | + | std::vector<std::vector<uint16_t>> generateLayers(const GenerationConfig& config); | |
| 188 | + | ||
| 189 | + | // Core generation methods | |
| 190 | + | std::vector<std::vector<double>> generateHeightMap(const GenerationConfig& config); | |
| 191 | + | std::vector<std::vector<double>> generateMoistureMap(const GenerationConfig& config); | |
| 192 | + | std::vector<std::vector<uint16_t>> generateTerrainLayer(const std::vector<std::vector<double>>& heightMap, | |
| 193 | + | const std::vector<std::vector<double>>& moistureMap, | |
| 194 | + | const GenerationConfig& config); | |
| 195 | + | ||
| 196 | + | // Helper methods for multi-floor generation | |
| 197 | + | void fillColumn(std::vector<std::vector<std::vector<uint16_t>>>& layers, | |
| 198 | + | int x, int y, int elevation, uint16_t surfaceTileId, | |
| 199 | + | const GenerationConfig& config); | |
| 200 | + | uint16_t getTerrainTileId(double height, double moisture, const GenerationConfig& config); | |
| 201 | + | const TerrainLayer* selectTerrainLayer(double height, double moisture, const GenerationConfig& config); | |
| 202 | + | ||
| 203 | + | private: | |
| 204 | + | SimplexNoise* noise_generator; | |
| 205 | + | std::mt19937 rng; | |
| 206 | + | ||
| 207 | + | // Cave generation | |
| 208 | + | std::vector<std::vector<uint16_t>> generateCaveLayer(const GenerationConfig& config); | |
| 209 | + | ||
| 210 | + | // Utility functions | |
| 211 | + | double getDistance(int x, int y, int centerX, int centerY, bool euclidean = false); | |
| 212 | + | double smoothstep(double edge0, double edge1, double x); | |
| 213 | + | void seedRandom(const std::string& seed); | |
| 214 | + | ||
| 215 | + | // Border generation (integrate with brush system) | |
| 216 | + | void generateBorders(BaseMap* map, const GenerationConfig& config); | |
| 217 | + | void addBordersToTile(BaseMap* map, Tile* tile, int x, int y, int z); | |
| 218 | + | ||
| 219 | + | // Decoration placement | |
| 220 | + | void addClutter(BaseMap* map, const GenerationConfig& config); | |
| 221 | + | void placeTreesAndVegetation(BaseMap* map, Tile* tile, uint16_t groundId); | |
| 222 | + | void placeStones(BaseMap* map, Tile* tile, uint16_t groundId); | |
| 223 | + | void placeCaveDecorations(BaseMap* map, Tile* tile); | |
| 224 | + | }; | |
| 225 | + | ||
| 226 | + | // Helper functions for tile creation and manipulation | |
| 227 | + | namespace OTMapGenUtils { | |
| 228 | + | Tile* getOrCreateTile(BaseMap* map, int x, int y, int z); | |
| 229 | + | void setGroundTile(Tile* tile, uint16_t itemId); | |
| 230 | + | void addDecoration(Tile* tile, uint16_t itemId); | |
| 231 | + | bool isWaterTile(uint16_t itemId); | |
| 232 | + | bool isLandTile(uint16_t itemId); | |
| 233 | + | bool isMountainTile(uint16_t itemId); | |
| 234 | + | ||
| 235 | + | // Brush system integration | |
| 236 | + | std::vector<std::string> getAvailableBrushes(); | |
| 237 | + | uint16_t getPrimaryItemFromBrush(const std::string& brushName); | |
| 238 | + | bool applyBrushToTile(BaseMap* map, Tile* tile, const std::string& brushName, int x, int y, int z); | |
| 239 | + | } | |
| 240 | + | ||
| 241 | + | #endif |