| @@ -0,0 +1,311 @@ | |||
| 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 "carpet_brush.h" | |
| 21 | + | ||
| 22 | + | #include "basemap.h" | |
| 23 | + | #include "items.h" | |
| 24 | + | ||
| 25 | + | //============================================================================= | |
| 26 | + | // Carpet brush | |
| 27 | + | ||
| 28 | + | uint32_t CarpetBrush::carpet_types[256]; | |
| 29 | + | ||
| 30 | + | CarpetBrush::CarpetBrush() : | |
| 31 | + | look_id(0) { | |
| 32 | + | //// | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | CarpetBrush::~CarpetBrush() { | |
| 36 | + | //// | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | bool CarpetBrush::load(pugi::xml_node node, wxArrayString& warnings) { | |
| 40 | + | pugi::xml_attribute attribute; | |
| 41 | + | if ((attribute = node.attribute("lookid"))) { | |
| 42 | + | look_id = attribute.as_ushort(); | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | if ((attribute = node.attribute("server_lookid"))) { | |
| 46 | + | look_id = g_items[attribute.as_ushort()].clientID; | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) { | |
| 50 | + | if (as_lower_str(childNode.name()) != "carpet") { | |
| 51 | + | continue; | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | uint32_t alignment; | |
| 55 | + | if ((attribute = childNode.attribute("align"))) { | |
| 56 | + | const std::string& alignString = attribute.as_string(); | |
| 57 | + | alignment = AutoBorder::edgeNameToID(alignString); | |
| 58 | + | if (alignment == BORDER_NONE) { | |
| 59 | + | if (alignString == "center") { | |
| 60 | + | alignment = CARPET_CENTER; | |
| 61 | + | } else { | |
| 62 | + | warnings.push_back("Invalid alignment of carpet node\n"); | |
| 63 | + | continue; | |
| 64 | + | } | |
| 65 | + | } | |
| 66 | + | } else { | |
| 67 | + | warnings.push_back("Could not read alignment tag of carpet node\n"); | |
| 68 | + | continue; | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | bool use_local_id = true; | |
| 72 | + | for (pugi::xml_node subChildNode = childNode.first_child(); subChildNode; subChildNode = subChildNode.next_sibling()) { | |
| 73 | + | if (as_lower_str(subChildNode.name()) != "item") { | |
| 74 | + | continue; | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | use_local_id = false; | |
| 78 | + | if (!(attribute = subChildNode.attribute("id"))) { | |
| 79 | + | warnings.push_back("Could not read id tag of item node\n"); | |
| 80 | + | continue; | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | int32_t id = attribute.as_int(); | |
| 84 | + | if (!(attribute = subChildNode.attribute("chance"))) { | |
| 85 | + | warnings.push_back("Could not read chance tag of item node\n"); | |
| 86 | + | continue; | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | int32_t chance = attribute.as_int(); | |
| 90 | + | ||
| 91 | + | ItemType& it = g_items[id]; | |
| 92 | + | if (it.id == 0) { | |
| 93 | + | warnings.push_back("There is no itemtype with id " + std::to_string(id)); | |
| 94 | + | continue; | |
| 95 | + | } else if (it.brush && it.brush != this) { | |
| 96 | + | warnings.push_back("Itemtype id " + std::to_string(id) + " already has a brush"); | |
| 97 | + | continue; | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | it.isCarpet = true; | |
| 101 | + | it.brush = this; | |
| 102 | + | ||
| 103 | + | auto& alignItem = carpet_items[alignment]; | |
| 104 | + | alignItem.total_chance += chance; | |
| 105 | + | ||
| 106 | + | CarpetType t; | |
| 107 | + | t.id = id; | |
| 108 | + | t.chance = chance; | |
| 109 | + | ||
| 110 | + | alignItem.items.push_back(t); | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | if (use_local_id) { | |
| 114 | + | if (!(attribute = childNode.attribute("id"))) { | |
| 115 | + | warnings.push_back("Could not read id tag of carpet node\n"); | |
| 116 | + | continue; | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | uint16_t id = attribute.as_ushort(); | |
| 120 | + | ||
| 121 | + | ItemType& it = g_items[id]; | |
| 122 | + | if (it.id == 0) { | |
| 123 | + | warnings.push_back("There is no itemtype with id " + std::to_string(id)); | |
| 124 | + | return false; | |
| 125 | + | } else if (it.brush && it.brush != this) { | |
| 126 | + | warnings.push_back("Itemtype id " + std::to_string(id) + " already has a brush"); | |
| 127 | + | return false; | |
| 128 | + | } | |
| 129 | + | ||
| 130 | + | it.isCarpet = true; | |
| 131 | + | it.brush = this; | |
| 132 | + | ||
| 133 | + | auto& alignItem = carpet_items[alignment]; | |
| 134 | + | alignItem.total_chance = 1; | |
| 135 | + | ||
| 136 | + | CarpetType carpetType; | |
| 137 | + | carpetType.id = id; | |
| 138 | + | carpetType.chance = 1; | |
| 139 | + | ||
| 140 | + | alignItem.items.push_back(carpetType); | |
| 141 | + | } | |
| 142 | + | } | |
| 143 | + | return true; | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | bool CarpetBrush::canDraw(BaseMap* map, const Position& position) const { | |
| 147 | + | return true; | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | void CarpetBrush::draw(BaseMap* map, Tile* tile, void* parameter) { | |
| 151 | + | // Only remove old carpets if layering is disabled | |
| 152 | + | if (!g_settings.getBoolean(Config::LAYER_CARPETS)) { | |
| 153 | + | undraw(map, tile); | |
| 154 | + | } | |
| 155 | + | tile->addItem(Item::Create(getRandomCarpet(CARPET_CENTER))); | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | void CarpetBrush::undraw(BaseMap* map, Tile* tile) { | |
| 159 | + | auto& items = tile->items; | |
| 160 | + | for (auto it = items.begin(); it != items.end();) { | |
| 161 | + | Item* item = *it; | |
| 162 | + | if (item->isCarpet()) { | |
| 163 | + | CarpetBrush* carpetBrush = item->getCarpetBrush(); | |
| 164 | + | if (carpetBrush) { | |
| 165 | + | delete item; | |
| 166 | + | it = items.erase(it); | |
| 167 | + | } else { | |
| 168 | + | ++it; | |
| 169 | + | } | |
| 170 | + | } else { | |
| 171 | + | ++it; | |
| 172 | + | } | |
| 173 | + | } | |
| 174 | + | } | |
| 175 | + | ||
| 176 | + | void CarpetBrush::doCarpets(BaseMap* map, Tile* tile) { | |
| 177 | + | static const auto hasMatchingCarpetBrushAtTile = [](BaseMap* map, CarpetBrush* carpetBrush, uint32_t x, uint32_t y, uint32_t z) -> bool { | |
| 178 | + | Tile* tile = map->getTile(x, y, z); | |
| 179 | + | if (!tile) { | |
| 180 | + | return false; | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | for (Item* item : tile->items) { | |
| 184 | + | if (item->getCarpetBrush() == carpetBrush) { | |
| 185 | + | return true; | |
| 186 | + | } | |
| 187 | + | } | |
| 188 | + | return false; | |
| 189 | + | }; | |
| 190 | + | ||
| 191 | + | ASSERT(tile); | |
| 192 | + | if (!tile->hasCarpet()) { | |
| 193 | + | return; | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | const Position& position = tile->getPosition(); | |
| 197 | + | uint32_t x = position.x; | |
| 198 | + | uint32_t y = position.y; | |
| 199 | + | uint32_t z = position.z; | |
| 200 | + | /* | |
| 201 | + | static const std::pair<int32_t, int32_t> positionOffset[8] = { | |
| 202 | + | {-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1} | |
| 203 | + | }; | |
| 204 | + | ||
| 205 | + | const auto& offset = positionOffset[i]; | |
| 206 | + | if(neighbours[i] && hasMatchingCarpetBrushAtTile(map, carpetBrush, x + offset.first, y + offset.second, z)) { | |
| 207 | + | // | |
| 208 | + | } | |
| 209 | + | */ | |
| 210 | + | for (Item* item : tile->items) { | |
| 211 | + | ASSERT(item); | |
| 212 | + | ||
| 213 | + | CarpetBrush* carpetBrush = item->getCarpetBrush(); | |
| 214 | + | if (!carpetBrush) { | |
| 215 | + | continue; | |
| 216 | + | } | |
| 217 | + | ||
| 218 | + | bool neighbours[8] = { false }; | |
| 219 | + | if (x == 0) { | |
| 220 | + | if (y == 0) { | |
| 221 | + | neighbours[0] = false; | |
| 222 | + | neighbours[1] = false; | |
| 223 | + | neighbours[2] = false; | |
| 224 | + | neighbours[3] = false; | |
| 225 | + | neighbours[4] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y, z); | |
| 226 | + | neighbours[5] = false; | |
| 227 | + | neighbours[6] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y + 1, z); | |
| 228 | + | neighbours[7] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y + 1, z); | |
| 229 | + | } else { | |
| 230 | + | neighbours[0] = false; | |
| 231 | + | neighbours[1] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y - 1, z); | |
| 232 | + | neighbours[2] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y - 1, z); | |
| 233 | + | neighbours[3] = false; | |
| 234 | + | neighbours[4] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y, z); | |
| 235 | + | neighbours[5] = false; | |
| 236 | + | neighbours[6] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y + 1, z); | |
| 237 | + | neighbours[7] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y + 1, z); | |
| 238 | + | } | |
| 239 | + | } else if (y == 0) { | |
| 240 | + | neighbours[0] = false; | |
| 241 | + | neighbours[1] = false; | |
| 242 | + | neighbours[2] = false; | |
| 243 | + | neighbours[3] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y, z); | |
| 244 | + | neighbours[4] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y, z); | |
| 245 | + | neighbours[5] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y + 1, z); | |
| 246 | + | neighbours[6] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y + 1, z); | |
| 247 | + | neighbours[7] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y + 1, z); | |
| 248 | + | } else { | |
| 249 | + | neighbours[0] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y - 1, z); | |
| 250 | + | neighbours[1] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y - 1, z); | |
| 251 | + | neighbours[2] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y - 1, z); | |
| 252 | + | neighbours[3] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y, z); | |
| 253 | + | neighbours[4] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y, z); | |
| 254 | + | neighbours[5] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x - 1, y + 1, z); | |
| 255 | + | neighbours[6] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x, y + 1, z); | |
| 256 | + | neighbours[7] = hasMatchingCarpetBrushAtTile(map, carpetBrush, x + 1, y + 1, z); | |
| 257 | + | } | |
| 258 | + | ||
| 259 | + | uint32_t tileData = 0; | |
| 260 | + | for (uint32_t i = 0; i < 8; ++i) { | |
| 261 | + | if (neighbours[i]) { | |
| 262 | + | // Same table as this one, calculate what border | |
| 263 | + | tileData |= static_cast<uint32_t>(1) << i; | |
| 264 | + | } | |
| 265 | + | } | |
| 266 | + | ||
| 267 | + | // border type is always valid. | |
| 268 | + | uint16_t id = carpetBrush->getRandomCarpet(static_cast<BorderType>(carpet_types[tileData])); | |
| 269 | + | if (id != 0) { | |
| 270 | + | item->setID(id); | |
| 271 | + | } | |
| 272 | + | } | |
| 273 | + | } | |
| 274 | + | ||
| 275 | + | uint16_t CarpetBrush::getRandomCarpet(BorderType alignment) { | |
| 276 | + | static const auto findRandomCarpet = [](const CarpetNode& node) -> uint16_t { | |
| 277 | + | int32_t chance = random(1, node.total_chance); | |
| 278 | + | for (const CarpetType& carpetType : node.items) { | |
| 279 | + | if (chance <= carpetType.chance) { | |
| 280 | + | return carpetType.id; | |
| 281 | + | } | |
| 282 | + | chance -= carpetType.chance; | |
| 283 | + | } | |
| 284 | + | return 0; | |
| 285 | + | }; | |
| 286 | + | ||
| 287 | + | CarpetNode node = carpet_items[alignment]; | |
| 288 | + | if (node.total_chance > 0) { | |
| 289 | + | return findRandomCarpet(node); | |
| 290 | + | } | |
| 291 | + | ||
| 292 | + | node = carpet_items[CARPET_CENTER]; | |
| 293 | + | if (alignment != CARPET_CENTER && node.total_chance > 0) { | |
| 294 | + | uint16_t id = findRandomCarpet(node); | |
| 295 | + | if (id != 0) { | |
| 296 | + | return id; | |
| 297 | + | } | |
| 298 | + | } | |
| 299 | + | ||
| 300 | + | // Find an item to place on the tile, first center, then the rest. | |
| 301 | + | for (int32_t i = 0; i < 12; ++i) { | |
| 302 | + | node = carpet_items[i]; | |
| 303 | + | if (node.total_chance > 0) { | |
| 304 | + | uint16_t id = findRandomCarpet(node); | |
| 305 | + | if (id != 0) { | |
| 306 | + | return id; | |
| 307 | + | } | |
| 308 | + | } | |
| 309 | + | } | |
| 310 | + | return 0; | |
| 311 | + | } |
| @@ -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_CARPET_BRUSH_H | |
| 19 | + | #define RME_CARPET_BRUSH_H | |
| 20 | + | ||
| 21 | + | #include "brush.h" | |
| 22 | + | ||
| 23 | + | //============================================================================= | |
| 24 | + | // Carpetbrush, for tables, and some things that behave like tables | |
| 25 | + | // and with tables I really mean counters. | |
| 26 | + | ||
| 27 | + | class CarpetBrush : public Brush { | |
| 28 | + | public: | |
| 29 | + | static void init(); | |
| 30 | + | ||
| 31 | + | CarpetBrush(); | |
| 32 | + | virtual ~CarpetBrush(); | |
| 33 | + | ||
| 34 | + | bool isCarpet() const { | |
| 35 | + | return true; | |
| 36 | + | } | |
| 37 | + | CarpetBrush* asCarpet() { | |
| 38 | + | return static_cast<CarpetBrush*>(this); | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | virtual bool load(pugi::xml_node node, wxArrayString& warnings); | |
| 42 | + | ||
| 43 | + | virtual bool canDraw(BaseMap* map, const Position& position) const; | |
| 44 | + | virtual void draw(BaseMap* map, Tile* tile, void* parameter); | |
| 45 | + | virtual void undraw(BaseMap* map, Tile* tile); | |
| 46 | + | ||
| 47 | + | static void doCarpets(BaseMap* map, Tile* tile); | |
| 48 | + | ||
| 49 | + | virtual bool canDrag() const { | |
| 50 | + | return true; | |
| 51 | + | } | |
| 52 | + | virtual bool needBorders() const { | |
| 53 | + | return true; | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | virtual int getLookID() const { | |
| 57 | + | return look_id; | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | virtual std::string getName() const { | |
| 61 | + | return name; | |
| 62 | + | } | |
| 63 | + | virtual void setName(const std::string& newName) { | |
| 64 | + | name = newName; | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | protected: | |
| 68 | + | uint16_t getRandomCarpet(BorderType alignment); | |
| 69 | + | ||
| 70 | + | struct CarpetType { | |
| 71 | + | int32_t chance; | |
| 72 | + | uint16_t id; | |
| 73 | + | }; | |
| 74 | + | ||
| 75 | + | struct CarpetNode { | |
| 76 | + | std::vector<CarpetType> items; | |
| 77 | + | int32_t total_chance; | |
| 78 | + | ||
| 79 | + | CarpetNode() : | |
| 80 | + | items(), total_chance(0) { } | |
| 81 | + | }; | |
| 82 | + | ||
| 83 | + | CarpetNode carpet_items[14]; | |
| 84 | + | std::string name; | |
| 85 | + | uint16_t look_id; | |
| 86 | + | ||
| 87 | + | static uint32_t carpet_types[256]; | |
| 88 | + | }; | |
| 89 | + | ||
| 90 | + | #endif |
| @@ -0,0 +1,625 @@ | |||
| 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 "settings.h" | |
| 21 | + | #include "filehandle.h" | |
| 22 | + | ||
| 23 | + | #include "gui.h" | |
| 24 | + | ||
| 25 | + | #include "client_version.h" | |
| 26 | + | #include "otml.h" | |
| 27 | + | #include <wx/dir.h> | |
| 28 | + | ||
| 29 | + | // Static methods to load/save | |
| 30 | + | ||
| 31 | + | ClientVersion::VersionMap ClientVersion::client_versions; | |
| 32 | + | ClientVersion* ClientVersion::latest_version = nullptr; | |
| 33 | + | ClientVersion::OtbMap ClientVersion::otb_versions; | |
| 34 | + | ||
| 35 | + | void ClientVersion::loadVersions() { | |
| 36 | + | // Clean up old stuff | |
| 37 | + | ClientVersion::unloadVersions(); | |
| 38 | + | ||
| 39 | + | // Locate the clients.xml file | |
| 40 | + | wxFileName file_to_load; | |
| 41 | + | ||
| 42 | + | wxFileName exec_dir_client_xml; | |
| 43 | + | exec_dir_client_xml.Assign(g_gui.GetExecDirectory()); | |
| 44 | + | exec_dir_client_xml.SetFullName("clients.xml"); | |
| 45 | + | ||
| 46 | + | wxFileName data_dir_client_xml; | |
| 47 | + | data_dir_client_xml.Assign(g_gui.GetDataDirectory()); | |
| 48 | + | data_dir_client_xml.SetFullName("clients.xml"); | |
| 49 | + | ||
| 50 | + | wxFileName work_dir_client_xml; | |
| 51 | + | work_dir_client_xml.Assign(g_gui.getFoundDataDirectory()); | |
| 52 | + | work_dir_client_xml.SetFullName("clients.xml"); | |
| 53 | + | ||
| 54 | + | file_to_load = exec_dir_client_xml; | |
| 55 | + | if (!file_to_load.FileExists()) { | |
| 56 | + | file_to_load = data_dir_client_xml; | |
| 57 | + | if (!file_to_load.FileExists()) { | |
| 58 | + | file_to_load = work_dir_client_xml; | |
| 59 | + | if (!file_to_load.FileExists()) { | |
| 60 | + | file_to_load.Clear(); | |
| 61 | + | } | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | if (!file_to_load.FileExists()) { | |
| 66 | + | wxLogError(wxString() + "Could not load clients.xml, editor will NOT be able to load any client data files.\n" + "Checked paths:\n" + exec_dir_client_xml.GetFullPath() + "\n" + data_dir_client_xml.GetFullPath() + "\n" + work_dir_client_xml.GetFullPath()); | |
| 67 | + | return; | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | // Parse the file | |
| 71 | + | pugi::xml_document doc; | |
| 72 | + | pugi::xml_parse_result result = doc.load_file(file_to_load.GetFullPath().mb_str()); | |
| 73 | + | if (result) { | |
| 74 | + | pugi::xml_node node = doc.child("client_config"); | |
| 75 | + | if (!node) { | |
| 76 | + | wxLogError("Could not load clients.xml (syntax error), editor will NOT be able to load any client data files."); | |
| 77 | + | return; | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) { | |
| 81 | + | const std::string& childName = as_lower_str(childNode.name()); | |
| 82 | + | if (childName == "otbs") { | |
| 83 | + | for (pugi::xml_node otbNode = childNode.first_child(); otbNode; otbNode = otbNode.next_sibling()) { | |
| 84 | + | if (as_lower_str(otbNode.name()) == "otb") { | |
| 85 | + | loadOTBInfo(otbNode); | |
| 86 | + | } | |
| 87 | + | } | |
| 88 | + | } else if (childName == "clients") { | |
| 89 | + | for (pugi::xml_node versionNode = childNode.first_child(); versionNode; versionNode = versionNode.next_sibling()) { | |
| 90 | + | if (as_lower_str(versionNode.name()) == "client") { | |
| 91 | + | loadVersion(versionNode); | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | } | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) { | |
| 98 | + | if (as_lower_str(childNode.name()) != "clients") { | |
| 99 | + | continue; | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | for (pugi::xml_node versionNode = childNode.first_child(); versionNode; versionNode = versionNode.next_sibling()) { | |
| 103 | + | if (as_lower_str(versionNode.name()) == "client") { | |
| 104 | + | loadVersionExtensions(versionNode); | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | } | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | // Assign a default if there isn't one. | |
| 111 | + | if (!latest_version && !client_versions.empty()) { | |
| 112 | + | latest_version = client_versions.begin()->second; | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | // Load the data directory info | |
| 116 | + | try { | |
| 117 | + | json::mValue read_obj; | |
| 118 | + | json::read_or_throw(g_settings.getString(Config::ASSETS_DATA_DIRS), read_obj); | |
| 119 | + | json::mArray& vers_obj = read_obj.get_array(); | |
| 120 | + | for (json::mArray::iterator ver_iter = vers_obj.begin(); ver_iter != vers_obj.end(); ++ver_iter) { | |
| 121 | + | json::mObject& ver_obj = ver_iter->get_obj(); | |
| 122 | + | ClientVersion* version = get(ver_obj["id"].get_str()); | |
| 123 | + | if (version == nullptr) { | |
| 124 | + | continue; | |
| 125 | + | } | |
| 126 | + | version->setClientPath(wxstr(ver_obj["path"].get_str())); | |
| 127 | + | } | |
| 128 | + | } catch (std::runtime_error&) { | |
| 129 | + | // pass | |
| 130 | + | ; | |
| 131 | + | } | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | void ClientVersion::unloadVersions() { | |
| 135 | + | for (VersionMap::iterator it = client_versions.begin(); it != client_versions.end(); ++it) { | |
| 136 | + | delete it->second; | |
| 137 | + | } | |
| 138 | + | client_versions.clear(); | |
| 139 | + | latest_version = nullptr; | |
| 140 | + | otb_versions.clear(); | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | void ClientVersion::loadOTBInfo(pugi::xml_node otbNode) { | |
| 144 | + | if (as_lower_str(otbNode.name()) != "otb") { | |
| 145 | + | return; | |
| 146 | + | } | |
| 147 | + | ||
| 148 | + | pugi::xml_attribute attribute; | |
| 149 | + | if (!(attribute = otbNode.attribute("client"))) { | |
| 150 | + | wxLogError("Node 'otb' must contain 'client' tag."); | |
| 151 | + | return; | |
| 152 | + | } | |
| 153 | + | ||
| 154 | + | OtbVersion otb = { "", OTB_VERSION_3, CLIENT_VERSION_NONE }; | |
| 155 | + | otb.name = attribute.as_string(); | |
| 156 | + | if (!(attribute = otbNode.attribute("id"))) { | |
| 157 | + | wxLogError("Node 'otb' must contain 'id' tag."); | |
| 158 | + | return; | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | otb.id = static_cast<ClientVersionID>(attribute.as_int()); | |
| 162 | + | if (!(attribute = otbNode.attribute("version"))) { | |
| 163 | + | wxLogError("Node 'otb' must contain 'version' tag."); | |
| 164 | + | return; | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | OtbFormatVersion versionId = static_cast<OtbFormatVersion>(attribute.as_uint()); | |
| 168 | + | if (versionId < OTB_VERSION_1 || versionId > OTB_VERSION_3) { | |
| 169 | + | wxLogError("Node 'otb' unrecognized format version (version 1..3 supported)."); | |
| 170 | + | return; | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | otb.format_version = versionId; | |
| 174 | + | otb_versions[otb.name] = otb; | |
| 175 | + | } | |
| 176 | + | ||
| 177 | + | void ClientVersion::loadVersion(pugi::xml_node versionNode) { | |
| 178 | + | pugi::xml_attribute attribute; | |
| 179 | + | if (!(attribute = versionNode.attribute("name"))) { | |
| 180 | + | wxLogError("Node 'client' must contain 'name', 'data_directory' and 'otb' tags."); | |
| 181 | + | return; | |
| 182 | + | } | |
| 183 | + | ||
| 184 | + | const std::string& versionName = attribute.as_string(); | |
| 185 | + | if (!(attribute = versionNode.attribute("data_directory"))) { | |
| 186 | + | wxLogError("Node 'client' must contain 'name', 'data_directory' and 'otb' tags."); | |
| 187 | + | return; | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | const std::string& dataPath = attribute.as_string(); | |
| 191 | + | if (!(attribute = versionNode.attribute("otb"))) { | |
| 192 | + | wxLogError("Node 'client' must contain 'name', 'data_directory' and 'otb' tags."); | |
| 193 | + | return; | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | const std::string& otbVersionName = attribute.as_string(); | |
| 197 | + | if (otb_versions.find(otbVersionName) == otb_versions.end()) { | |
| 198 | + | wxLogError("Node 'client' 'otb' tag is invalid (couldn't find this otb version)."); | |
| 199 | + | return; | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | ClientVersion* version = newd ClientVersion(otb_versions[otbVersionName], versionName, wxstr(dataPath)); | |
| 203 | + | ||
| 204 | + | bool should_be_default = versionNode.attribute("default").as_bool(); | |
| 205 | + | version->visible = versionNode.attribute("visible").as_bool(); | |
| 206 | + | ||
| 207 | + | for (pugi::xml_node childNode = versionNode.first_child(); childNode; childNode = childNode.next_sibling()) { | |
| 208 | + | const std::string& childName = as_lower_str(childNode.name()); | |
| 209 | + | if (childName == "otbm") { | |
| 210 | + | if (!(attribute = childNode.attribute("version"))) { | |
| 211 | + | wxLogError("Node 'otbm' missing version."); | |
| 212 | + | continue; | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | int32_t otbmVersion = attribute.as_int() - 1; | |
| 216 | + | if (otbmVersion < MAP_OTBM_1 || otbmVersion > MAP_OTBM_4) { | |
| 217 | + | wxLogError("Node 'otbm' unsupported version."); | |
| 218 | + | continue; | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | if (childNode.attribute("preffered").as_bool() || version->preferred_map_version == MAP_OTBM_UNKNOWN) { | |
| 222 | + | version->preferred_map_version = static_cast<MapVersionID>(otbmVersion); | |
| 223 | + | } | |
| 224 | + | version->map_versions_supported.push_back(version->preferred_map_version); | |
| 225 | + | } else if (childName == "fucked_up_charges") { | |
| 226 | + | version->usesFuckedUpCharges = true; | |
| 227 | + | } else if (childName == "data") { | |
| 228 | + | ||
| 229 | + | if (!(attribute = childNode.attribute("format"))) { | |
| 230 | + | wxLogError("Node 'data' does not have 'format' tag."); | |
| 231 | + | continue; | |
| 232 | + | } | |
| 233 | + | ||
| 234 | + | const std::string& format = attribute.as_string(); | |
| 235 | + | ClientData client_data = { DAT_FORMAT_74, 0, 0 }; | |
| 236 | + | if (format == "7.4") { | |
| 237 | + | client_data.datFormat = DAT_FORMAT_74; | |
| 238 | + | } else if (format == "7.55") { | |
| 239 | + | client_data.datFormat = DAT_FORMAT_755; | |
| 240 | + | } else if (format == "7.8") { | |
| 241 | + | client_data.datFormat = DAT_FORMAT_78; | |
| 242 | + | } else if (format == "8.6") { | |
| 243 | + | client_data.datFormat = DAT_FORMAT_86; | |
| 244 | + | } else if (format == "9.6") { | |
| 245 | + | client_data.datFormat = DAT_FORMAT_96; | |
| 246 | + | } else if (format == "10.10") { | |
| 247 | + | client_data.datFormat = DAT_FORMAT_1010; | |
| 248 | + | } else if (format == "10.50") { | |
| 249 | + | client_data.datFormat = DAT_FORMAT_1050; | |
| 250 | + | } else if (format == "10.57") { | |
| 251 | + | client_data.datFormat = DAT_FORMAT_1057; | |
| 252 | + | } else { | |
| 253 | + | wxLogError("Node 'data' 'format' is invalid (7.4, 7.55, 7.8, 8.6, 9.6, 10.10, 10.50, 10.57 are supported)"); | |
| 254 | + | continue; | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | if (!(attribute = childNode.attribute("dat")) || !wxString(attribute.as_string(), wxConvUTF8).ToULong((unsigned long*)&client_data.datSignature, 16)) { | |
| 258 | + | wxLogError("Node 'data' 'dat' tag is not hex-formatted."); | |
| 259 | + | continue; | |
| 260 | + | } | |
| 261 | + | ||
| 262 | + | if (!(attribute = childNode.attribute("spr")) || !wxString(attribute.as_string(), wxConvUTF8).ToULong((unsigned long*)&client_data.sprSignature, 16)) { | |
| 263 | + | wxLogError("Node 'data' 'spr' tag is not hex-formatted."); | |
| 264 | + | continue; | |
| 265 | + | } | |
| 266 | + | ||
| 267 | + | version->data_versions.push_back(client_data); | |
| 268 | + | } | |
| 269 | + | } | |
| 270 | + | ||
| 271 | + | if (client_versions[version->getID()]) { | |
| 272 | + | wxLogError("Duplicate version id %i, discarding version '%s'.", version->getID(), version->name); | |
| 273 | + | delete version; | |
| 274 | + | return; | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | client_versions[version->getID()] = version; | |
| 278 | + | if (should_be_default) { | |
| 279 | + | latest_version = version; | |
| 280 | + | } | |
| 281 | + | } | |
| 282 | + | ||
| 283 | + | void ClientVersion::loadVersionExtensions(pugi::xml_node versionNode) { | |
| 284 | + | pugi::xml_attribute attribute; | |
| 285 | + | if (!(attribute = versionNode.attribute("name"))) { | |
| 286 | + | // Error has already been displayed earlier, no need to show another error about the same thing | |
| 287 | + | return; | |
| 288 | + | } | |
| 289 | + | ||
| 290 | + | ClientVersion* version = get(attribute.as_string()); | |
| 291 | + | if (!version) { | |
| 292 | + | // Same rationale as above | |
| 293 | + | return; | |
| 294 | + | } | |
| 295 | + | ||
| 296 | + | for (pugi::xml_node childNode = versionNode.first_child(); childNode; childNode = childNode.next_sibling()) { | |
| 297 | + | if (as_lower_str(childNode.name()) != "extensions") { | |
| 298 | + | continue; | |
| 299 | + | } | |
| 300 | + | ||
| 301 | + | const std::string& from = childNode.attribute("from").as_string(); | |
| 302 | + | const std::string& to = childNode.attribute("to").as_string(); | |
| 303 | + | ||
| 304 | + | ClientVersion* fromVersion = get(from); | |
| 305 | + | ClientVersion* toVersion = get(to); | |
| 306 | + | ||
| 307 | + | if (!fromVersion && !toVersion) { | |
| 308 | + | wxLogError("Unknown client extension data."); | |
| 309 | + | continue; | |
| 310 | + | } | |
| 311 | + | ||
| 312 | + | if (!fromVersion) { | |
| 313 | + | fromVersion = client_versions.begin()->second; | |
| 314 | + | } | |
| 315 | + | ||
| 316 | + | if (!toVersion) { | |
| 317 | + | toVersion = client_versions.rbegin()->second; | |
| 318 | + | } | |
| 319 | + | ||
| 320 | + | for (const auto& versionEntry : client_versions) { | |
| 321 | + | ClientVersion* version = versionEntry.second; | |
| 322 | + | if (version->getID() >= fromVersion->getID() && version->getID() <= toVersion->getID()) { | |
| 323 | + | version->extension_versions.push_back(version); | |
| 324 | + | } | |
| 325 | + | } | |
| 326 | + | ||
| 327 | + | std::sort(version->extension_versions.begin(), version->extension_versions.end(), VersionComparisonPredicate); | |
| 328 | + | } | |
| 329 | + | } | |
| 330 | + | ||
| 331 | + | void ClientVersion::saveVersions() { | |
| 332 | + | json::Array vers_obj; | |
| 333 | + | ||
| 334 | + | for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { | |
| 335 | + | ClientVersion* version = i->second; | |
| 336 | + | json::Object ver_obj; | |
| 337 | + | ver_obj.push_back(json::Pair("id", version->getName())); | |
| 338 | + | ver_obj.push_back(json::Pair("path", nstr(version->getClientPath().GetFullPath()))); | |
| 339 | + | vers_obj.push_back(ver_obj); | |
| 340 | + | } | |
| 341 | + | std::ostringstream out; | |
| 342 | + | json::write(vers_obj, out); | |
| 343 | + | g_settings.setString(Config::ASSETS_DATA_DIRS, out.str()); | |
| 344 | + | } | |
| 345 | + | ||
| 346 | + | // Client version class | |
| 347 | + | ||
| 348 | + | ClientVersion::ClientVersion(OtbVersion otb, std::string versionName, wxString path) : | |
| 349 | + | otb(otb), | |
| 350 | + | name(versionName), | |
| 351 | + | visible(false), | |
| 352 | + | preferred_map_version(MAP_OTBM_UNKNOWN), | |
| 353 | + | data_path(path) { | |
| 354 | + | //// | |
| 355 | + | } | |
| 356 | + | ||
| 357 | + | ClientVersion* ClientVersion::get(ClientVersionID id) { | |
| 358 | + | VersionMap::iterator i = client_versions.find(id); | |
| 359 | + | if (i == client_versions.end()) { | |
| 360 | + | return nullptr; | |
| 361 | + | } | |
| 362 | + | return i->second; | |
| 363 | + | } | |
| 364 | + | ||
| 365 | + | ClientVersion* ClientVersion::get(std::string id) { | |
| 366 | + | for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { | |
| 367 | + | if (i->second->getName() == id) { | |
| 368 | + | return i->second; | |
| 369 | + | } | |
| 370 | + | } | |
| 371 | + | return nullptr; | |
| 372 | + | } | |
| 373 | + | ||
| 374 | + | ClientVersionList ClientVersion::getAll() { | |
| 375 | + | ClientVersionList l; | |
| 376 | + | for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { | |
| 377 | + | l.push_back(i->second); | |
| 378 | + | } | |
| 379 | + | return l; | |
| 380 | + | } | |
| 381 | + | ||
| 382 | + | ClientVersionList ClientVersion::getAllVisible() { | |
| 383 | + | ClientVersionList l; | |
| 384 | + | for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { | |
| 385 | + | if (i->second->isVisible()) { | |
| 386 | + | l.push_back(i->second); | |
| 387 | + | } | |
| 388 | + | } | |
| 389 | + | return l; | |
| 390 | + | } | |
| 391 | + | ||
| 392 | + | ClientVersionList ClientVersion::getAllForOTBMVersion(MapVersionID id) { | |
| 393 | + | ClientVersionList list; | |
| 394 | + | for (VersionMap::iterator i = client_versions.begin(); i != client_versions.end(); ++i) { | |
| 395 | + | if (i->second->isVisible()) { | |
| 396 | + | for (std::vector<MapVersionID>::iterator v = i->second->map_versions_supported.begin(); v != i->second->map_versions_supported.end(); ++v) { | |
| 397 | + | if (*v == id) { | |
| 398 | + | list.push_back(i->second); | |
| 399 | + | } | |
| 400 | + | } | |
| 401 | + | } | |
| 402 | + | } | |
| 403 | + | return list; | |
| 404 | + | } | |
| 405 | + | ||
| 406 | + | ClientVersion* ClientVersion::getLatestVersion() { | |
| 407 | + | return latest_version; | |
| 408 | + | } | |
| 409 | + | ||
| 410 | + | FileName ClientVersion::getDataPath() const { | |
| 411 | + | wxString basePath = g_gui.GetDataDirectory(); | |
| 412 | + | if (!wxFileName(basePath).DirExists()) { | |
| 413 | + | basePath = g_gui.getFoundDataDirectory(); | |
| 414 | + | } | |
| 415 | + | return basePath + data_path + FileName::GetPathSeparator(); | |
| 416 | + | } | |
| 417 | + | ||
| 418 | + | FileName ClientVersion::getLocalDataPath() const { | |
| 419 | + | FileName f = g_gui.GetLocalDataDirectory() + data_path + FileName::GetPathSeparator(); | |
| 420 | + | f.Mkdir(0755, wxPATH_MKDIR_FULL); | |
| 421 | + | return f; | |
| 422 | + | } | |
| 423 | + | ||
| 424 | + | bool ClientVersion::hasValidPaths() { | |
| 425 | + | if (!client_path.DirExists()) { | |
| 426 | + | return false; | |
| 427 | + | } | |
| 428 | + | ||
| 429 | + | wxDir dir(client_path.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR)); | |
| 430 | + | wxString otfi_file; | |
| 431 | + | metadata_path = wxFileName(client_path.GetFullPath(), wxString(ASSETS_NAME) + ".dat"); | |
| 432 | + | sprites_path = wxFileName(client_path.GetFullPath(), wxString(ASSETS_NAME) + ".spr"); | |
| 433 | + | ||
| 434 | + | if (dir.GetFirst(&otfi_file, "*.otfi", wxDIR_FILES)) { | |
| 435 | + | wxFileName otfi(client_path.GetFullPath(), otfi_file); | |
| 436 | + | OTMLDocumentPtr doc = OTMLDocument::parse(otfi.GetFullPath().ToStdString()); | |
| 437 | + | if (doc->size() != 0 && doc->hasChildAt("DatSpr")) { | |
| 438 | + | OTMLNodePtr node = doc->get("DatSpr"); | |
| 439 | + | std::string metadata = node->valueAt<std::string>("metadata-file", std::string(ASSETS_NAME) + ".dat"); | |
| 440 | + | std::string sprites = node->valueAt<std::string>("sprites-file", std::string(ASSETS_NAME) + ".spr"); | |
| 441 | + | metadata_path = wxFileName(client_path.GetFullPath(), wxString(metadata)); | |
| 442 | + | sprites_path = wxFileName(client_path.GetFullPath(), wxString(sprites)); | |
| 443 | + | } | |
| 444 | + | } | |
| 445 | + | ||
| 446 | + | if (!metadata_path.FileExists() || !sprites_path.FileExists()) { | |
| 447 | + | return false; | |
| 448 | + | } | |
| 449 | + | ||
| 450 | + | if (!g_settings.getInteger(Config::CHECK_SIGNATURES)) { | |
| 451 | + | return true; | |
| 452 | + | } | |
| 453 | + | ||
| 454 | + | // Peek the version of the files | |
| 455 | + | FileReadHandle dat_file(static_cast<const char*>(metadata_path.GetFullPath().mb_str())); | |
| 456 | + | if (!dat_file.isOk()) { | |
| 457 | + | wxLogError("Could not open metadata file."); | |
| 458 | + | return false; | |
| 459 | + | } | |
| 460 | + | ||
| 461 | + | uint32_t datSignature; | |
| 462 | + | dat_file.getU32(datSignature); | |
| 463 | + | dat_file.close(); | |
| 464 | + | ||
| 465 | + | FileReadHandle spr_file(static_cast<const char*>(sprites_path.GetFullPath().mb_str())); | |
| 466 | + | if (!spr_file.isOk()) { | |
| 467 | + | wxLogError("Could not open sprites file."); | |
| 468 | + | return false; | |
| 469 | + | } | |
| 470 | + | ||
| 471 | + | uint32_t sprSignature; | |
| 472 | + | spr_file.getU32(sprSignature); | |
| 473 | + | spr_file.close(); | |
| 474 | + | ||
| 475 | + | for (const auto& clientData : data_versions) { | |
| 476 | + | if (clientData.sprSignature == sprSignature && clientData.datSignature == datSignature) { | |
| 477 | + | return true; | |
| 478 | + | } | |
| 479 | + | } | |
| 480 | + | ||
| 481 | + | wxString message = "Signatures are incorrect.\n"; | |
| 482 | + | message << "Metadata signature: %X\n"; | |
| 483 | + | message << "Sprites signature: %X"; | |
| 484 | + | wxLogError(wxString::Format(message, datSignature, sprSignature)); | |
| 485 | + | return false; | |
| 486 | + | } | |
| 487 | + | ||
| 488 | + | bool ClientVersion::loadValidPaths() { | |
| 489 | + | while (!hasValidPaths()) { | |
| 490 | + | wxString message = "Could not locate metadata and/or sprite files, please navigate to your client assets %s installation folder.\n"; | |
| 491 | + | message << "Attempted metadata file: %s\n"; | |
| 492 | + | message << "Attempted sprites file: %s\n"; | |
| 493 | + | ||
| 494 | + | // Create a custom dialog with version selection | |
| 495 | + | wxDialog* dialog = new wxDialog(nullptr, wxID_ANY, "Select Client Version", wxDefaultPosition, wxSize(400, 200)); | |
| 496 | + | wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); | |
| 497 | + | ||
| 498 | + | // Add version selection dropdown | |
| 499 | + | wxStaticText* versionLabel = new wxStaticText(dialog, wxID_ANY, "Select Client Version:"); | |
| 500 | + | wxChoice* versionChoice = new wxChoice(dialog, wxID_ANY); | |
| 501 | + | ||
| 502 | + | // Add all visible versions to the dropdown | |
| 503 | + | ClientVersionList versions = ClientVersion::getAllVisible(); | |
| 504 | + | for (const auto& version : versions) { | |
| 505 | + | versionChoice->Append(wxstr(version->getName())); | |
| 506 | + | } | |
| 507 | + | ||
| 508 | + | // Set current version as default selection | |
| 509 | + | versionChoice->SetStringSelection(wxstr(name)); | |
| 510 | + | ||
| 511 | + | // Add force load checkbox | |
| 512 | + | wxCheckBox* forceLoadCheckbox = new wxCheckBox(dialog, wxID_ANY, "Force load version"); | |
| 513 | + | ||
| 514 | + | // Add directory selection | |
| 515 | + | wxString dirHelpText("Select assets directory."); | |
| 516 | + | wxDirPickerCtrl* dirPicker = new wxDirPickerCtrl(dialog, wxID_ANY, "", dirHelpText, wxDefaultPosition, wxDefaultSize, wxDIRP_USE_TEXTCTRL | wxDIRP_DIR_MUST_EXIST); | |
| 517 | + | ||
| 518 | + | // Add buttons | |
| 519 | + | wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL); | |
| 520 | + | wxButton* okButton = new wxButton(dialog, wxID_OK, "OK"); | |
| 521 | + | wxButton* cancelButton = new wxButton(dialog, wxID_CANCEL, "Cancel"); | |
| 522 | + | buttonSizer->Add(okButton, 0, wxALL, 5); | |
| 523 | + | buttonSizer->Add(cancelButton, 0, wxALL, 5); | |
| 524 | + | ||
| 525 | + | // Add all elements to main sizer | |
| 526 | + | mainSizer->Add(versionLabel, 0, wxALL, 5); | |
| 527 | + | mainSizer->Add(versionChoice, 0, wxEXPAND | wxALL, 5); | |
| 528 | + | mainSizer->Add(forceLoadCheckbox, 0, wxALL, 5); | |
| 529 | + | mainSizer->Add(dirPicker, 0, wxEXPAND | wxALL, 5); | |
| 530 | + | mainSizer->Add(buttonSizer, 0, wxALIGN_CENTER | wxALL, 5); | |
| 531 | + | ||
| 532 | + | dialog->SetSizer(mainSizer); | |
| 533 | + | ||
| 534 | + | // Show error message first | |
| 535 | + | g_gui.PopupDialog("Error", wxString::Format(message, name, metadata_path.GetFullPath(), sprites_path.GetFullPath()), wxOK); | |
| 536 | + | ||
| 537 | + | // Show the dialog | |
| 538 | + | int result = dialog->ShowModal(); | |
| 539 | + | if (result == wxID_CANCEL) { | |
| 540 | + | dialog->Destroy(); | |
| 541 | + | return false; | |
| 542 | + | } | |
| 543 | + | ||
| 544 | + | // Get selected version | |
| 545 | + | wxString selectedVersion = versionChoice->GetStringSelection(); | |
| 546 | + | bool forceLoad = forceLoadCheckbox->GetValue(); | |
| 547 | + | ||
| 548 | + | // If force load is checked, update the version | |
| 549 | + | if (forceLoad) { | |
| 550 | + | ClientVersion* newVersion = ClientVersion::get(nstr(selectedVersion)); | |
| 551 | + | if (newVersion) { | |
| 552 | + | // Copy individual members instead of using assignment operator | |
| 553 | + | otb = newVersion->otb; | |
| 554 | + | name = newVersion->name; | |
| 555 | + | visible = newVersion->visible; | |
| 556 | + | preferred_map_version = newVersion->preferred_map_version; | |
| 557 | + | data_path = newVersion->data_path; | |
| 558 | + | data_versions = newVersion->data_versions; | |
| 559 | + | map_versions_supported = newVersion->map_versions_supported; | |
| 560 | + | extension_versions = newVersion->extension_versions; | |
| 561 | + | usesFuckedUpCharges = newVersion->usesFuckedUpCharges; | |
| 562 | + | } | |
| 563 | + | } | |
| 564 | + | ||
| 565 | + | // Update client path | |
| 566 | + | client_path.Assign(dirPicker->GetPath() + FileName::GetPathSeparator()); | |
| 567 | + | ||
| 568 | + | dialog->Destroy(); | |
| 569 | + | } | |
| 570 | + | ||
| 571 | + | ClientVersion::saveVersions(); | |
| 572 | + | return true; | |
| 573 | + | } | |
| 574 | + | ||
| 575 | + | DatFormat ClientVersion::getDatFormatForSignature(uint32_t signature) const { | |
| 576 | + | for (std::vector<ClientData>::const_iterator iter = data_versions.begin(); iter != data_versions.end(); ++iter) { | |
| 577 | + | if (iter->datSignature == signature) { | |
| 578 | + | return iter->datFormat; | |
| 579 | + | } | |
| 580 | + | } | |
| 581 | + | ||
| 582 | + | return DAT_FORMAT_UNKNOWN; | |
| 583 | + | } | |
| 584 | + | ||
| 585 | + | std::string ClientVersion::getName() const { | |
| 586 | + | return name; | |
| 587 | + | } | |
| 588 | + | ||
| 589 | + | ClientVersionID ClientVersion::getID() const { | |
| 590 | + | return otb.id; | |
| 591 | + | } | |
| 592 | + | ||
| 593 | + | bool ClientVersion::isVisible() const { | |
| 594 | + | return visible; | |
| 595 | + | } | |
| 596 | + | ||
| 597 | + | void ClientVersion::setClientPath(const FileName& dir) { | |
| 598 | + | client_path.Assign(dir); | |
| 599 | + | } | |
| 600 | + | ||
| 601 | + | MapVersionID ClientVersion::getPrefferedMapVersionID() const { | |
| 602 | + | return preferred_map_version; | |
| 603 | + | } | |
| 604 | + | ||
| 605 | + | OtbVersion ClientVersion::getOTBVersion() const { | |
| 606 | + | return otb; | |
| 607 | + | } | |
| 608 | + | ||
| 609 | + | ClientVersionList ClientVersion::getExtensionsSupported() const { | |
| 610 | + | return extension_versions; | |
| 611 | + | } | |
| 612 | + | ||
| 613 | + | ClientVersionList ClientVersion::getAllVersionsSupportedForClientVersion(ClientVersion* clientVersion) { | |
| 614 | + | ClientVersionList versionList; | |
| 615 | + | for (const auto& versionEntry : client_versions) { | |
| 616 | + | ClientVersion* version = versionEntry.second; | |
| 617 | + | for (ClientVersion* checkVersion : version->getExtensionsSupported()) { | |
| 618 | + | if (clientVersion == checkVersion) { | |
| 619 | + | versionList.push_back(version); | |
| 620 | + | } | |
| 621 | + | } | |
| 622 | + | } | |
| 623 | + | std::sort(versionList.begin(), versionList.end(), VersionComparisonPredicate); | |
| 624 | + | return versionList; | |
| 625 | + | } |
| @@ -0,0 +1,276 @@ | |||
| 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_CLIENT_VERSION_H_ | |
| 19 | + | #define RME_CLIENT_VERSION_H_ | |
| 20 | + | ||
| 21 | + | #include "main.h" | |
| 22 | + | #include "settings.h" | |
| 23 | + | ||
| 24 | + | typedef int ClientVersionID; | |
| 25 | + | ||
| 26 | + | // Client versions | |
| 27 | + | enum ClientVersions { | |
| 28 | + | CLIENT_VERSION_NONE = -1, | |
| 29 | + | CLIENT_VERSION_ALL = -2, | |
| 30 | + | CLIENT_VERSION_750 = 1, | |
| 31 | + | CLIENT_VERSION_755 = 2, | |
| 32 | + | CLIENT_VERSION_760 = 3, | |
| 33 | + | CLIENT_VERSION_770 = 3, | |
| 34 | + | CLIENT_VERSION_780 = 4, | |
| 35 | + | CLIENT_VERSION_790 = 5, | |
| 36 | + | CLIENT_VERSION_792 = 6, | |
| 37 | + | CLIENT_VERSION_800 = 7, | |
| 38 | + | CLIENT_VERSION_810 = 8, | |
| 39 | + | CLIENT_VERSION_811 = 9, | |
| 40 | + | CLIENT_VERSION_820 = 10, // After this version, OTBM stores charges as an attribute | |
| 41 | + | CLIENT_VERSION_830 = 11, | |
| 42 | + | CLIENT_VERSION_840 = 12, | |
| 43 | + | CLIENT_VERSION_841 = 13, | |
| 44 | + | CLIENT_VERSION_842 = 14, | |
| 45 | + | CLIENT_VERSION_850 = 15, | |
| 46 | + | CLIENT_VERSION_854_BAD = 16, | |
| 47 | + | CLIENT_VERSION_854 = 17, | |
| 48 | + | CLIENT_VERSION_855 = 18, | |
| 49 | + | CLIENT_VERSION_860_OLD = 19, | |
| 50 | + | CLIENT_VERSION_860 = 20, | |
| 51 | + | CLIENT_VERSION_861 = 21, | |
| 52 | + | CLIENT_VERSION_862 = 22, | |
| 53 | + | CLIENT_VERSION_870 = 23, | |
| 54 | + | CLIENT_VERSION_871 = 24, | |
| 55 | + | CLIENT_VERSION_872 = 25, | |
| 56 | + | CLIENT_VERSION_873 = 26, | |
| 57 | + | CLIENT_VERSION_900 = 27, | |
| 58 | + | CLIENT_VERSION_910 = 28, | |
| 59 | + | CLIENT_VERSION_920 = 29, | |
| 60 | + | CLIENT_VERSION_940 = 30, | |
| 61 | + | CLIENT_VERSION_944_V1 = 31, | |
| 62 | + | CLIENT_VERSION_944_V2 = 32, | |
| 63 | + | CLIENT_VERSION_944_V3 = 33, | |
| 64 | + | CLIENT_VERSION_944_V4 = 34, | |
| 65 | + | CLIENT_VERSION_946 = 35, | |
| 66 | + | CLIENT_VERSION_950 = 36, | |
| 67 | + | CLIENT_VERSION_952 = 37, | |
| 68 | + | CLIENT_VERSION_953 = 38, | |
| 69 | + | CLIENT_VERSION_954 = 39, | |
| 70 | + | CLIENT_VERSION_960 = 40, | |
| 71 | + | CLIENT_VERSION_961 = 41, | |
| 72 | + | CLIENT_VERSION_963 = 42, | |
| 73 | + | CLIENT_VERSION_970 = 43, | |
| 74 | + | CLIENT_VERSION_980 = 44, | |
| 75 | + | CLIENT_VERSION_981 = 45, | |
| 76 | + | CLIENT_VERSION_982 = 46, | |
| 77 | + | CLIENT_VERSION_983 = 47, | |
| 78 | + | CLIENT_VERSION_985 = 48, | |
| 79 | + | CLIENT_VERSION_986 = 49, | |
| 80 | + | CLIENT_VERSION_1010 = 50, | |
| 81 | + | CLIENT_VERSION_1020 = 51, | |
| 82 | + | CLIENT_VERSION_1021 = 52 | |
| 83 | + | }; | |
| 84 | + | ||
| 85 | + | // OTBM versions | |
| 86 | + | enum MapVersionID { | |
| 87 | + | MAP_OTBM_UNKNOWN = -1, | |
| 88 | + | MAP_OTBM_1 = 0, | |
| 89 | + | MAP_OTBM_2 = 1, | |
| 90 | + | MAP_OTBM_3 = 2, | |
| 91 | + | MAP_OTBM_4 = 3, | |
| 92 | + | }; | |
| 93 | + | ||
| 94 | + | // The composed version of a otbm file (otbm version, client version) | |
| 95 | + | struct MapVersion { | |
| 96 | + | MapVersion() : | |
| 97 | + | otbm(MAP_OTBM_1), client(CLIENT_VERSION_NONE) { } | |
| 98 | + | MapVersion(MapVersionID m, ClientVersionID c) : | |
| 99 | + | otbm(m), client(c) { } | |
| 100 | + | MapVersionID otbm; | |
| 101 | + | ClientVersionID client; | |
| 102 | + | }; | |
| 103 | + | ||
| 104 | + | enum OtbFormatVersion : uint32_t { | |
| 105 | + | OTB_VERSION_1 = 1, | |
| 106 | + | OTB_VERSION_2 = 2, | |
| 107 | + | OTB_VERSION_3 = 3, | |
| 108 | + | }; | |
| 109 | + | ||
| 110 | + | // Represents an OTB version | |
| 111 | + | struct OtbVersion { | |
| 112 | + | // '8.60', '7.40' etc. | |
| 113 | + | std::string name; | |
| 114 | + | // What file format the OTB is in (version 1..3) | |
| 115 | + | OtbFormatVersion format_version; | |
| 116 | + | // The minor version ID of the OTB (maps to CLIENT_VERSION in OTServ) | |
| 117 | + | ClientVersionID id; | |
| 118 | + | }; | |
| 119 | + | ||
| 120 | + | // Formats for the metadata file | |
| 121 | + | enum DatFormat { | |
| 122 | + | DAT_FORMAT_UNKNOWN, | |
| 123 | + | DAT_FORMAT_74, | |
| 124 | + | DAT_FORMAT_755, | |
| 125 | + | DAT_FORMAT_78, | |
| 126 | + | DAT_FORMAT_86, | |
| 127 | + | DAT_FORMAT_96, | |
| 128 | + | DAT_FORMAT_1010, | |
| 129 | + | DAT_FORMAT_1050, | |
| 130 | + | DAT_FORMAT_1057 | |
| 131 | + | }; | |
| 132 | + | ||
| 133 | + | enum DatFlags : uint8_t { | |
| 134 | + | DatFlagGround = 0, | |
| 135 | + | DatFlagGroundBorder = 1, | |
| 136 | + | DatFlagOnBottom = 2, | |
| 137 | + | DatFlagOnTop = 3, | |
| 138 | + | DatFlagContainer = 4, | |
| 139 | + | DatFlagStackable = 5, | |
| 140 | + | DatFlagForceUse = 6, | |
| 141 | + | DatFlagMultiUse = 7, | |
| 142 | + | DatFlagWritable = 8, | |
| 143 | + | DatFlagWritableOnce = 9, | |
| 144 | + | DatFlagFluidContainer = 10, | |
| 145 | + | DatFlagSplash = 11, | |
| 146 | + | DatFlagNotWalkable = 12, | |
| 147 | + | DatFlagNotMoveable = 13, | |
| 148 | + | DatFlagBlockProjectile = 14, | |
| 149 | + | DatFlagNotPathable = 15, | |
| 150 | + | DatFlagPickupable = 16, | |
| 151 | + | DatFlagHangable = 17, | |
| 152 | + | DatFlagHookSouth = 18, | |
| 153 | + | DatFlagHookEast = 19, | |
| 154 | + | DatFlagRotateable = 20, | |
| 155 | + | DatFlagLight = 21, | |
| 156 | + | DatFlagDontHide = 22, | |
| 157 | + | DatFlagTranslucent = 23, | |
| 158 | + | DatFlagDisplacement = 24, | |
| 159 | + | DatFlagElevation = 25, | |
| 160 | + | DatFlagLyingCorpse = 26, | |
| 161 | + | DatFlagAnimateAlways = 27, | |
| 162 | + | DatFlagMinimapColor = 28, | |
| 163 | + | DatFlagLensHelp = 29, | |
| 164 | + | DatFlagFullGround = 30, | |
| 165 | + | DatFlagLook = 31, | |
| 166 | + | DatFlagCloth = 32, | |
| 167 | + | DatFlagMarket = 33, | |
| 168 | + | DatFlagUsable = 34, | |
| 169 | + | DatFlagWrappable = 35, | |
| 170 | + | DatFlagUnwrappable = 36, | |
| 171 | + | DatFlagTopEffect = 37, | |
| 172 | + | ||
| 173 | + | DatFlagFloorChange = 252, | |
| 174 | + | DatFlagNoMoveAnimation = 253, // 10.10: real value is 16, but we need to do this for backwards compatibility | |
| 175 | + | DatFlagChargeable = 254, | |
| 176 | + | DatFlagLast = 255 | |
| 177 | + | }; | |
| 178 | + | ||
| 179 | + | // Represents a client file version | |
| 180 | + | struct ClientData { | |
| 181 | + | DatFormat datFormat; | |
| 182 | + | uint32_t datSignature; | |
| 183 | + | uint32_t sprSignature; | |
| 184 | + | }; | |
| 185 | + | ||
| 186 | + | // typedef the client version | |
| 187 | + | class ClientVersion; | |
| 188 | + | typedef std::vector<ClientVersion*> ClientVersionList; | |
| 189 | + | ||
| 190 | + | class ClientVersion : boost::noncopyable { | |
| 191 | + | public: | |
| 192 | + | ClientVersion(OtbVersion otb, std::string versionName, wxString path); | |
| 193 | + | ~ClientVersion() = default; | |
| 194 | + | ||
| 195 | + | static void loadVersions(); | |
| 196 | + | static void unloadVersions(); | |
| 197 | + | static void saveVersions(); | |
| 198 | + | ||
| 199 | + | static ClientVersion* get(ClientVersionID id); | |
| 200 | + | static ClientVersion* get(std::string name); | |
| 201 | + | static ClientVersionList getVisible(std::string from, std::string to); | |
| 202 | + | static ClientVersionList getAll(); | |
| 203 | + | static ClientVersionList getAllVisible(); | |
| 204 | + | static ClientVersionList getAllForOTBMVersion(MapVersionID map_version); | |
| 205 | + | static ClientVersionList getAllVersionsSupportedForClientVersion(ClientVersion* v); | |
| 206 | + | static ClientVersion* getLatestVersion(); | |
| 207 | + | ||
| 208 | + | bool operator==(const ClientVersion& o) const { | |
| 209 | + | return otb.id == o.otb.id; | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | bool hasValidPaths(); | |
| 213 | + | bool loadValidPaths(); | |
| 214 | + | void setClientPath(const FileName& dir); | |
| 215 | + | ||
| 216 | + | bool isVisible() const; | |
| 217 | + | std::string getName() const; | |
| 218 | + | ||
| 219 | + | ClientVersionID getID() const; | |
| 220 | + | MapVersionID getPrefferedMapVersionID() const; | |
| 221 | + | OtbVersion getOTBVersion() const; | |
| 222 | + | DatFormat getDatFormatForSignature(uint32_t signature) const; | |
| 223 | + | ClientVersionList getExtensionsSupported() const; | |
| 224 | + | ||
| 225 | + | FileName getDataPath() const; | |
| 226 | + | FileName getLocalDataPath() const; | |
| 227 | + | FileName getClientPath() const { | |
| 228 | + | return client_path; | |
| 229 | + | } | |
| 230 | + | wxFileName getMetadataPath() const { | |
| 231 | + | return metadata_path; | |
| 232 | + | } | |
| 233 | + | wxFileName getSpritesPath() const { | |
| 234 | + | return sprites_path; | |
| 235 | + | } | |
| 236 | + | ||
| 237 | + | private: | |
| 238 | + | OtbVersion otb; | |
| 239 | + | ||
| 240 | + | std::string name; | |
| 241 | + | bool visible; | |
| 242 | + | bool usesFuckedUpCharges; | |
| 243 | + | ||
| 244 | + | std::vector<MapVersionID> map_versions_supported; | |
| 245 | + | MapVersionID preferred_map_version; | |
| 246 | + | std::vector<ClientData> data_versions; | |
| 247 | + | std::vector<ClientVersion*> extension_versions; | |
| 248 | + | ||
| 249 | + | wxString data_path; | |
| 250 | + | FileName client_path; | |
| 251 | + | wxFileName metadata_path; | |
| 252 | + | wxFileName sprites_path; | |
| 253 | + | ||
| 254 | + | private: | |
| 255 | + | static void loadOTBInfo(pugi::xml_node otb_nodes); | |
| 256 | + | static void loadVersion(pugi::xml_node client_node); | |
| 257 | + | static void loadVersionExtensions(pugi::xml_node client_node); | |
| 258 | + | ||
| 259 | + | // All versions | |
| 260 | + | typedef std::map<ClientVersionID, ClientVersion*> VersionMap; | |
| 261 | + | static VersionMap client_versions; | |
| 262 | + | static ClientVersion* latest_version; | |
| 263 | + | ||
| 264 | + | // All otbs | |
| 265 | + | typedef std::map<std::string, OtbVersion> OtbMap; | |
| 266 | + | static OtbMap otb_versions; | |
| 267 | + | }; | |
| 268 | + | ||
| 269 | + | inline int VersionComparisonPredicate(ClientVersion* a, ClientVersion* b) { | |
| 270 | + | if (a->getID() < b->getID()) { | |
| 271 | + | return 1; | |
| 272 | + | } | |
| 273 | + | return 0; | |
| 274 | + | } | |
| 275 | + | ||
| 276 | + | #endif |
| @@ -0,0 +1,208 @@ | |||
| 1 | + | set(rme_H | |
| 2 | + | ${CMAKE_CURRENT_LIST_DIR}/about_window.h | |
| 3 | + | ${CMAKE_CURRENT_LIST_DIR}/action.h | |
| 4 | + | ${CMAKE_CURRENT_LIST_DIR}/application.h | |
| 5 | + | ${CMAKE_CURRENT_LIST_DIR}/artprovider.h | |
| 6 | + | ${CMAKE_CURRENT_LIST_DIR}/basemap.h | |
| 7 | + | ${CMAKE_CURRENT_LIST_DIR}/border_editor_window.h | |
| 8 | + | ${CMAKE_CURRENT_LIST_DIR}/browse_tile_window.h | |
| 9 | + | ${CMAKE_CURRENT_LIST_DIR}/brush.h | |
| 10 | + | ${CMAKE_CURRENT_LIST_DIR}/brush_enums.h | |
| 11 | + | ${CMAKE_CURRENT_LIST_DIR}/carpet_brush.h | |
| 12 | + | ${CMAKE_CURRENT_LIST_DIR}/client_version.h | |
| 13 | + | ${CMAKE_CURRENT_LIST_DIR}/common.h | |
| 14 | + | ${CMAKE_CURRENT_LIST_DIR}/common_windows.h | |
| 15 | + | ${CMAKE_CURRENT_LIST_DIR}/complexitem.h | |
| 16 | + | ${CMAKE_CURRENT_LIST_DIR}/con_vector.h | |
| 17 | + | ${CMAKE_CURRENT_LIST_DIR}/container_properties_window.h | |
| 18 | + | ${CMAKE_CURRENT_LIST_DIR}/copybuffer.h | |
| 19 | + | ${CMAKE_CURRENT_LIST_DIR}/creature.h | |
| 20 | + | ${CMAKE_CURRENT_LIST_DIR}/creature_brush.h | |
| 21 | + | ${CMAKE_CURRENT_LIST_DIR}/creatures.h | |
| 22 | + | ${CMAKE_CURRENT_LIST_DIR}/creature_sprite_manager.h | |
| 23 | + | ${CMAKE_CURRENT_LIST_DIR}/dat_debug_view.h | |
| 24 | + | ${CMAKE_CURRENT_LIST_DIR}/dcbutton.h | |
| 25 | + | ${CMAKE_CURRENT_LIST_DIR}/definitions.h | |
| 26 | + | ${CMAKE_CURRENT_LIST_DIR}/doodad_brush.h | |
| 27 | + | ${CMAKE_CURRENT_LIST_DIR}/editor.h | |
| 28 | + | ${CMAKE_CURRENT_LIST_DIR}/editor_tabs.h | |
| 29 | + | ${CMAKE_CURRENT_LIST_DIR}/extension.h | |
| 30 | + | ${CMAKE_CURRENT_LIST_DIR}/extension_window.h | |
| 31 | + | ${CMAKE_CURRENT_LIST_DIR}/find_item_window.h | |
| 32 | + | ${CMAKE_CURRENT_LIST_DIR}/filehandle.h | |
| 33 | + | ${CMAKE_CURRENT_LIST_DIR}/graphics.h | |
| 34 | + | ${CMAKE_CURRENT_LIST_DIR}/ground_brush.h | |
| 35 | + | ${CMAKE_CURRENT_LIST_DIR}/gui.h | |
| 36 | + | ${CMAKE_CURRENT_LIST_DIR}/gui_ids.h | |
| 37 | + | ${CMAKE_CURRENT_LIST_DIR}/house.h | |
| 38 | + | ${CMAKE_CURRENT_LIST_DIR}/house_brush.h | |
| 39 | + | ${CMAKE_CURRENT_LIST_DIR}/house_exit_brush.h | |
| 40 | + | ${CMAKE_CURRENT_LIST_DIR}/iomap.h | |
| 41 | + | ${CMAKE_CURRENT_LIST_DIR}/iomap_otbm.h | |
| 42 | + | #${CMAKE_CURRENT_LIST_DIR}/iomap_otmm.h | |
| 43 | + | ${CMAKE_CURRENT_LIST_DIR}/item.h | |
| 44 | + | ${CMAKE_CURRENT_LIST_DIR}/item_attributes.h | |
| 45 | + | ${CMAKE_CURRENT_LIST_DIR}/items.h | |
| 46 | + | ${CMAKE_CURRENT_LIST_DIR}/json.h | |
| 47 | + | ${CMAKE_CURRENT_LIST_DIR}/light_drawer.h | |
| 48 | + | ${CMAKE_CURRENT_LIST_DIR}/live_action.h | |
| 49 | + | ${CMAKE_CURRENT_LIST_DIR}/live_client.h | |
| 50 | + | ${CMAKE_CURRENT_LIST_DIR}/live_packets.h | |
| 51 | + | ${CMAKE_CURRENT_LIST_DIR}/live_peer.h | |
| 52 | + | ${CMAKE_CURRENT_LIST_DIR}/live_server.h | |
| 53 | + | ${CMAKE_CURRENT_LIST_DIR}/live_socket.h | |
| 54 | + | ${CMAKE_CURRENT_LIST_DIR}/live_tab.h | |
| 55 | + | ${CMAKE_CURRENT_LIST_DIR}/main.h | |
| 56 | + | ${CMAKE_CURRENT_LIST_DIR}/main_menubar.h | |
| 57 | + | ${CMAKE_CURRENT_LIST_DIR}/main_toolbar.h | |
| 58 | + | ${CMAKE_CURRENT_LIST_DIR}/map.h | |
| 59 | + | ${CMAKE_CURRENT_LIST_DIR}/map_allocator.h | |
| 60 | + | ${CMAKE_CURRENT_LIST_DIR}/map_display.h | |
| 61 | + | ${CMAKE_CURRENT_LIST_DIR}/map_drawer.h | |
| 62 | + | ${CMAKE_CURRENT_LIST_DIR}/map_region.h | |
| 63 | + | ${CMAKE_CURRENT_LIST_DIR}/map_tab.h | |
| 64 | + | ${CMAKE_CURRENT_LIST_DIR}/map_window.h | |
| 65 | + | ${CMAKE_CURRENT_LIST_DIR}/materials.h | |
| 66 | + | ${CMAKE_CURRENT_LIST_DIR}/minimap_window.h | |
| 67 | + | ${CMAKE_CURRENT_LIST_DIR}/mt_rand.h | |
| 68 | + | ${CMAKE_CURRENT_LIST_DIR}/net_connection.h | |
| 69 | + | ${CMAKE_CURRENT_LIST_DIR}/numbertextctrl.h | |
| 70 | + | ${CMAKE_CURRENT_LIST_DIR}/old_properties_window.h | |
| 71 | + | ${CMAKE_CURRENT_LIST_DIR}/otml.h | |
| 72 | + | ${CMAKE_CURRENT_LIST_DIR}/outfit.h | |
| 73 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_brushlist.h | |
| 74 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_common.h | |
| 75 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_creature.h | |
| 76 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_house.h | |
| 77 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_waypoints.h | |
| 78 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_window.h | |
| 79 | + | ${CMAKE_CURRENT_LIST_DIR}/pngfiles.h | |
| 80 | + | ${CMAKE_CURRENT_LIST_DIR}/position.h | |
| 81 | + | ${CMAKE_CURRENT_LIST_DIR}/positionctrl.h | |
| 82 | + | ${CMAKE_CURRENT_LIST_DIR}/preferences.h | |
| 83 | + | ${CMAKE_CURRENT_LIST_DIR}/process_com.h | |
| 84 | + | ${CMAKE_CURRENT_LIST_DIR}/properties_window.h | |
| 85 | + | ${CMAKE_CURRENT_LIST_DIR}/raw_brush.h | |
| 86 | + | ${CMAKE_CURRENT_LIST_DIR}/replace_items_window.h | |
| 87 | + | ${CMAKE_CURRENT_LIST_DIR}/result_window.h | |
| 88 | + | ${CMAKE_CURRENT_LIST_DIR}/rme_forward_declarations.h | |
| 89 | + | ${CMAKE_CURRENT_LIST_DIR}/rme_net.h | |
| 90 | + | ${CMAKE_CURRENT_LIST_DIR}/selection.h | |
| 91 | + | ${CMAKE_CURRENT_LIST_DIR}/settings.h | |
| 92 | + | ${CMAKE_CURRENT_LIST_DIR}/spawn.h | |
| 93 | + | ${CMAKE_CURRENT_LIST_DIR}/spawn_brush.h | |
| 94 | + | ${CMAKE_CURRENT_LIST_DIR}/sprites.h | |
| 95 | + | ${CMAKE_CURRENT_LIST_DIR}/table_brush.h | |
| 96 | + | ${CMAKE_CURRENT_LIST_DIR}/templates.h | |
| 97 | + | ${CMAKE_CURRENT_LIST_DIR}/threads.h | |
| 98 | + | ${CMAKE_CURRENT_LIST_DIR}/tile.h | |
| 99 | + | ${CMAKE_CURRENT_LIST_DIR}/tileset.h | |
| 100 | + | ${CMAKE_CURRENT_LIST_DIR}/town.h | |
| 101 | + | ${CMAKE_CURRENT_LIST_DIR}/updater.h | |
| 102 | + | ${CMAKE_CURRENT_LIST_DIR}/wall_brush.h | |
| 103 | + | ${CMAKE_CURRENT_LIST_DIR}/waypoint_brush.h | |
| 104 | + | ${CMAKE_CURRENT_LIST_DIR}/waypoints.h | |
| 105 | + | ${CMAKE_CURRENT_LIST_DIR}/welcome_dialog.h | |
| 106 | + | ) | |
| 107 | + | ||
| 108 | + | set(rme_SRC | |
| 109 | + | ${CMAKE_CURRENT_LIST_DIR}/about_window.cpp | |
| 110 | + | ${CMAKE_CURRENT_LIST_DIR}/action.cpp | |
| 111 | + | ${CMAKE_CURRENT_LIST_DIR}/application.cpp | |
| 112 | + | ${CMAKE_CURRENT_LIST_DIR}/artprovider.cpp | |
| 113 | + | ${CMAKE_CURRENT_LIST_DIR}/basemap.cpp | |
| 114 | + | ${CMAKE_CURRENT_LIST_DIR}/border_editor_window.cpp | |
| 115 | + | ${CMAKE_CURRENT_LIST_DIR}/brush.cpp | |
| 116 | + | ${CMAKE_CURRENT_LIST_DIR}/brush_tables.cpp | |
| 117 | + | ${CMAKE_CURRENT_LIST_DIR}/browse_tile_window.cpp | |
| 118 | + | ${CMAKE_CURRENT_LIST_DIR}/positionctrl.cpp | |
| 119 | + | ${CMAKE_CURRENT_LIST_DIR}/carpet_brush.cpp | |
| 120 | + | ${CMAKE_CURRENT_LIST_DIR}/client_version.cpp | |
| 121 | + | ${CMAKE_CURRENT_LIST_DIR}/common.cpp | |
| 122 | + | ${CMAKE_CURRENT_LIST_DIR}/common_windows.cpp | |
| 123 | + | ${CMAKE_CURRENT_LIST_DIR}/complexitem.cpp | |
| 124 | + | ${CMAKE_CURRENT_LIST_DIR}/container_properties_window.cpp | |
| 125 | + | ${CMAKE_CURRENT_LIST_DIR}/copybuffer.cpp | |
| 126 | + | ${CMAKE_CURRENT_LIST_DIR}/creature_brush.cpp | |
| 127 | + | ${CMAKE_CURRENT_LIST_DIR}/creature.cpp | |
| 128 | + | ${CMAKE_CURRENT_LIST_DIR}/creatures.cpp | |
| 129 | + | ${CMAKE_CURRENT_LIST_DIR}/creature_sprite_manager.cpp | |
| 130 | + | ${CMAKE_CURRENT_LIST_DIR}/dat_debug_view.cpp | |
| 131 | + | ${CMAKE_CURRENT_LIST_DIR}/dcbutton.cpp | |
| 132 | + | ${CMAKE_CURRENT_LIST_DIR}/doodad_brush.cpp | |
| 133 | + | ${CMAKE_CURRENT_LIST_DIR}/editor.cpp | |
| 134 | + | ${CMAKE_CURRENT_LIST_DIR}/editor_tabs.cpp | |
| 135 | + | ${CMAKE_CURRENT_LIST_DIR}/eraser_brush.cpp | |
| 136 | + | ${CMAKE_CURRENT_LIST_DIR}/extension.cpp | |
| 137 | + | ${CMAKE_CURRENT_LIST_DIR}/extension_window.cpp | |
| 138 | + | ${CMAKE_CURRENT_LIST_DIR}/find_item_window.cpp | |
| 139 | + | ${CMAKE_CURRENT_LIST_DIR}/filehandle.cpp | |
| 140 | + | ${CMAKE_CURRENT_LIST_DIR}/graphics.cpp | |
| 141 | + | ${CMAKE_CURRENT_LIST_DIR}/ground_brush.cpp | |
| 142 | + | ${CMAKE_CURRENT_LIST_DIR}/gui.cpp | |
| 143 | + | ${CMAKE_CURRENT_LIST_DIR}/house_brush.cpp | |
| 144 | + | ${CMAKE_CURRENT_LIST_DIR}/house.cpp | |
| 145 | + | ${CMAKE_CURRENT_LIST_DIR}/house_exit_brush.cpp | |
| 146 | + | ${CMAKE_CURRENT_LIST_DIR}/iomap.cpp | |
| 147 | + | ${CMAKE_CURRENT_LIST_DIR}/iomap_otbm.cpp | |
| 148 | + | #${CMAKE_CURRENT_LIST_DIR}/iomap_otmm.cpp | |
| 149 | + | ${CMAKE_CURRENT_LIST_DIR}/item_attributes.cpp | |
| 150 | + | ${CMAKE_CURRENT_LIST_DIR}/item.cpp | |
| 151 | + | ${CMAKE_CURRENT_LIST_DIR}/items.cpp | |
| 152 | + | ${CMAKE_CURRENT_LIST_DIR}/light_drawer.cpp | |
| 153 | + | ${CMAKE_CURRENT_LIST_DIR}/live_action.cpp | |
| 154 | + | ${CMAKE_CURRENT_LIST_DIR}/live_client.cpp | |
| 155 | + | ${CMAKE_CURRENT_LIST_DIR}/live_peer.cpp | |
| 156 | + | ${CMAKE_CURRENT_LIST_DIR}/live_server.cpp | |
| 157 | + | ${CMAKE_CURRENT_LIST_DIR}/live_socket.cpp | |
| 158 | + | ${CMAKE_CURRENT_LIST_DIR}/live_tab.cpp | |
| 159 | + | ${CMAKE_CURRENT_LIST_DIR}/main_menubar.cpp | |
| 160 | + | ${CMAKE_CURRENT_LIST_DIR}/main_toolbar.cpp | |
| 161 | + | ${CMAKE_CURRENT_LIST_DIR}/map.cpp | |
| 162 | + | ${CMAKE_CURRENT_LIST_DIR}/map_display.cpp | |
| 163 | + | ${CMAKE_CURRENT_LIST_DIR}/map_drawer.cpp | |
| 164 | + | ${CMAKE_CURRENT_LIST_DIR}/map_region.cpp | |
| 165 | + | ${CMAKE_CURRENT_LIST_DIR}/map_tab.cpp | |
| 166 | + | ${CMAKE_CURRENT_LIST_DIR}/map_window.cpp | |
| 167 | + | ${CMAKE_CURRENT_LIST_DIR}/materials.cpp | |
| 168 | + | ${CMAKE_CURRENT_LIST_DIR}/minimap_window.cpp | |
| 169 | + | ${CMAKE_CURRENT_LIST_DIR}/mkpch.cpp | |
| 170 | + | ${CMAKE_CURRENT_LIST_DIR}/mt_rand.cpp | |
| 171 | + | ${CMAKE_CURRENT_LIST_DIR}/net_connection.cpp | |
| 172 | + | ${CMAKE_CURRENT_LIST_DIR}/numbertextctrl.cpp | |
| 173 | + | ${CMAKE_CURRENT_LIST_DIR}/old_properties_window.cpp | |
| 174 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_brushlist.cpp | |
| 175 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_common.cpp | |
| 176 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_creature.cpp | |
| 177 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_house.cpp | |
| 178 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_waypoints.cpp | |
| 179 | + | ${CMAKE_CURRENT_LIST_DIR}/palette_window.cpp | |
| 180 | + | ${CMAKE_CURRENT_LIST_DIR}/pngfiles.cpp | |
| 181 | + | ${CMAKE_CURRENT_LIST_DIR}/preferences.cpp | |
| 182 | + | ${CMAKE_CURRENT_LIST_DIR}/process_com.cpp | |
| 183 | + | ${CMAKE_CURRENT_LIST_DIR}/properties_window.cpp | |
| 184 | + | ${CMAKE_CURRENT_LIST_DIR}/raw_brush.cpp | |
| 185 | + | ${CMAKE_CURRENT_LIST_DIR}/replace_items_window.cpp | |
| 186 | + | ${CMAKE_CURRENT_LIST_DIR}/result_window.cpp | |
| 187 | + | ${CMAKE_CURRENT_LIST_DIR}/rme_net.cpp | |
| 188 | + | ${CMAKE_CURRENT_LIST_DIR}/selection.cpp | |
| 189 | + | ${CMAKE_CURRENT_LIST_DIR}/settings.cpp | |
| 190 | + | ${CMAKE_CURRENT_LIST_DIR}/spawn_brush.cpp | |
| 191 | + | ${CMAKE_CURRENT_LIST_DIR}/spawn.cpp | |
| 192 | + | ${CMAKE_CURRENT_LIST_DIR}/table_brush.cpp | |
| 193 | + | ${CMAKE_CURRENT_LIST_DIR}/templatemap76-74.cpp | |
| 194 | + | ${CMAKE_CURRENT_LIST_DIR}/templatemap81.cpp | |
| 195 | + | ${CMAKE_CURRENT_LIST_DIR}/templatemap854.cpp | |
| 196 | + | ${CMAKE_CURRENT_LIST_DIR}/templatemapclassic.cpp | |
| 197 | + | ${CMAKE_CURRENT_LIST_DIR}/tile.cpp | |
| 198 | + | ${CMAKE_CURRENT_LIST_DIR}/tileset.cpp | |
| 199 | + | ${CMAKE_CURRENT_LIST_DIR}/town.cpp | |
| 200 | + | ${CMAKE_CURRENT_LIST_DIR}/updater.cpp | |
| 201 | + | ${CMAKE_CURRENT_LIST_DIR}/wall_brush.cpp | |
| 202 | + | ${CMAKE_CURRENT_LIST_DIR}/waypoint_brush.cpp | |
| 203 | + | ${CMAKE_CURRENT_LIST_DIR}/waypoints.cpp | |
| 204 | + | ${CMAKE_CURRENT_LIST_DIR}/welcome_dialog.cpp | |
| 205 | + | ${CMAKE_CURRENT_LIST_DIR}/json/json_spirit_reader.cpp | |
| 206 | + | ${CMAKE_CURRENT_LIST_DIR}/json/json_spirit_value.cpp | |
| 207 | + | ${CMAKE_CURRENT_LIST_DIR}/json/json_spirit_writer.cpp | |
| 208 | + | ) |
| @@ -0,0 +1,122 @@ | |||
| 1 | + | #ifndef RME_COLOR_UTILS_H_ | |
| 2 | + | #define RME_COLOR_UTILS_H_ | |
| 3 | + | ||
| 4 | + | #include <wx/wx.h> | |
| 5 | + | #include <wx/image.h> | |
| 6 | + | #include <ctime> | |
| 7 | + | #include <random> | |
| 8 | + | ||
| 9 | + | class ColorUtils { | |
| 10 | + | public: | |
| 11 | + | static void ShiftHue(wxImage& image, float hueShift) { | |
| 12 | + | if (!image.IsOk()) return; | |
| 13 | + | ||
| 14 | + | // Make sure we have alpha channel | |
| 15 | + | if (!image.HasAlpha()) { | |
| 16 | + | image.InitAlpha(); | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | unsigned char* data = image.GetData(); | |
| 20 | + | unsigned char* alpha = image.GetAlpha(); | |
| 21 | + | int width = image.GetWidth(); | |
| 22 | + | int height = image.GetHeight(); | |
| 23 | + | ||
| 24 | + | for (int i = 0; i < width * height; ++i) { | |
| 25 | + | // Get alpha value first | |
| 26 | + | unsigned char a = alpha[i]; | |
| 27 | + | ||
| 28 | + | // Only process pixels that are not fully transparent | |
| 29 | + | if (a > 0) { | |
| 30 | + | int r = data[i * 3]; | |
| 31 | + | int g = data[i * 3 + 1]; | |
| 32 | + | int b = data[i * 3 + 2]; | |
| 33 | + | ||
| 34 | + | // Convert RGB to HSV | |
| 35 | + | float h, s, v; | |
| 36 | + | RGBtoHSV(r, g, b, h, s, v); | |
| 37 | + | ||
| 38 | + | // Shift hue | |
| 39 | + | h += hueShift; | |
| 40 | + | if (h >= 360.0f) h -= 360.0f; | |
| 41 | + | else if (h < 0.0f) h += 360.0f; | |
| 42 | + | ||
| 43 | + | // Convert back to RGB | |
| 44 | + | HSVtoRGB(h, s, v, r, g, b); | |
| 45 | + | ||
| 46 | + | data[i * 3] = r; | |
| 47 | + | data[i * 3 + 1] = g; | |
| 48 | + | data[i * 3 + 2] = b; | |
| 49 | + | } else { | |
| 50 | + | // For fully transparent pixels, set RGB to white to avoid black edges | |
| 51 | + | data[i * 3] = 255; | |
| 52 | + | data[i * 3 + 1] = 255; | |
| 53 | + | data[i * 3 + 2] = 255; | |
| 54 | + | } | |
| 55 | + | } | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | static float GetRandomHueShift() { | |
| 59 | + | static std::random_device rd; | |
| 60 | + | static std::mt19937 gen(rd()); | |
| 61 | + | static std::uniform_real_distribution<float> dis(0.0f, 360.0f); | |
| 62 | + | return dis(gen); | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | private: | |
| 66 | + | static void RGBtoHSV(int r, int g, int b, float& h, float& s, float& v) { | |
| 67 | + | float rf = r / 255.0f; | |
| 68 | + | float gf = g / 255.0f; | |
| 69 | + | float bf = b / 255.0f; | |
| 70 | + | ||
| 71 | + | float max = std::max(std::max(rf, gf), bf); | |
| 72 | + | float min = std::min(std::min(rf, gf), bf); | |
| 73 | + | float delta = max - min; | |
| 74 | + | ||
| 75 | + | v = max; | |
| 76 | + | s = (max != 0.0f) ? (delta / max) : 0.0f; | |
| 77 | + | ||
| 78 | + | if (delta == 0.0f) { | |
| 79 | + | h = 0.0f; | |
| 80 | + | } else { | |
| 81 | + | if (max == rf) { | |
| 82 | + | h = 60.0f * fmod(((gf - bf) / delta), 6.0f); | |
| 83 | + | } else if (max == gf) { | |
| 84 | + | h = 60.0f * (((bf - rf) / delta) + 2.0f); | |
| 85 | + | } else { | |
| 86 | + | h = 60.0f * (((rf - gf) / delta) + 4.0f); | |
| 87 | + | } | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | if (h < 0.0f) h += 360.0f; | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | static void HSVtoRGB(float h, float s, float v, int& r, int& g, int& b) { | |
| 94 | + | if (s == 0.0f) { | |
| 95 | + | r = g = b = static_cast<int>(v * 255.0f); | |
| 96 | + | return; | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | h /= 60.0f; | |
| 100 | + | int i = static_cast<int>(floor(h)); | |
| 101 | + | float f = h - i; | |
| 102 | + | float p = v * (1.0f - s); | |
| 103 | + | float q = v * (1.0f - s * f); | |
| 104 | + | float t = v * (1.0f - s * (1.0f - f)); | |
| 105 | + | ||
| 106 | + | float rf, gf, bf; | |
| 107 | + | switch (i % 6) { | |
| 108 | + | case 0: rf = v; gf = t; bf = p; break; | |
| 109 | + | case 1: rf = q; gf = v; bf = p; break; | |
| 110 | + | case 2: rf = p; gf = v; bf = t; break; | |
| 111 | + | case 3: rf = p; gf = q; bf = v; break; | |
| 112 | + | case 4: rf = t; gf = p; bf = v; break; | |
| 113 | + | case 5: rf = v; gf = p; bf = q; break; | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | r = static_cast<int>(rf * 255.0f); | |
| 117 | + | g = static_cast<int>(gf * 255.0f); | |
| 118 | + | b = static_cast<int>(bf * 255.0f); | |
| 119 | + | } | |
| 120 | + | }; | |
| 121 | + | ||
| 122 | + | #endif // RME_COLOR_UTILS_H_ |
| @@ -0,0 +1,262 @@ | |||
| 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 "common.h" | |
| 21 | + | #include "math.h" | |
| 22 | + | ||
| 23 | + | #include <sstream> | |
| 24 | + | #include <random> | |
| 25 | + | #include <regex> | |
| 26 | + | #include <algorithm> | |
| 27 | + | ||
| 28 | + | // random generator | |
| 29 | + | std::mt19937& getRandomGenerator() { | |
| 30 | + | static std::random_device rd; | |
| 31 | + | static std::mt19937 generator(rd()); | |
| 32 | + | return generator; | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | int32_t uniform_random(int32_t minNumber, int32_t maxNumber) { | |
| 36 | + | static std::uniform_int_distribution<int32_t> uniformRand; | |
| 37 | + | if (minNumber == maxNumber) { | |
| 38 | + | return minNumber; | |
| 39 | + | } else if (minNumber > maxNumber) { | |
| 40 | + | std::swap(minNumber, maxNumber); | |
| 41 | + | } | |
| 42 | + | return uniformRand(getRandomGenerator(), std::uniform_int_distribution<int32_t>::param_type(minNumber, maxNumber)); | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | int32_t uniform_random(int32_t maxNumber) { | |
| 46 | + | return uniform_random(0, maxNumber); | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | // | |
| 50 | + | std::string i2s(const int _i) { | |
| 51 | + | static std::stringstream ss; | |
| 52 | + | ss.str(""); | |
| 53 | + | ss << _i; | |
| 54 | + | return ss.str(); | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | std::string f2s(const double _d) { | |
| 58 | + | static std::stringstream ss; | |
| 59 | + | ss.str(""); | |
| 60 | + | ss << _d; | |
| 61 | + | return ss.str(); | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | int s2i(const std::string s) { | |
| 65 | + | return atoi(s.c_str()); | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | double s2f(const std::string s) { | |
| 69 | + | return atof(s.c_str()); | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | wxString i2ws(const int _i) { | |
| 73 | + | wxString str; | |
| 74 | + | str << _i; | |
| 75 | + | return str; | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | wxString f2ws(const double _d) { | |
| 79 | + | wxString str; | |
| 80 | + | str << _d; | |
| 81 | + | return str; | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | int ws2i(const wxString s) { | |
| 85 | + | long _i; | |
| 86 | + | if (s.ToLong(&_i)) { | |
| 87 | + | return int(_i); | |
| 88 | + | } | |
| 89 | + | return 0; | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | double ws2f(const wxString s) { | |
| 93 | + | double _d; | |
| 94 | + | if (s.ToDouble(&_d)) { | |
| 95 | + | return _d; | |
| 96 | + | } | |
| 97 | + | return 0.0; | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | void replaceString(std::string& str, const std::string sought, const std::string replacement) { | |
| 101 | + | size_t pos = 0; | |
| 102 | + | size_t start = 0; | |
| 103 | + | size_t soughtLen = sought.length(); | |
| 104 | + | size_t replaceLen = replacement.length(); | |
| 105 | + | while ((pos = str.find(sought, start)) != std::string::npos) { | |
| 106 | + | str = str.substr(0, pos) + replacement + str.substr(pos + soughtLen); | |
| 107 | + | start = pos + replaceLen; | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | void trim_right(std::string& source, const std::string& t) { | |
| 112 | + | source.erase(source.find_last_not_of(t) + 1); | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | void trim_left(std::string& source, const std::string& t) { | |
| 116 | + | source.erase(0, source.find_first_not_of(t)); | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | void to_lower_str(std::string& source) { | |
| 120 | + | std::transform(source.begin(), source.end(), source.begin(), tolower); | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | void to_upper_str(std::string& source) { | |
| 124 | + | std::transform(source.begin(), source.end(), source.begin(), toupper); | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | std::string as_lower_str(const std::string& other) { | |
| 128 | + | std::string ret = other; | |
| 129 | + | to_lower_str(ret); | |
| 130 | + | return ret; | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | std::string as_upper_str(const std::string& other) { | |
| 134 | + | std::string ret = other; | |
| 135 | + | to_upper_str(ret); | |
| 136 | + | return ret; | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | bool isFalseString(std::string& str) { | |
| 140 | + | if (str == "false" || str == "0" || str == "" || str == "no" || str == "not") { | |
| 141 | + | return true; | |
| 142 | + | } | |
| 143 | + | return false; | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | bool isTrueString(std::string& str) { | |
| 147 | + | return !isFalseString(str); | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | int random(int low, int high) { | |
| 151 | + | if (low == high) { | |
| 152 | + | return low; | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | if (low > high) { | |
| 156 | + | return low; | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | int range = high - low; | |
| 160 | + | ||
| 161 | + | double dist = double(mt_randi()) / 0xFFFFFFFF; | |
| 162 | + | return low + min(range, int((1 + range) * dist)); | |
| 163 | + | } | |
| 164 | + | ||
| 165 | + | int random(int high) { | |
| 166 | + | return random(0, high); | |
| 167 | + | } | |
| 168 | + | ||
| 169 | + | std::wstring string2wstring(const std::string& utf8string) { | |
| 170 | + | wxString s(utf8string.c_str(), wxConvUTF8); | |
| 171 | + | return std::wstring((const wchar_t*)s.c_str()); | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | std::string wstring2string(const std::wstring& widestring) { | |
| 175 | + | wxString s(widestring.c_str()); | |
| 176 | + | return std::string((const char*)s.mb_str(wxConvUTF8)); | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | bool posFromClipboard(Position& position, int mapWidth, int mapHeight, const std::string& input) { | |
| 180 | + | std::string text_to_parse; | |
| 181 | + | ||
| 182 | + | // If input is provided, use it directly, otherwise get from clipboard | |
| 183 | + | if (!input.empty()) { | |
| 184 | + | text_to_parse = input; | |
| 185 | + | } else { | |
| 186 | + | if (!wxTheClipboard->Open()) | |
| 187 | + | return false; | |
| 188 | + | ||
| 189 | + | wxTextDataObject data; | |
| 190 | + | if (!wxTheClipboard->GetData(data)) { | |
| 191 | + | wxTheClipboard->Close(); | |
| 192 | + | return false; | |
| 193 | + | } | |
| 194 | + | ||
| 195 | + | text_to_parse = data.GetText().ToStdString(); | |
| 196 | + | wxTheClipboard->Close(); | |
| 197 | + | ||
| 198 | + | if (text_to_parse.empty()) | |
| 199 | + | return false; | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | // Extract just numbers, spaces, and commas | |
| 203 | + | std::string numbers_only; | |
| 204 | + | for (char c : text_to_parse) { | |
| 205 | + | // Accept digits, spaces, commas, and convert any whitespace/separator to space | |
| 206 | + | if (std::isdigit(c)) { | |
| 207 | + | numbers_only += c; | |
| 208 | + | } else if (std::isspace(c) || c == ',' || c == ';') { | |
| 209 | + | // Convert any separator to space | |
| 210 | + | if (!numbers_only.empty() && numbers_only.back() != ' ') { | |
| 211 | + | numbers_only += ' '; | |
| 212 | + | } | |
| 213 | + | } | |
| 214 | + | } | |
| 215 | + | ||
| 216 | + | // Trim leading/trailing spaces | |
| 217 | + | while (!numbers_only.empty() && numbers_only.front() == ' ') { | |
| 218 | + | numbers_only.erase(0, 1); | |
| 219 | + | } | |
| 220 | + | while (!numbers_only.empty() && numbers_only.back() == ' ') { | |
| 221 | + | numbers_only.pop_back(); | |
| 222 | + | } | |
| 223 | + | ||
| 224 | + | // Split into numbers | |
| 225 | + | std::istringstream iss(numbers_only); | |
| 226 | + | std::vector<int> coords; | |
| 227 | + | int num; | |
| 228 | + | while (iss >> num) { | |
| 229 | + | coords.push_back(num); | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | // If we have exactly 3 numbers, use them | |
| 233 | + | if (coords.size() >= 3) { | |
| 234 | + | const int tmpX = coords[0]; | |
| 235 | + | const int tmpY = coords[1]; | |
| 236 | + | const int tmpZ = coords[2]; | |
| 237 | + | ||
| 238 | + | const Position pastedPos = Position(tmpX, tmpY, tmpZ); | |
| 239 | + | if (pastedPos.isValid() && tmpX <= mapWidth && tmpY <= mapHeight) { | |
| 240 | + | position.x = tmpX; | |
| 241 | + | position.y = tmpY; | |
| 242 | + | position.z = tmpZ; | |
| 243 | + | return true; | |
| 244 | + | } | |
| 245 | + | } | |
| 246 | + | ||
| 247 | + | return false; | |
| 248 | + | } | |
| 249 | + | ||
| 250 | + | wxString b2yn(bool value) { | |
| 251 | + | return value ? "Yes" : "No"; | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | wxColor colorFromEightBit(int color) { | |
| 255 | + | if (color <= 0 || color >= 216) { | |
| 256 | + | return wxColor(0, 0, 0); | |
| 257 | + | } | |
| 258 | + | const uint8_t red = (uint8_t)(int(color / 36) % 6 * 51); | |
| 259 | + | const uint8_t green = (uint8_t)(int(color / 6) % 6 * 51); | |
| 260 | + | const uint8_t blue = (uint8_t)(color % 6 * 51); | |
| 261 | + | return wxColor(red, green, blue); | |
| 262 | + | } |
| @@ -0,0 +1,122 @@ | |||
| 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_COMMONS_H_ | |
| 19 | + | #define RME_COMMONS_H_ | |
| 20 | + | ||
| 21 | + | #include "main.h" | |
| 22 | + | #include "position.h" | |
| 23 | + | #include <stdio.h> | |
| 24 | + | #include <stdlib.h> | |
| 25 | + | #include <cmath> | |
| 26 | + | #include <iomanip> | |
| 27 | + | #include <string> | |
| 28 | + | ||
| 29 | + | #include "mt_rand.h" | |
| 30 | + | ||
| 31 | + | // | |
| 32 | + | inline bool testFlags(size_t flags, size_t test) { | |
| 33 | + | return (flags & test) != 0; | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | int32_t uniform_random(int32_t minNumber, int32_t maxNumber); | |
| 37 | + | int32_t uniform_random(int32_t maxNumber); | |
| 38 | + | ||
| 39 | + | // Function-like convertions between float, int and doubles | |
| 40 | + | std::string i2s(int i); | |
| 41 | + | std::string f2s(double i); | |
| 42 | + | int s2i(std::string s); | |
| 43 | + | double s2f(std::string s); | |
| 44 | + | wxString i2ws(int i); | |
| 45 | + | wxString f2ws(double i); | |
| 46 | + | int ws2i(wxString s); | |
| 47 | + | double ws2f(wxString s); | |
| 48 | + | double frand(); | |
| 49 | + | ||
| 50 | + | // replaces all instances of sought in str with replacement | |
| 51 | + | void replaceString(std::string& str, const std::string sought, const std::string replacement); | |
| 52 | + | // Removes all characters in t from source (from either start or beginning of the string) | |
| 53 | + | void trim_right(std::string& source, const std::string& t); | |
| 54 | + | void trim_left(std::string& source, const std::string& t); | |
| 55 | + | // Converts the argument to lower/uppercase | |
| 56 | + | void to_lower_str(std::string& source); | |
| 57 | + | void to_upper_str(std::string& source); | |
| 58 | + | std::string as_lower_str(const std::string& other); | |
| 59 | + | std::string as_upper_str(const std::string& other); | |
| 60 | + | ||
| 61 | + | // isFalseString returns true if the string is either "0", "false", "no", "not" or blank | |
| 62 | + | // isTrueString returns the opposite value of isFalseString | |
| 63 | + | bool isFalseString(std::string& str); | |
| 64 | + | bool isTrueString(std::string& str); | |
| 65 | + | ||
| 66 | + | // Generates a random number between low and high using the mersenne twister | |
| 67 | + | int random(int high); | |
| 68 | + | int random(int low, int high); | |
| 69 | + | ||
| 70 | + | // Unicode conversions | |
| 71 | + | std::wstring string2wstring(const std::string& utf8string); | |
| 72 | + | std::string wstring2string(const std::wstring& widestring); | |
| 73 | + | ||
| 74 | + | // Gets position values from ClipBoard | |
| 75 | + | bool posFromClipboard(Position& position, int mapWidth, int mapHeight, const std::string& input = ""); | |
| 76 | + | ||
| 77 | + | // Returns 'yes' if the defined value is true or 'no' if it is false. | |
| 78 | + | wxString b2yn(bool v); | |
| 79 | + | ||
| 80 | + | wxColor colorFromEightBit(int color); | |
| 81 | + | ||
| 82 | + | // Standard math functions | |
| 83 | + | template <class T> | |
| 84 | + | inline T abs(T t) { | |
| 85 | + | return (t < 0 ? -t : t); | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | template <class T, class U> | |
| 89 | + | inline T min(T t, U u) { | |
| 90 | + | return (t < u ? t : u); | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | template <class T, class U> | |
| 94 | + | T max(T t, U u) { | |
| 95 | + | return (t > u ? t : u); | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | template <class T, class U, class V> | |
| 99 | + | inline T min(T t, U u, V v) { | |
| 100 | + | int min = t; | |
| 101 | + | if (u < min) { | |
| 102 | + | min = u; | |
| 103 | + | } | |
| 104 | + | if (v < min) { | |
| 105 | + | min = v; | |
| 106 | + | } | |
| 107 | + | return min; | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | template <class T, class U, class V> | |
| 111 | + | inline T max(T t, U u, V v) { | |
| 112 | + | int max = t; | |
| 113 | + | if (u > max) { | |
| 114 | + | max = u; | |
| 115 | + | } | |
| 116 | + | if (v > max) { | |
| 117 | + | max = v; | |
| 118 | + | } | |
| 119 | + | return max; | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | #endif |