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 "materials.h"
21#include "gui.h"
22#include <string.h> // memcpy
23
24#include "items.h"
25#include "item.h"
26
27ItemDatabase g_items;
28
29ItemType::ItemType() :
30 sprite(nullptr),
31 id(0),
32 clientID(0),
33 brush(nullptr),
34 doodad_brush(nullptr),
35 collection_brush(nullptr),
36 raw_brush(nullptr),
37 is_metaitem(false),
38 has_raw(false),
39 in_other_tileset(false),
40 group(ITEM_GROUP_NONE),
41 type(ITEM_TYPE_NONE),
42 volume(0),
43 maxTextLen(0),
44 // writeOnceItemID(0),
45 ground_equivalent(0),
46 border_group(0),
47 has_equivalent(false),
48 wall_hate_me(false),
49 name(""),
50 description(""),
51 weight(0.0f),
52 attack(0),
53 defense(0),
54 armor(0),
55 charges(0),
56 slot_position(SLOTP_HAND),
57 weapon_type(WEAPON_NONE),
58 client_chargeable(false),
59 extra_chargeable(false),
60 ignoreLook(false),
61
62 isHangable(false),
63 hookEast(false),
64 hookSouth(false),
65 canReadText(false),
66 canWriteText(false),
67 replaceable(true),
68 decays(false),
69 stackable(false),
70 moveable(true),
71 alwaysOnBottom(false),
72 pickupable(false),
73 rotable(false),
74 isBorder(false),
75 isOptionalBorder(false),
76 isWall(false),
77 isBrushDoor(false),
78 isOpen(false),
79 isTable(false),
80 isCarpet(false),
81
82 floorChangeDown(false),
83 floorChangeNorth(false),
84 floorChangeSouth(false),
85 floorChangeEast(false),
86 floorChangeWest(false),
87 floorChange(false),
88
89 unpassable(false),
90 blockPickupable(false),
91 blockMissiles(false),
92 blockPathfinder(false),
93 hasElevation(false),
94
95 alwaysOnTopOrder(0),
96 rotateTo(0),
97 border_alignment(BORDER_NONE) {
98 ////
99}
100
101ItemType::~ItemType() {
102 ////
103}
104
105bool ItemType::isFloorChange() const {
106 return floorChange || floorChangeDown || floorChangeNorth || floorChangeSouth || floorChangeEast || floorChangeWest;
107}
108
109ItemDatabase::ItemDatabase() :
110 // Version information
111 MajorVersion(0),
112 MinorVersion(0),
113 BuildNumber(0),
114
115 // Count of GameSprite types
116 item_count(0),
117 effect_count(0),
118 monster_count(0),
119 distance_count(0),
120
121 minclientID(0),
122 maxclientID(0),
123
124 max_item_id(0) {
125 ////
126}
127
128ItemDatabase::~ItemDatabase() {
129 clear();
130}
131
132void ItemDatabase::clear() {
133 for (uint32_t i = 0; i < items.size(); i++) {
134 delete items[i];
135 items.set(i, nullptr);
136 }
137}
138
139bool ItemDatabase::loadFromOtbVer1(BinaryNode* itemNode, wxString& error, wxArrayString& warnings) {
140 uint8_t u8;
141
142 for (; itemNode != nullptr; itemNode = itemNode->advance()) {
143 if (!itemNode->getU8(u8)) {
144 // Invalid!
145 warnings.push_back("Invalid item type encountered...");
146 continue;
147 }
148
149 if (u8 == ITEM_GROUP_DEPRECATED) {
150 continue;
151 }
152
153 ItemType* t = newd ItemType();
154 t->group = ItemGroup_t(u8);
155
156 switch (t->group) {
157 case ITEM_GROUP_NONE:
158 case ITEM_GROUP_GROUND:
159 case ITEM_GROUP_SPLASH:
160 case ITEM_GROUP_FLUID:
161 case ITEM_GROUP_WEAPON:
162 case ITEM_GROUP_AMMUNITION:
163 case ITEM_GROUP_ARMOR:
164 case ITEM_GROUP_WRITEABLE:
165 case ITEM_GROUP_KEY:
166 break;
167 case ITEM_GROUP_DOOR:
168 t->type = ITEM_TYPE_DOOR;
169 break;
170 case ITEM_GROUP_CONTAINER:
171 t->type = ITEM_TYPE_CONTAINER;
172 break;
173 case ITEM_GROUP_RUNE:
174 t->client_chargeable = true;
175 break;
176 case ITEM_GROUP_TELEPORT:
177 t->type = ITEM_TYPE_TELEPORT;
178 break;
179 case ITEM_GROUP_MAGICFIELD:
180 t->type = ITEM_TYPE_MAGICFIELD;
181 break;
182 default:
183 warnings.push_back("Unknown item group declaration");
184 }
185
186 uint32_t flags;
187 if (itemNode->getU32(flags)) {
188 t->unpassable = ((flags & FLAG_UNPASSABLE) == FLAG_UNPASSABLE);
189 t->blockMissiles = ((flags & FLAG_BLOCK_MISSILES) == FLAG_BLOCK_MISSILES);
190 t->blockPathfinder = ((flags & FLAG_BLOCK_PATHFINDER) == FLAG_BLOCK_PATHFINDER);
191 t->hasElevation = ((flags & FLAG_HAS_ELEVATION) == FLAG_HAS_ELEVATION);
192 // t->useable = ((flags & FLAG_USEABLE) == FLAG_USEABLE);
193 t->pickupable = ((flags & FLAG_PICKUPABLE) == FLAG_PICKUPABLE);
194 t->moveable = ((flags & FLAG_MOVEABLE) == FLAG_MOVEABLE);
195 t->stackable = ((flags & FLAG_STACKABLE) == FLAG_STACKABLE);
196 t->floorChangeDown = ((flags & FLAG_FLOORCHANGEDOWN) == FLAG_FLOORCHANGEDOWN);
197 t->floorChangeNorth = ((flags & FLAG_FLOORCHANGENORTH) == FLAG_FLOORCHANGENORTH);
198 t->floorChangeEast = ((flags & FLAG_FLOORCHANGEEAST) == FLAG_FLOORCHANGEEAST);
199 t->floorChangeSouth = ((flags & FLAG_FLOORCHANGESOUTH) == FLAG_FLOORCHANGESOUTH);
200 t->floorChangeWest = ((flags & FLAG_FLOORCHANGEWEST) == FLAG_FLOORCHANGEWEST);
201 t->floorChange = t->floorChangeDown || t->floorChangeNorth || t->floorChangeEast || t->floorChangeSouth || t->floorChangeWest;
202 // Now this is confusing, just accept that the ALWAYSONTOP flag means it's always on bottom, got it?!
203 t->alwaysOnBottom = ((flags & FLAG_ALWAYSONTOP) == FLAG_ALWAYSONTOP);
204 t->isHangable = ((flags & FLAG_HANGABLE) == FLAG_HANGABLE);
205 t->hookEast = ((flags & FLAG_HOOK_EAST) == FLAG_HOOK_EAST);
206 t->hookSouth = ((flags & FLAG_HOOK_SOUTH) == FLAG_HOOK_SOUTH);
207 t->allowDistRead = ((flags & FLAG_ALLOWDISTREAD) == FLAG_ALLOWDISTREAD);
208 t->rotable = ((flags & FLAG_ROTABLE) == FLAG_ROTABLE);
209 t->canReadText = ((flags & FLAG_READABLE) == FLAG_READABLE);
210 }
211
212 uint8_t attribute;
213 while (itemNode->getU8(attribute)) {
214 uint16_t datalen;
215 if (!itemNode->getU16(datalen)) {
216 warnings.push_back("Invalid item type property");
217 break;
218 }
219
220 switch (attribute) {
221 case ITEM_ATTR_SERVERID: {
222 if (datalen != sizeof(uint16_t)) {
223 error = "items.otb: Unexpected data length of server id block (Should be 2 bytes)";
224 return false;
225 }
226 if (!itemNode->getU16(t->id)) {
227 warnings.push_back("Invalid item type property (2)");
228 }
229
230 if (max_item_id < t->id) {
231 max_item_id = t->id;
232 }
233 break;
234 }
235
236 case ITEM_ATTR_CLIENTID: {
237 if (datalen != sizeof(uint16_t)) {
238 error = "items.otb: Unexpected data length of client id block (Should be 2 bytes)";
239 return false;
240 }
241
242 if (!itemNode->getU16(t->clientID)) {
243 warnings.push_back("Invalid item type property (2)");
244 }
245
246 t->sprite = static_cast<GameSprite*>(g_gui.gfx.getSprite(t->clientID));
247 break;
248 }
249
250 case ITEM_ATTR_SPEED: {
251 if (datalen != sizeof(uint16_t)) {
252 error = "items.otb: Unexpected data length of speed block (Should be 2 bytes)";
253 return false;
254 }
255
256 // t->speed = itemNode->getU16();
257 if (!itemNode->skip(2)) { // Just skip two bytes, we don't need speed
258 warnings.push_back("Invalid item type property (3)");
259 }
260 break;
261 }
262
263 case ITEM_ATTR_LIGHT2: {
264 if (datalen != sizeof(lightBlock2)) {
265 warnings.push_back("items.otb: Unexpected data length of item light (2) block (Should be " + i2ws(sizeof(lightBlock2)) + " bytes)");
266 break;
267 }
268
269 if (!itemNode->skip(4)) { // Just skip two bytes, we don't need light
270 warnings.push_back("Invalid item type property (4)");
271 }
272
273 // t->lightLevel = itemNode->getU16();
274 // t->lightColor = itemNode->getU16();
275 break;
276 }
277
278 case ITEM_ATTR_TOPORDER: {
279 if (datalen != sizeof(uint8_t)) {
280 warnings.push_back("items.otb: Unexpected data length of item toporder block (Should be 1 byte)");
281 break;
282 }
283
284 uint8_t u8 = 0;
285 if (!itemNode->getU8(u8)) {
286 warnings.push_back("Invalid item type property (5)");
287 }
288
289 t->alwaysOnTopOrder = u8;
290 break;
291 }
292
293 case ITEM_ATTR_NAME: {
294 if (datalen >= 128) {
295 warnings.push_back("items.otb: Unexpected data length of item name block (Should be 128 bytes)");
296 break;
297 }
298
299 uint8_t name[128];
300 memset(&name, 0, 128);
301
302 if (!itemNode->getRAW(name, datalen)) {
303 warnings.push_back("Invalid item type property (6)");
304 break;
305 }
306 t->name = (char*)name;
307 break;
308 }
309
310 case ITEM_ATTR_DESCR: {
311 if (datalen >= 128) {
312 warnings.push_back("items.otb: Unexpected data length of item descr block (Should be 128 bytes)");
313 break;
314 }
315
316 uint8_t description[128];
317 memset(&description, 0, 128);
318
319 if (!itemNode->getRAW(description, datalen)) {
320 warnings.push_back("Invalid item type property (7)");
321 break;
322 }
323
324 t->description = (char*)description;
325 break;
326 }
327
328 case ITEM_ATTR_MAXITEMS: {
329 if (datalen != sizeof(unsigned short)) {
330 warnings.push_back("items.otb: Unexpected data length of item volume block (Should be 2 bytes)");
331 break;
332 }
333
334 if (!itemNode->getU16(t->volume)) {
335 warnings.push_back("Invalid item type property (8)");
336 }
337 break;
338 }
339
340 case ITEM_ATTR_WEIGHT: {
341 if (datalen != sizeof(double)) {
342 warnings.push_back("items.otb: Unexpected data length of item weight block (Should be 8 bytes)");
343 break;
344 }
345 uint8_t w[sizeof(double)];
346 if (!itemNode->getRAW(w, sizeof(double))) {
347 warnings.push_back("Invalid item type property (7)");
348 break;
349 }
350
351 double wi = *reinterpret_cast<double*>(&w);
352 t->weight = wi;
353 break;
354 }
355
356 case ITEM_ATTR_ROTATETO: {
357 if (datalen != sizeof(unsigned short)) {
358 warnings.push_back("items.otb: Unexpected data length of item rotateTo block (Should be 2 bytes)");
359 break;
360 }
361
362 uint16_t rotate;
363 if (!itemNode->getU16(rotate)) {
364 warnings.push_back("Invalid item type property (8)");
365 break;
366 }
367
368 t->rotateTo = rotate;
369 break;
370 }
371
372 case ITEM_ATTR_WRITEABLE3: {
373 if (datalen != sizeof(writeableBlock3)) {
374 warnings.push_back("items.otb: Unexpected data length of item toporder block (Should be 1 byte)");
375 break;
376 }
377
378 uint16_t readOnlyID;
379 uint16_t maxTextLen;
380
381 if (!itemNode->getU16(readOnlyID)) {
382 warnings.push_back("Invalid item type property (9)");
383 break;
384 }
385
386 if (!itemNode->getU16(maxTextLen)) {
387 warnings.push_back("Invalid item type property (10)");
388 break;
389 }
390
391 // t->readOnlyId = wb3->readOnlyId;
392 t->maxTextLen = maxTextLen;
393 break;
394 }
395
396 default: {
397 // skip unknown attributes
398 itemNode->skip(datalen);
399 // warnings.push_back("items.otb: Skipped unknown attribute");
400 break;
401 }
402 }
403 }
404
405 if (t) {
406 if (items[t->id]) {
407 warnings.push_back("items.otb: Duplicate items");
408 delete items[t->id];
409 }
410 items.set(t->id, t);
411 }
412 }
413 return true;
414}
415
416bool ItemDatabase::loadFromOtbVer2(BinaryNode* itemNode, wxString& error, wxArrayString& warnings) {
417 uint8_t u8;
418 for (; itemNode != nullptr; itemNode = itemNode->advance()) {
419 if (!itemNode->getU8(u8)) {
420 // Invalid!
421 warnings.push_back("Invalid item type encountered...");
422 continue;
423 }
424
425 if (ItemGroup_t(u8) == ITEM_GROUP_DEPRECATED) {
426 continue;
427 }
428
429 ItemType* t = newd ItemType();
430 t->group = ItemGroup_t(u8);
431
432 switch (t->group) {
433 case ITEM_GROUP_NONE:
434 case ITEM_GROUP_GROUND:
435 case ITEM_GROUP_SPLASH:
436 case ITEM_GROUP_FLUID:
437 break;
438 case ITEM_GROUP_DOOR:
439 t->type = ITEM_TYPE_DOOR;
440 break;
441 case ITEM_GROUP_CONTAINER:
442 t->type = ITEM_TYPE_CONTAINER;
443 break;
444 case ITEM_GROUP_RUNE:
445 t->client_chargeable = true;
446 break;
447 case ITEM_GROUP_TELEPORT:
448 t->type = ITEM_TYPE_TELEPORT;
449 break;
450 case ITEM_GROUP_MAGICFIELD:
451 t->type = ITEM_TYPE_MAGICFIELD;
452 break;
453 default:
454 warnings.push_back("Unknown item group declaration");
455 }
456
457 uint32_t flags;
458 if (itemNode->getU32(flags)) {
459 t->unpassable = ((flags & FLAG_UNPASSABLE) == FLAG_UNPASSABLE);
460 t->blockMissiles = ((flags & FLAG_BLOCK_MISSILES) == FLAG_BLOCK_MISSILES);
461 t->blockPathfinder = ((flags & FLAG_BLOCK_PATHFINDER) == FLAG_BLOCK_PATHFINDER);
462 t->hasElevation = ((flags & FLAG_HAS_ELEVATION) == FLAG_HAS_ELEVATION);
463 t->pickupable = ((flags & FLAG_PICKUPABLE) == FLAG_PICKUPABLE);
464 t->moveable = ((flags & FLAG_MOVEABLE) == FLAG_MOVEABLE);
465 t->stackable = ((flags & FLAG_STACKABLE) == FLAG_STACKABLE);
466 t->floorChangeDown = ((flags & FLAG_FLOORCHANGEDOWN) == FLAG_FLOORCHANGEDOWN);
467 t->floorChangeNorth = ((flags & FLAG_FLOORCHANGENORTH) == FLAG_FLOORCHANGENORTH);
468 t->floorChangeEast = ((flags & FLAG_FLOORCHANGEEAST) == FLAG_FLOORCHANGEEAST);
469 t->floorChangeSouth = ((flags & FLAG_FLOORCHANGESOUTH) == FLAG_FLOORCHANGESOUTH);
470 t->floorChangeWest = ((flags & FLAG_FLOORCHANGEWEST) == FLAG_FLOORCHANGEWEST);
471 t->floorChange = t->floorChangeDown || t->floorChangeNorth || t->floorChangeEast || t->floorChangeSouth || t->floorChangeWest;
472 // Now this is confusing, just accept that the ALWAYSONTOP flag means it's always on bottom, got it?!
473 t->alwaysOnBottom = ((flags & FLAG_ALWAYSONTOP) == FLAG_ALWAYSONTOP);
474 t->isHangable = ((flags & FLAG_HANGABLE) == FLAG_HANGABLE);
475 t->hookEast = ((flags & FLAG_HOOK_EAST) == FLAG_HOOK_EAST);
476 t->hookSouth = ((flags & FLAG_HOOK_SOUTH) == FLAG_HOOK_SOUTH);
477 t->allowDistRead = ((flags & FLAG_ALLOWDISTREAD) == FLAG_ALLOWDISTREAD);
478 t->rotable = ((flags & FLAG_ROTABLE) == FLAG_ROTABLE);
479 t->canReadText = ((flags & FLAG_READABLE) == FLAG_READABLE);
480 }
481
482 uint8_t attribute;
483 while (itemNode->getU8(attribute)) {
484 uint16_t datalen;
485 if (!itemNode->getU16(datalen)) {
486 warnings.push_back("Invalid item type property");
487 break;
488 }
489
490 switch (attribute) {
491 case ITEM_ATTR_SERVERID: {
492 if (datalen != sizeof(uint16_t)) {
493 error = "items.otb: Unexpected data length of server id block (Should be 2 bytes)";
494 return false;
495 }
496
497 if (!itemNode->getU16(t->id)) {
498 warnings.push_back("Invalid item type property (2)");
499 }
500
501 if (max_item_id < t->id) {
502 max_item_id = t->id;
503 }
504 break;
505 }
506
507 case ITEM_ATTR_CLIENTID: {
508 if (datalen != sizeof(uint16_t)) {
509 error = "items.otb: Unexpected data length of client id block (Should be 2 bytes)";
510 return false;
511 }
512
513 if (!itemNode->getU16(t->clientID)) {
514 warnings.push_back("Invalid item type property (2)");
515 }
516
517 t->sprite = static_cast<GameSprite*>(g_gui.gfx.getSprite(t->clientID));
518 break;
519 }
520
521 case ITEM_ATTR_SPEED: {
522 if (datalen != sizeof(uint16_t)) {
523 error = "items.otb: Unexpected data length of speed block (Should be 2 bytes)";
524 return false;
525 }
526
527 // t->speed = itemNode->getU16();
528 if (!itemNode->skip(2)) { // Just skip two bytes, we don't need speed
529 warnings.push_back("Invalid item type property (3)");
530 }
531 break;
532 }
533
534 case ITEM_ATTR_LIGHT2: {
535 if (datalen != sizeof(lightBlock2)) {
536 warnings.push_back("items.otb: Unexpected data length of item light (2) block (Should be " + i2ws(sizeof(lightBlock2)) + " bytes)");
537 break;
538 }
539
540 if (!itemNode->skip(4)) { // Just skip two bytes, we don't need light
541 warnings.push_back("Invalid item type property (4)");
542 }
543
544 // t->lightLevel = itemNode->getU16();
545 // t->lightColor = itemNode->getU16();
546 break;
547 }
548
549 case ITEM_ATTR_TOPORDER: {
550 if (datalen != sizeof(uint8_t)) {
551 warnings.push_back("items.otb: Unexpected data length of item toporder block (Should be 1 byte)");
552 break;
553 }
554
555 uint8_t u8 = 0;
556 if (!itemNode->getU8(u8)) {
557 warnings.push_back("Invalid item type property (5)");
558 }
559 t->alwaysOnTopOrder = u8;
560 break;
561 }
562
563 default: {
564 // skip unknown attributes
565 itemNode->skip(datalen);
566 // warnings.push_back("items.otb: Skipped unknown attribute");
567 break;
568 }
569 }
570 }
571
572 if (t) {
573 if (items[t->id]) {
574 warnings.push_back("items.otb: Duplicate items");
575 delete items[t->id];
576 }
577 items.set(t->id, t);
578 }
579 }
580 return true;
581}
582
583bool ItemDatabase::loadFromOtbVer3(BinaryNode* itemNode, wxString& error, wxArrayString& warnings) {
584 uint8_t u8;
585 for (; itemNode != nullptr; itemNode = itemNode->advance()) {
586 if (!itemNode->getU8(u8)) {
587 // Invalid!
588 warnings.push_back("Invalid item type encountered...");
589 continue;
590 }
591
592 if (ItemGroup_t(u8) == ITEM_GROUP_DEPRECATED) {
593 continue;
594 }
595
596 ItemType* t = newd ItemType();
597 t->group = ItemGroup_t(u8);
598
599 switch (t->group) {
600 case ITEM_GROUP_NONE:
601 case ITEM_GROUP_GROUND:
602 case ITEM_GROUP_SPLASH:
603 case ITEM_GROUP_FLUID:
604 break;
605 case ITEM_GROUP_CONTAINER:
606 t->type = ITEM_TYPE_CONTAINER;
607 break;
608 case ITEM_GROUP_PODIUM:
609 t->type = ITEM_TYPE_PODIUM;
610 break;
611 default:
612 warnings.push_back("Unknown item group declaration");
613 }
614
615 uint32_t flags;
616 if (itemNode->getU32(flags)) {
617 t->unpassable = ((flags & FLAG_UNPASSABLE) == FLAG_UNPASSABLE);
618 t->blockMissiles = ((flags & FLAG_BLOCK_MISSILES) == FLAG_BLOCK_MISSILES);
619 t->blockPathfinder = ((flags & FLAG_BLOCK_PATHFINDER) == FLAG_BLOCK_PATHFINDER);
620 t->hasElevation = ((flags & FLAG_HAS_ELEVATION) == FLAG_HAS_ELEVATION);
621 t->pickupable = ((flags & FLAG_PICKUPABLE) == FLAG_PICKUPABLE);
622 t->moveable = ((flags & FLAG_MOVEABLE) == FLAG_MOVEABLE);
623 t->stackable = ((flags & FLAG_STACKABLE) == FLAG_STACKABLE);
624 t->floorChangeDown = ((flags & FLAG_FLOORCHANGEDOWN) == FLAG_FLOORCHANGEDOWN);
625 t->floorChangeNorth = ((flags & FLAG_FLOORCHANGENORTH) == FLAG_FLOORCHANGENORTH);
626 t->floorChangeEast = ((flags & FLAG_FLOORCHANGEEAST) == FLAG_FLOORCHANGEEAST);
627 t->floorChangeSouth = ((flags & FLAG_FLOORCHANGESOUTH) == FLAG_FLOORCHANGESOUTH);
628 t->floorChangeWest = ((flags & FLAG_FLOORCHANGEWEST) == FLAG_FLOORCHANGEWEST);
629 t->floorChange = t->floorChangeDown || t->floorChangeNorth || t->floorChangeEast || t->floorChangeSouth || t->floorChangeWest;
630 // Now this is confusing, just accept that the ALWAYSONTOP flag means it's always on bottom, got it?!
631 t->alwaysOnBottom = ((flags & FLAG_ALWAYSONTOP) == FLAG_ALWAYSONTOP);
632 t->isHangable = ((flags & FLAG_HANGABLE) == FLAG_HANGABLE);
633 t->hookEast = ((flags & FLAG_HOOK_EAST) == FLAG_HOOK_EAST);
634 t->hookSouth = ((flags & FLAG_HOOK_SOUTH) == FLAG_HOOK_SOUTH);
635 t->allowDistRead = ((flags & FLAG_ALLOWDISTREAD) == FLAG_ALLOWDISTREAD);
636 t->rotable = ((flags & FLAG_ROTABLE) == FLAG_ROTABLE);
637 t->canReadText = ((flags & FLAG_READABLE) == FLAG_READABLE);
638 t->client_chargeable = ((flags & FLAG_CLIENTCHARGES) == FLAG_CLIENTCHARGES);
639 t->ignoreLook = ((flags & FLAG_IGNORE_LOOK) == FLAG_IGNORE_LOOK);
640 }
641
642 uint8_t attribute;
643 while (itemNode->getU8(attribute)) {
644 uint16_t datalen;
645 if (!itemNode->getU16(datalen)) {
646 warnings.push_back("Invalid item type property");
647 break;
648 }
649
650 switch (attribute) {
651 case ITEM_ATTR_SERVERID: {
652 if (datalen != sizeof(uint16_t)) {
653 error = "items.otb: Unexpected data length of server id block (Should be 2 bytes)";
654 return false;
655 }
656
657 if (!itemNode->getU16(t->id)) {
658 warnings.push_back("Invalid item type property (2)");
659 }
660
661 if (max_item_id < t->id) {
662 max_item_id = t->id;
663 }
664 break;
665 }
666
667 case ITEM_ATTR_CLIENTID: {
668 if (datalen != sizeof(uint16_t)) {
669 error = "items.otb: Unexpected data length of client id block (Should be 2 bytes)";
670 return false;
671 }
672
673 if (!itemNode->getU16(t->clientID)) {
674 warnings.push_back("Invalid item type property (2)");
675 }
676
677 t->sprite = static_cast<GameSprite*>(g_gui.gfx.getSprite(t->clientID));
678 break;
679 }
680
681 case ITEM_ATTR_SPEED: {
682 if (datalen != sizeof(uint16_t)) {
683 error = "items.otb: Unexpected data length of speed block (Should be 2 bytes)";
684 return false;
685 }
686
687 // t->speed = itemNode->getU16();
688 if (!itemNode->skip(2)) { // Just skip two bytes, we don't need speed
689 warnings.push_back("Invalid item type property (3)");
690 }
691 break;
692 }
693
694 case ITEM_ATTR_LIGHT2: {
695 if (datalen != sizeof(lightBlock2)) {
696 warnings.push_back("items.otb: Unexpected data length of item light (2) block (Should be " + i2ws(sizeof(lightBlock2)) + " bytes)");
697 break;
698 }
699 if (!itemNode->skip(4)) { // Just skip two bytes, we don't need light
700 warnings.push_back("Invalid item type property (4)");
701 }
702
703 // t->lightLevel = itemNode->getU16();
704 // t->lightColor = itemNode->getU16();
705 break;
706 }
707
708 case ITEM_ATTR_TOPORDER: {
709 if (datalen != sizeof(uint8_t)) {
710 warnings.push_back("items.otb: Unexpected data length of item toporder block (Should be 1 byte)");
711 break;
712 }
713
714 if (!itemNode->getU8(u8)) {
715 warnings.push_back("Invalid item type property (5)");
716 }
717
718 t->alwaysOnTopOrder = u8;
719 break;
720 }
721
722 case ITEM_ATTR_CLASSIFICATION: {
723 if (datalen != sizeof(uint8_t)) {
724 warnings.push_back("items.otb: Unexpected data length of item classification block (Should be 1 byte)");
725 break;
726 }
727
728 if (!itemNode->getU8(u8)) {
729 warnings.push_back("Invalid item type property (5)");
730 }
731
732 t->classification = u8;
733 break;
734 }
735
736 default: {
737 // skip unknown attributes
738 itemNode->skip(datalen);
739 // warnings.push_back("items.otb: Skipped unknown attribute");
740 break;
741 }
742 }
743 }
744
745 if (t) {
746 if (items[t->id]) {
747 warnings.push_back("items.otb: Duplicate items");
748 delete items[t->id];
749 }
750 items.set(t->id, t);
751 }
752 }
753 return true;
754}
755
756bool ItemDatabase::loadFromOtb(const FileName& datafile, wxString& error, wxArrayString& warnings) {
757 std::string filename = nstr((datafile.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + datafile.GetFullName()));
758 DiskNodeFileReadHandle f(filename, StringVector(1, "OTBI"));
759
760 if (!f.isOk()) {
761 error = "Couldn't open file \"" + wxstr(filename) + "\":" + wxstr(f.getErrorMessage());
762 return false;
763 }
764
765 BinaryNode* root = f.getRootNode();
766
767#define safe_get(node, func, ...) \
768 do { \
769 if (!node->get##func(__VA_ARGS__)) { \
770 error = wxstr(f.getErrorMessage()); \
771 return false; \
772 } \
773 } while (false)
774
775 // Read root flags
776 root->skip(1); // Type info
777 // uint32_t flags =
778
779 root->skip(4); // Unused?
780
781 uint8_t attr;
782 safe_get(root, U8, attr);
783 if (attr == ROOT_ATTR_VERSION) {
784 uint16_t datalen;
785 if (!root->getU16(datalen) || datalen != 4 + 4 + 4 + 1 * 128) {
786 error = "items.otb: Size of version header is invalid, updated .otb version?";
787 return false;
788 }
789 safe_get(root, U32, MajorVersion); // items otb format file version
790 safe_get(root, U32, MinorVersion); // client version
791 safe_get(root, U32, BuildNumber); // revision
792 std::string csd;
793 csd.resize(128);
794
795 if (!root->getRAW((uint8_t*)csd.data(), 128)) { // CSDVersion ??
796 error = wxstr(f.getErrorMessage());
797 return false;
798 }
799 } else {
800 error = "Expected ROOT_ATTR_VERSION as first node of items.otb!";
801 }
802
803 if (g_settings.getInteger(Config::CHECK_SIGNATURES)) {
804 if (g_gui.GetCurrentVersion().getOTBVersion().format_version != MajorVersion) {
805 error = "Unsupported items.otb version (version " + i2ws(MajorVersion) + ")";
806 return false;
807 }
808 }
809
810 BinaryNode* itemNode = root->getChild();
811 switch (MajorVersion) {
812 case 1:
813 return loadFromOtbVer1(itemNode, error, warnings);
814 case 2:
815 return loadFromOtbVer2(itemNode, error, warnings);
816 case 3:
817 return loadFromOtbVer3(itemNode, error, warnings);
818 }
819 return true;
820}
821
822bool ItemDatabase::loadItemFromGameXml(pugi::xml_node itemNode, int id) {
823 ClientVersionID clientVersion = g_gui.GetCurrentVersionID();
824 if (clientVersion < CLIENT_VERSION_980 && id > 20000 && id < 20100) {
825 itemNode = itemNode.next_sibling();
826 return true;
827 } else if (id > 30000 && id < 30100) {
828 itemNode = itemNode.next_sibling();
829 return true;
830 }
831
832 ItemType& it = getItemType(id);
833
834 it.name = itemNode.attribute("name").as_string();
835 it.editorsuffix = itemNode.attribute("editorsuffix").as_string();
836
837 pugi::xml_attribute attribute;
838 for (pugi::xml_node itemAttributesNode = itemNode.first_child(); itemAttributesNode; itemAttributesNode = itemAttributesNode.next_sibling()) {
839 if (!(attribute = itemAttributesNode.attribute("key"))) {
840 continue;
841 }
842
843 std::string key = attribute.as_string();
844 to_lower_str(key);
845 if (key == "type") {
846 if (!(attribute = itemAttributesNode.attribute("value"))) {
847 continue;
848 }
849
850 std::string typeValue = attribute.as_string();
851 to_lower_str(key);
852 if (typeValue == "depot") {
853 it.type = ITEM_TYPE_DEPOT;
854 } else if (typeValue == "mailbox") {
855 it.type = ITEM_TYPE_MAILBOX;
856 } else if (typeValue == "trashholder") {
857 it.type = ITEM_TYPE_TRASHHOLDER;
858 } else if (typeValue == "container") {
859 it.type = ITEM_TYPE_CONTAINER;
860 } else if (typeValue == "door") {
861 it.type = ITEM_TYPE_DOOR;
862 } else if (typeValue == "magicfield") {
863 it.group = ITEM_GROUP_MAGICFIELD;
864 it.type = ITEM_TYPE_MAGICFIELD;
865 } else if (typeValue == "teleport") {
866 it.type = ITEM_TYPE_TELEPORT;
867 } else if (typeValue == "bed") {
868 it.type = ITEM_TYPE_BED;
869 } else if (typeValue == "key") {
870 it.type = ITEM_TYPE_KEY;
871 } else if (typeValue == "podium") {
872 it.type = ITEM_TYPE_PODIUM;
873 }
874 } else if (key == "name") {
875 if ((attribute = itemAttributesNode.attribute("value"))) {
876 it.name = attribute.as_string();
877 }
878 } else if (key == "description") {
879 if ((attribute = itemAttributesNode.attribute("value"))) {
880 it.description = attribute.as_string();
881 }
882 } else if (key == "runespellName") {
883 /*if((attribute = itemAttributesNode.attribute("value"))) {
884 it.runeSpellName = attribute.as_string();
885 }*/
886 } else if (key == "weight") {
887 if ((attribute = itemAttributesNode.attribute("value"))) {
888 it.weight = attribute.as_int() / 100.f;
889 }
890 } else if (key == "armor") {
891 if ((attribute = itemAttributesNode.attribute("value"))) {
892 it.armor = attribute.as_int();
893 }
894 } else if (key == "defense") {
895 if ((attribute = itemAttributesNode.attribute("value"))) {
896 it.defense = attribute.as_int();
897 }
898 } else if (key == "slottype") {
899 if ((attribute = itemAttributesNode.attribute("value"))) {
900 std::string typeValue = attribute.as_string();
901 if (typeValue == "head") {
902 it.slot_position |= SLOTP_HEAD;
903 } else if (typeValue == "body") {
904 it.slot_position |= SLOTP_ARMOR;
905 } else if (typeValue == "legs") {
906 it.slot_position |= SLOTP_LEGS;
907 } else if (typeValue == "feet") {
908 it.slot_position |= SLOTP_FEET;
909 } else if (typeValue == "backpack") {
910 it.slot_position |= SLOTP_BACKPACK;
911 } else if (typeValue == "two-handed") {
912 it.slot_position |= SLOTP_TWO_HAND;
913 } else if (typeValue == "right-hand") {
914 it.slot_position &= ~SLOTP_LEFT;
915 } else if (typeValue == "left-hand") {
916 it.slot_position &= ~SLOTP_RIGHT;
917 } else if (typeValue == "necklace") {
918 it.slot_position |= SLOTP_NECKLACE;
919 } else if (typeValue == "ring") {
920 it.slot_position |= SLOTP_RING;
921 } else if (typeValue == "ammo") {
922 it.slot_position |= SLOTP_AMMO;
923 } else if (typeValue == "hand") {
924 it.slot_position |= SLOTP_HAND;
925 }
926 }
927 } else if (key == "weapontype") {
928 if ((attribute = itemAttributesNode.attribute("value"))) {
929 std::string typeValue = attribute.as_string();
930 if (typeValue == "sword") {
931 it.weapon_type = WEAPON_SWORD;
932 } else if (typeValue == "club") {
933 it.weapon_type = WEAPON_CLUB;
934 } else if (typeValue == "axe") {
935 it.weapon_type = WEAPON_AXE;
936 } else if (typeValue == "shield") {
937 it.weapon_type = WEAPON_SHIELD;
938 } else if (typeValue == "distance") {
939 it.weapon_type = WEAPON_DISTANCE;
940 } else if (typeValue == "wand") {
941 it.weapon_type = WEAPON_WAND;
942 } else if (typeValue == "ammunition") {
943 it.weapon_type = WEAPON_AMMO;
944 }
945 }
946 } else if (key == "rotateto") {
947 if ((attribute = itemAttributesNode.attribute("value"))) {
948 it.rotateTo = attribute.as_ushort();
949 }
950 } else if (key == "containersize") {
951 if ((attribute = itemAttributesNode.attribute("value"))) {
952 it.volume = attribute.as_ushort();
953 }
954 } else if (key == "readable") {
955 if ((attribute = itemAttributesNode.attribute("value"))) {
956 it.canReadText = attribute.as_bool();
957 }
958 } else if (key == "writeable") {
959 if ((attribute = itemAttributesNode.attribute("value"))) {
960 it.canWriteText = it.canReadText = attribute.as_bool();
961 }
962 } else if (key == "decayto") {
963 it.decays = true;
964 } else if (key == "maxtextlen" || key == "maxtextlength") {
965 if ((attribute = itemAttributesNode.attribute("value"))) {
966 it.maxTextLen = attribute.as_ushort();
967 it.canReadText = it.maxTextLen > 0;
968 }
969 } else if (key == "writeonceitemid") {
970 /*if((attribute = itemAttributesNode.attribute("value"))) {
971 it.writeOnceItemId = pugi::cast<int32_t>(attribute.value());
972 }*/
973 } else if (key == "allowdistread") {
974 if ((attribute = itemAttributesNode.attribute("value"))) {
975 it.allowDistRead = attribute.as_bool();
976 }
977 } else if (key == "charges") {
978 if ((attribute = itemAttributesNode.attribute("value"))) {
979 it.charges = attribute.as_uint();
980 it.extra_chargeable = true;
981 }
982 } else if (key == "floorchange") {
983 if ((attribute = itemAttributesNode.attribute("value"))) {
984 std::string value = attribute.as_string();
985 if (value == "down") {
986 it.floorChangeDown = true;
987 it.floorChange = true;
988 } else if (value == "north") {
989 it.floorChangeNorth = true;
990 it.floorChange = true;
991 } else if (value == "south") {
992 it.floorChangeSouth = true;
993 it.floorChange = true;
994 } else if (value == "west") {
995 it.floorChangeWest = true;
996 it.floorChange = true;
997 } else if (value == "east") {
998 it.floorChangeEast = true;
999 it.floorChange = true;
1000 } else if (value == "northex") {
1001 it.floorChange = true;
1002 } else if (value == "southex") {
1003 it.floorChange = true;
1004 } else if (value == "westex") {
1005 it.floorChange = true;
1006 } else if (value == "eastex") {
1007 it.floorChange = true;
1008 } else if (value == "southalt") {
1009 it.floorChange = true;
1010 } else if (value == "eastalt") {
1011 it.floorChange = true;
1012 }
1013 }
1014 }
1015 }
1016 return true;
1017}
1018
1019bool ItemDatabase::loadFromGameXml(const FileName& identifier, wxString& error, wxArrayString& warnings) {
1020 pugi::xml_document doc;
1021 pugi::xml_parse_result result = doc.load_file(identifier.GetFullPath().mb_str());
1022 if (!result) {
1023 error = "Could not load items.xml (Syntax error?)";
1024 return false;
1025 }
1026
1027 pugi::xml_node node = doc.child("items");
1028 if (!node) {
1029 error = "items.xml, invalid root node.";
1030 return false;
1031 }
1032
1033 for (pugi::xml_node itemNode = node.first_child(); itemNode; itemNode = itemNode.next_sibling()) {
1034 if (as_lower_str(itemNode.name()) != "item") {
1035 continue;
1036 }
1037
1038 uint16_t fromId = 0;
1039 uint16_t toId = 0;
1040 if (const pugi::xml_attribute attribute = itemNode.attribute("id")) {
1041 fromId = toId = attribute.as_ushort();
1042 } else {
1043 fromId = itemNode.attribute("fromid").as_ushort();
1044 toId = itemNode.attribute("toid").as_ushort();
1045 }
1046
1047 if (fromId == 0 || toId == 0) {
1048 error = "Could not read item id from item node.";
1049 return false;
1050 }
1051
1052 for (uint16_t id = fromId; id <= toId; ++id) {
1053 if (!loadItemFromGameXml(itemNode, id)) {
1054 return false;
1055 }
1056 }
1057 }
1058 return true;
1059}
1060
1061bool ItemDatabase::loadMetaItem(pugi::xml_node node) {
1062 if (const pugi::xml_attribute attribute = node.attribute("id")) {
1063 const uint16_t id = attribute.as_ushort();
1064 if (id == 0 || items[id]) {
1065 return false;
1066 }
1067 items.set(id, newd ItemType());
1068 items[id]->is_metaitem = true;
1069 items[id]->id = id;
1070 return true;
1071 }
1072 return false;
1073}
1074
1075ItemType& ItemDatabase::getItemType(int id) {
1076 ItemType* it = items[id];
1077 if (it) {
1078 return *it;
1079 } else {
1080 static ItemType dummyItemType; // use this for invalid ids
1081 return dummyItemType;
1082 }
1083}
1084
1085bool ItemDatabase::typeExists(int id) const {
1086 ItemType* it = items[id];
1087 return it != nullptr;
1088}
1089