materials.cpp

main 382 lines · 11.6 KB Raw
Alex Alex Commit Map Editor 18/10/2026 23:40
1//////////////////////////////////////////////////////////////////////
2// This file is part of Remere's Map Editor
3//////////////////////////////////////////////////////////////////////
4// Remere's Map Editor is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Remere's Map Editor is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//////////////////////////////////////////////////////////////////////
17
18#include "main.h"
19
20#include <wx/dir.h>
21
22#include "editor.h"
23#include "items.h"
24#include "creatures.h"
25
26#include "gui.h"
27#include "materials.h"
28#include "brush.h"
29#include "creature_brush.h"
30#include "raw_brush.h"
31
32Materials g_materials;
33
34Materials::Materials() {
35 ////
36}
37
38Materials::~Materials() {
39 clear();
40}
41
42void Materials::clear() {
43 for (TilesetContainer::iterator iter = tilesets.begin(); iter != tilesets.end(); ++iter) {
44 delete iter->second;
45 }
46
47 for (MaterialsExtensionList::iterator iter = extensions.begin(); iter != extensions.end(); ++iter) {
48 delete *iter;
49 }
50
51 tilesets.clear();
52 extensions.clear();
53}
54
55const MaterialsExtensionList& Materials::getExtensions() {
56 return extensions;
57}
58
59MaterialsExtensionList Materials::getExtensionsByVersion(uint16_t version_id) {
60 MaterialsExtensionList ret_list;
61 for (MaterialsExtensionList::iterator iter = extensions.begin(); iter != extensions.end(); ++iter) {
62 if ((*iter)->isForVersion(version_id)) {
63 ret_list.push_back(*iter);
64 }
65 }
66 return ret_list;
67}
68
69bool Materials::loadMaterials(const FileName& identifier, wxString& error, wxArrayString& warnings) {
70 pugi::xml_document doc;
71 pugi::xml_parse_result result = doc.load_file(identifier.GetFullPath().mb_str());
72 if (!result) {
73 warnings.push_back("Could not open " + identifier.GetFullName() + " (file not found or syntax error)");
74 return false;
75 }
76
77 pugi::xml_node node = doc.child("materials");
78 if (!node) {
79 warnings.push_back(identifier.GetFullName() + ": Invalid rootheader.");
80 return false;
81 }
82
83 unserializeMaterials(identifier, node, error, warnings);
84 return true;
85}
86
87bool Materials::loadExtensions(FileName directoryName, wxString& error, wxArrayString& warnings) {
88 directoryName.Mkdir(0755, wxPATH_MKDIR_FULL); // Create if it doesn't exist
89
90 wxDir ext_dir(directoryName.GetPath());
91 if (!ext_dir.IsOpened()) {
92 error = "Could not open extensions directory.";
93 return false;
94 }
95
96 wxString filename;
97 if (!ext_dir.GetFirst(&filename)) {
98 // No extensions found
99 return true;
100 }
101
102 StringVector clientVersions;
103 do {
104 FileName fn;
105 fn.SetPath(directoryName.GetPath());
106 fn.SetFullName(filename);
107 if (fn.GetExt() != "xml") {
108 continue;
109 }
110
111 pugi::xml_document doc;
112 pugi::xml_parse_result result = doc.load_file(fn.GetFullPath().mb_str());
113 if (!result) {
114 warnings.push_back("Could not open " + filename + " (file not found or syntax error)");
115 continue;
116 }
117
118 pugi::xml_node extensionNode = doc.child("materialsextension");
119 if (!extensionNode) {
120 warnings.push_back(filename + ": Invalid rootheader.");
121 continue;
122 }
123
124 pugi::xml_attribute attribute;
125 if (!(attribute = extensionNode.attribute("name"))) {
126 warnings.push_back(filename + ": Couldn't read extension name.");
127 continue;
128 }
129
130 const std::string& extensionName = attribute.as_string();
131 if (!(attribute = extensionNode.attribute("author"))) {
132 warnings.push_back(filename + ": Couldn't read extension name.");
133 continue;
134 }
135
136 const std::string& extensionAuthor = attribute.as_string();
137 if (!(attribute = extensionNode.attribute("description"))) {
138 warnings.push_back(filename + ": Couldn't read extension name.");
139 continue;
140 }
141
142 const std::string& extensionDescription = attribute.as_string();
143 if (extensionName.empty() || extensionAuthor.empty() || extensionDescription.empty()) {
144 warnings.push_back(filename + ": Couldn't read extension attributes (name, author, description).");
145 continue;
146 }
147
148 std::string extensionUrl = extensionNode.attribute("url").as_string();
149 extensionUrl.erase(std::remove(extensionUrl.begin(), extensionUrl.end(), '\''));
150
151 std::string extensionAuthorLink = extensionNode.attribute("authorurl").as_string();
152 extensionAuthorLink.erase(std::remove(extensionAuthorLink.begin(), extensionAuthorLink.end(), '\''));
153
154 MaterialsExtension* materialExtension = newd MaterialsExtension(extensionName, extensionAuthor, extensionDescription);
155 materialExtension->url = extensionUrl;
156 materialExtension->author_url = extensionAuthorLink;
157
158 if ((attribute = extensionNode.attribute("client"))) {
159 clientVersions.clear();
160 const std::string& extensionClientString = attribute.as_string();
161
162 size_t lastPosition = 0;
163 size_t position = extensionClientString.find(';');
164 while (position != std::string::npos) {
165 clientVersions.push_back(extensionClientString.substr(lastPosition, position - lastPosition));
166 lastPosition = position + 1;
167 position = extensionClientString.find(';', lastPosition);
168 }
169
170 clientVersions.push_back(extensionClientString.substr(lastPosition));
171 for (const std::string& version : clientVersions) {
172 materialExtension->addVersion(version);
173 }
174
175 std::sort(materialExtension->version_list.begin(), materialExtension->version_list.end(), VersionComparisonPredicate);
176
177 auto duplicate = std::unique(materialExtension->version_list.begin(), materialExtension->version_list.end());
178 while (duplicate != materialExtension->version_list.end()) {
179 materialExtension->version_list.erase(duplicate);
180 duplicate = std::unique(materialExtension->version_list.begin(), materialExtension->version_list.end());
181 }
182 } else {
183 warnings.push_back(filename + ": Extension is not available for any version.");
184 }
185
186 extensions.push_back(materialExtension);
187 if (materialExtension->isForVersion(g_gui.GetCurrentVersionID())) {
188 unserializeMaterials(filename, extensionNode, error, warnings);
189 }
190 } while (ext_dir.GetNext(&filename));
191
192 return true;
193}
194
195bool Materials::unserializeMaterials(const FileName& filename, pugi::xml_node node, wxString& error, wxArrayString& warnings) {
196 wxString warning;
197 pugi::xml_attribute attribute;
198 for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
199 const std::string& childName = as_lower_str(childNode.name());
200 if (childName == "include") {
201 if (!(attribute = childNode.attribute("file"))) {
202 continue;
203 }
204
205 FileName includeName;
206 includeName.SetPath(filename.GetPath());
207 includeName.SetFullName(wxString(attribute.as_string(), wxConvUTF8));
208
209 wxString subError;
210 if (!loadMaterials(includeName, subError, warnings)) {
211 warnings.push_back("Error while loading file \"" + includeName.GetFullName() + "\": " + subError);
212 }
213 } else if (childName == "metaitem") {
214 g_items.loadMetaItem(childNode);
215 } else if (childName == "border") {
216 g_brushes.unserializeBorder(childNode, warnings);
217 if (warning.size()) {
218 warnings.push_back("materials.xml: " + warning);
219 }
220 } else if (childName == "brush") {
221 g_brushes.unserializeBrush(childNode, warnings);
222 if (warning.size()) {
223 warnings.push_back("materials.xml: " + warning);
224 }
225 } else if (childName == "tileset") {
226 unserializeTileset(childNode, warnings);
227 }
228 }
229 return true;
230}
231
232void Materials::createOtherTileset() {
233 Tileset* others;
234 Tileset* npc_tileset;
235
236 if (tilesets["Others"] != nullptr) {
237 others = tilesets["Others"];
238 others->clear();
239 } else {
240 others = newd Tileset(g_brushes, "Others");
241 tilesets["Others"] = others;
242 }
243
244 if (tilesets["NPCs"] != nullptr) {
245 npc_tileset = tilesets["NPCs"];
246 npc_tileset->clear();
247 } else {
248 npc_tileset = newd Tileset(g_brushes, "NPCs");
249 tilesets["NPCs"] = npc_tileset;
250 }
251
252 // There should really be an iterator to do this
253 for (int32_t id = 0; id <= g_items.getMaxID(); ++id) {
254 ItemType& it = g_items[id];
255 if (it.id == 0) {
256 continue;
257 }
258
259 if (!it.isMetaItem()) {
260 Brush* brush;
261 if (it.in_other_tileset) {
262 others->getCategory(TILESET_RAW)->brushlist.push_back(it.raw_brush);
263 continue;
264 } else if (it.raw_brush == nullptr) {
265 brush = it.raw_brush = newd RAWBrush(it.id);
266 it.has_raw = true;
267 g_brushes.addBrush(it.raw_brush);
268 } else if (!it.has_raw) {
269 brush = it.raw_brush;
270 } else {
271 continue;
272 }
273
274 brush->flagAsVisible();
275 others->getCategory(TILESET_RAW)->brushlist.push_back(it.raw_brush);
276 it.in_other_tileset = true;
277 }
278 }
279
280 for (CreatureMap::iterator iter = g_creatures.begin(); iter != g_creatures.end(); ++iter) {
281 CreatureType* type = iter->second;
282 if (type->in_other_tileset) {
283 if (type->isNpc) {
284 npc_tileset->getCategory(TILESET_CREATURE)->brushlist.push_back(type->brush);
285 } else {
286 others->getCategory(TILESET_CREATURE)->brushlist.push_back(type->brush);
287 }
288 } else if (type->brush == nullptr) {
289 type->brush = newd CreatureBrush(type);
290 g_brushes.addBrush(type->brush);
291 type->brush->flagAsVisible();
292 type->in_other_tileset = true;
293 if (type->isNpc) {
294 npc_tileset->getCategory(TILESET_CREATURE)->brushlist.push_back(type->brush);
295 } else {
296 others->getCategory(TILESET_CREATURE)->brushlist.push_back(type->brush);
297 }
298 }
299 }
300}
301
302bool Materials::unserializeTileset(pugi::xml_node node, wxArrayString& warnings) {
303 pugi::xml_attribute attribute;
304 if (!(attribute = node.attribute("name"))) {
305 warnings.push_back("Couldn't read tileset name");
306 return false;
307 }
308
309 const std::string& name = attribute.as_string();
310
311 Tileset* tileset;
312 auto it = tilesets.find(name);
313 if (it != tilesets.end()) {
314 tileset = it->second;
315 } else {
316 tileset = newd Tileset(g_brushes, name);
317 tilesets.insert(std::make_pair(name, tileset));
318 }
319
320 for (pugi::xml_node childNode = node.first_child(); childNode; childNode = childNode.next_sibling()) {
321 tileset->loadCategory(childNode, warnings);
322 }
323 return true;
324}
325
326void Materials::addToTileset(std::string tilesetName, int itemId, TilesetCategoryType categoryType) {
327 ItemType& it = g_items[itemId];
328
329 if (it.id == 0) {
330 return;
331 }
332
333 Tileset* tileset;
334 auto _it = tilesets.find(tilesetName);
335 if (_it != tilesets.end()) {
336 tileset = _it->second;
337 } else {
338 tileset = newd Tileset(g_brushes, tilesetName);
339 tilesets.insert(std::make_pair(tilesetName, tileset));
340 }
341
342 TilesetCategory* category = tileset->getCategory(categoryType);
343
344 if (!it.isMetaItem()) {
345 Brush* brush;
346 if (it.in_other_tileset) {
347 category->brushlist.push_back(it.raw_brush);
348 return;
349 } else if (it.raw_brush == nullptr) {
350 brush = it.raw_brush = newd RAWBrush(it.id);
351 it.has_raw = true;
352 g_brushes.addBrush(it.raw_brush);
353 } else {
354 brush = it.raw_brush;
355 }
356
357 brush->flagAsVisible();
358 category->brushlist.push_back(it.raw_brush);
359 it.in_other_tileset = true;
360 }
361}
362
363bool Materials::isInTileset(Item* item, std::string tilesetName) const {
364 const ItemType& it = g_items[item->getID()];
365
366 return it.id != 0 && (isInTileset(it.brush, tilesetName) || isInTileset(it.doodad_brush, tilesetName) || isInTileset(it.raw_brush, tilesetName)) || isInTileset(it.collection_brush, tilesetName);
367}
368
369bool Materials::isInTileset(Brush* brush, std::string tilesetName) const {
370 if (!brush) {
371 return false;
372 }
373
374 TilesetContainer::const_iterator tilesetiter = tilesets.find(tilesetName);
375 if (tilesetiter == tilesets.end()) {
376 return false;
377 }
378 Tileset* tileset = tilesetiter->second;
379
380 return tileset->containsBrush(brush);
381}
382
Top