GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/db/itemdb.cpp Lines: 324 565 57.3 %
Date: 2021-03-17 Branches: 365 1071 34.1 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "resources/db/itemdb.h"
25
26
#include "const/resources/map/map.h"
27
28
#include "configuration.h"
29
30
#include "enums/resources/spritedirection.h"
31
32
#include "fs/virtfs/tools.h"
33
34
#include "resources/iteminfo.h"
35
#include "resources/itemmenuitem.h"
36
#include "resources/itemtypemapdata.h"
37
38
#include "resources/db/itemfielddb.h"
39
40
#include "resources/sprite/spritereference.h"
41
42
#ifdef TMWA_SUPPORT
43
#include "net/net.h"
44
#endif  // TMWA_SUPPORT
45
46
#include "utils/checkutils.h"
47
#include "utils/delete2.h"
48
#include "utils/dtor.h"
49
#include "utils/foreach.h"
50
#include "utils/itemxmlutils.h"
51
#include "utils/stdmove.h"
52
#include "utils/stringmap.h"
53
54
#include "debug.h"
55
56
namespace
57
{
58
1
    ItemDB::ItemInfos mItemInfos;
59
1
    ItemDB::NamedItemInfos mNamedItemInfos;
60
    ItemInfo *mUnknown = nullptr;
61
    bool mLoaded = false;
62
    bool mConstructed = false;
63
1
    StringVect mTagNames;
64
1
    StringIntMap mTags;
65
1
    std::map<std::string, ItemSoundEvent::Type> mSoundNames;
66
    int mNumberOfHairstyles = 1;
67
}  // namespace
68
69
// Forward declarations
70
static void loadSpriteRef(ItemInfo *const itemInfo,
71
                          XmlNodeConstPtr node) A_NONNULL(1);
72
static void loadSoundRef(ItemInfo *const itemInfo,
73
                         XmlNodeConstPtr node) A_NONNULL(1);
74
static void loadFloorSprite(SpriteDisplay &display,
75
                            XmlNodeConstPtrConst node);
76
static void loadReplaceSprite(ItemInfo *const itemInfo,
77
                              XmlNodeConstPtr replaceNode) A_NONNULL(1);
78
static void loadOrderSprite(ItemInfo *const itemInfo,
79
                            XmlNodeConstPtr node,
80
                            const bool drawAfter) A_NONNULL(1);
81
static int parseSpriteName(const std::string &name);
82
static int parseDirectionName(const std::string &name);
83
84
72
static ItemDbTypeT itemTypeFromString(const std::string &name)
85
{
86
72
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
87
1296
    for (size_t f = 0; f < sz; f ++)
88
    {
89
1296
        const ItemTypeMap &type = itemTypeMap[f];
90
1296
        if (type.name == name)
91
72
            return type.type;
92
    }
93
    logger->log("Unknown item type: " + name);
94
    return ItemDbType::UNUSABLE;
95
}
96
97
30
static std::string useButtonFromItemType(const ItemDbTypeT &type)
98
{
99
30
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
100
546
    for (size_t f = 0; f < sz; f ++)
101
    {
102
546
        const ItemTypeMap &item = itemTypeMap[f];
103
546
        if (item.type == type)
104
        {
105
60
            if (item.useButton.empty())
106
                return std::string();
107
            return gettext(item.useButton.c_str());
108
        }
109
    }
110
    logger->log("Unknown item type");
111
    return std::string();
112
}
113
114
36
static std::string useButton2FromItemType(const ItemDbTypeT &type)
115
{
116
36
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
117
648
    for (size_t f = 0; f < sz; f ++)
118
    {
119
648
        const ItemTypeMap &item = itemTypeMap[f];
120
648
        if (item.type == type)
121
        {
122
72
            if (item.useButton2.empty())
123
                return std::string();
124
            return gettext(item.useButton2.c_str());
125
        }
126
    }
127
    logger->log("Unknown item type");
128
    return std::string();
129
}
130
131
1
static void initStatic()
132
{
133
1
    mConstructed = true;
134
4
    mSoundNames["hit"] = ItemSoundEvent::HIT;
135
4
    mSoundNames["strike"] = ItemSoundEvent::MISS;
136
4
    mSoundNames["miss"] = ItemSoundEvent::MISS;
137
4
    mSoundNames["use"] = ItemSoundEvent::USE;
138
4
    mSoundNames["equip"] = ItemSoundEvent::EQUIP;
139
3
    mSoundNames["unequip"] = ItemSoundEvent::UNEQUIP;
140
4
    mSoundNames["drop"] = ItemSoundEvent::DROP;
141
4
    mSoundNames["pickup"] = ItemSoundEvent::PICKUP;
142
4
    mSoundNames["take"] = ItemSoundEvent::TAKE;
143
4
    mSoundNames["put"] = ItemSoundEvent::PUT;
144
4
    mSoundNames["usecard"] = ItemSoundEvent::USECARD;
145
1
}
146
147
3
void ItemDB::load()
148
{
149
3
    if (mLoaded)
150
        unload();
151
152
3
    logger->log1("Initializing item database...");
153
154
3
    if (!mConstructed)
155
1
        initStatic();
156
157
3
    int tagNum = 0;
158
159
3
    mTags.clear();
160
3
    mTagNames.clear();
161
15
    mTagNames.push_back("All");
162
15
    mTagNames.push_back("Usable");
163
15
    mTagNames.push_back("Unusable");
164
15
    mTagNames.push_back("Equipment");
165
12
    mTags["All"] = tagNum ++;
166
12
    mTags["Usable"] = tagNum ++;
167
12
    mTags["Unusable"] = tagNum ++;
168
12
    mTags["Equipment"] = tagNum ++;
169
170
3
    mUnknown = new ItemInfo;
171
    // TRANSLATORS: item name
172
15
    mUnknown->setName(_("Unknown item"));
173
6
    mUnknown->setDisplay(SpriteDisplay());
174
15
    std::string errFile = paths.getStringValue("spriteErrorFile");
175
3
    mUnknown->setSprite(errFile, Gender::MALE, 0);
176
3
    mUnknown->setSprite(errFile, Gender::FEMALE, 0);
177

15
    mUnknown->addTag(mTags["All"]);
178

15
    loadXmlFile(paths.getStringValue("itemsFile"),
179
        tagNum,
180
3
        SkipError_false);
181

15
    loadXmlFile(paths.getStringValue("itemsPatchFile"),
182
        tagNum,
183
3
        SkipError_true);
184
185
6
    StringVect list;
186

24
    VirtFs::getFilesInDir(paths.getStringValue("itemsPatchDir"),
187
        list,
188
3
        ".xml");
189
15
    FOR_EACH (StringVectCIter, it, list)
190
        loadXmlFile(*it, tagNum, SkipError_true);
191
192
    // Hairstyles are encoded as negative numbers. Count how far negative
193
    // we can go.
194
    int hairstyles = 1;
195

39
    while (ItemDB::exists(-hairstyles) &&
196
6
           ItemDB::get(-hairstyles).getSprite(Gender::MALE,
197



45
           BeingTypeId_zero) != paths.getStringValue("spriteErrorFile"))
198
    {
199
6
        hairstyles ++;
200
    }
201
3
    mNumberOfHairstyles = hairstyles;
202
203
3
    int races = 100;
204

114
    while (ItemDB::exists(-races) &&
205

42
           ItemDB::get(-races).getSprite(Gender::MALE, BeingTypeId_zero) !=
206



108
           paths.getStringValue("spriteErrorFile"))
207
    {
208
21
        races ++;
209
    }
210
3
}
211
212
static void loadMenu(XmlNodePtrConst parentNode,
213
                     STD_VECTOR<ItemMenuItem> &menu)
214
{
215
    for_each_xml_child_node(node, parentNode)
216
    {
217
        if (xmlNameEqual(node, "menu"))
218
        {
219
            const std::string name1 = XML::langProperty(node,
220
                "name1", "");
221
            const std::string name2 = XML::langProperty(node,
222
                "name2", "");
223
            const std::string command1 = XML::getProperty(node,
224
                "command1", "");
225
            const std::string command2 = XML::getProperty(node,
226
                "command2", command1);
227
            menu.push_back(ItemMenuItem(name1,
228
                name2,
229
                command1,
230
                command2));
231
        }
232
    }
233
}
234
235
static bool getIsEquipment(const ItemDbTypeT type)
236
{
237
    switch (type)
238
    {
239
        case ItemDbType::EQUIPMENT_ONE_HAND_WEAPON:
240
        case ItemDbType::EQUIPMENT_TWO_HANDS_WEAPON:
241
        case ItemDbType::EQUIPMENT_TORSO:
242
        case ItemDbType::EQUIPMENT_ARMS:
243
        case ItemDbType::EQUIPMENT_HEAD:
244
        case ItemDbType::EQUIPMENT_LEGS:
245
        case ItemDbType::EQUIPMENT_SHIELD:
246
        case ItemDbType::EQUIPMENT_RING:
247
        case ItemDbType::EQUIPMENT_NECKLACE:
248
        case ItemDbType::EQUIPMENT_FEET:
249
        case ItemDbType::EQUIPMENT_AMMO:
250
        case ItemDbType::EQUIPMENT_CHARM:
251
            return true;
252
        case ItemDbType::UNUSABLE:
253
        case ItemDbType::USABLE:
254
        case ItemDbType::CARD:
255
        case ItemDbType::SPRITE_RACE:
256
        case ItemDbType::SPRITE_HAIR:
257
        default:
258
            return false;
259
    }
260
}
261
262
6
void ItemDB::loadXmlFile(const std::string &fileName,
263
                         int &tagNum,
264
                         const SkipError skipError)
265
{
266
6
    if (fileName.empty())
267
    {
268
        mLoaded = true;
269
3
        return;
270
    }
271
272
    XML::Document doc(fileName,
273
        UseVirtFs_true,
274
9
        skipError);
275
6
    XmlNodeConstPtrConst rootNode = doc.rootNode();
276
277


6
    if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "items"))
278
    {
279
3
        logger->log("ItemDB: Error while loading %s!", fileName.c_str());
280
3
        mLoaded = true;
281
3
        return;
282
    }
283
284
    const ItemFieldInfos &requiredFields =
285
3
        ItemFieldDb::getRequiredFields();
286
    const ItemFieldInfos &addFields =
287
3
        ItemFieldDb::getAddFields();
288
289
102
    for_each_xml_child_node(node, rootNode)
290
    {
291

99
        if (xmlNameEqual(node, "include"))
292
        {
293
            const std::string name = XML::getProperty(node, "name", "");
294
            if (!name.empty())
295
                loadXmlFile(name, tagNum, skipError);
296
            continue;
297
        }
298

99
        if (!xmlNameEqual(node, "item"))
299
            continue;
300
301
36
        const int id = XML::getProperty(node, "id", 0);
302
36
        ItemInfo *itemInfo = nullptr;
303
304
36
        if (id == 0)
305
        {
306
            reportAlways("ItemDB: Invalid or missing item ID in %s!",
307
                fileName.c_str())
308
            continue;
309
        }
310
72
        else if (mItemInfos.find(id) != mItemInfos.end())
311
        {
312
            logger->log("ItemDB: Redefinition of item ID %d", id);
313
            itemInfo = mItemInfos[id];
314
        }
315
36
        if (itemInfo == nullptr)
316

36
            itemInfo = new ItemInfo;
317
318

180
        const std::string typeStr = XML::getProperty(node, "type", "");
319
36
        int weight = XML::getProperty(node, "weight", 0);
320
36
        int view = XML::getProperty(node, "view", 0);
321
36
        const int cardColor = XML::getProperty(node, "cardColor", -1);
322
36
        const int inherit = XML::getProperty(node, "inherit", -1);
323
324

180
        std::string name = XML::langProperty(node, "name", "");
325

180
        std::string nameEn = XML::getProperty(node, "name", "");
326

180
        std::string image = XML::getProperty(node, "image", "");
327

180
        std::string floor = XML::getProperty(node, "floor", "");
328

180
        std::string description = XML::langProperty(node, "description", "");
329

180
        std::string attackAction = XML::getProperty(node, "attack-action", "");
330
        std::string skyAttackAction = XML::getProperty(
331

180
            node, "skyattack-action", "");
332
        std::string waterAttackAction = XML::getProperty(
333

180
            node, "waterattack-action", "");
334
        std::string rideAttackAction = XML::getProperty(
335

180
            node, "rideattack-action", "");
336

180
        std::string drawBefore = XML::getProperty(node, "drawBefore", "");
337

180
        std::string drawAfter = XML::getProperty(node, "drawAfter", "");
338
        const int maxFloorOffset = XML::getIntProperty(
339
36
            node, "maxFloorOffset", mapTileSize, 0, mapTileSize);
340
        const int maxFloorOffsetX = XML::getIntProperty(
341
36
            node, "maxFloorOffsetX", maxFloorOffset, 0, mapTileSize);
342
        const int maxFloorOffsetY = XML::getIntProperty(
343
36
            node, "maxFloorOffsetY", maxFloorOffset, 0, mapTileSize);
344

180
        std::string useButton = XML::langProperty(node, "useButton", "");
345

180
        std::string useButton2 = XML::langProperty(node, "useButton2", "");
346

180
        std::string colors = XML::getProperty(node, "colors", "");
347

180
        std::string iconColors = XML::getProperty(node, "iconColors", "");
348
36
        if (iconColors.empty())
349
            iconColors = colors;
350
351
        // check for empty hair palete
352
36
        if (id <= -1 && id > -100)
353
        {
354
6
            if (colors.empty())
355
                colors = "hair";
356
6
            if (iconColors.empty())
357
                iconColors = "hair";
358
        }
359
360

396
        std::string tags[3];
361
72
        tags[0] = XML::getProperty(node, "tag",
362

216
            XML::getProperty(node, "tag1", ""));
363

180
        tags[1] = XML::getProperty(node, "tag2", "");
364

180
        tags[2] = XML::getProperty(node, "tag3", "");
365
366
36
        const int drawPriority = XML::getProperty(node, "drawPriority", 0);
367
368
36
        int attackRange = XML::getProperty(node, "attack-range", 0);
369
        std::string missileParticle = XML::getProperty(
370

180
            node, "missile-particle", "");
371
        float missileZ = XML::getFloatProperty(
372
36
            node, "missile-z", 32.0F);
373
        int missileLifeTime = XML::getProperty(
374
36
            node, "missile-lifetime", 500);
375
        float missileSpeed = XML::getFloatProperty(
376
36
            node, "missile-speed", 7.0F);
377
        float missileDieDistance = XML::getFloatProperty(
378
36
            node, "missile-diedistance", 8.0F);
379

144
        int hitEffectId = XML::getProperty(node, "hit-effect-id",
380
36
            paths.getIntValue("hitEffectId"));
381

144
        int criticalEffectId = XML::getProperty(
382
            node, "critical-hit-effect-id",
383
36
            paths.getIntValue("criticalHitEffectId"));
384

144
        int missEffectId = XML::getProperty(node, "miss-effect-id",
385
36
            paths.getIntValue("missEffectId"));
386
387
72
        SpriteDisplay display;
388
36
        display.image = image;
389
36
        if (!floor.empty())
390
            display.floor = STD_MOVE(floor);
391
        else
392
            display.floor = image;
393
394
36
        const ItemInfo *inheritItemInfo = nullptr;
395
396
36
        if (inherit >= 0)
397
        {
398
            if (mItemInfos.find(inherit) != mItemInfos.end())
399
            {
400
                inheritItemInfo = mItemInfos[inherit];
401
            }
402
            else
403
            {
404
                reportAlways("Inherit item %d from not existing item %d",
405
                    id,
406
                    inherit)
407
            }
408
        }
409
410
72
        itemInfo->setId(id);
411

36
        if (name.empty() && (inheritItemInfo != nullptr))
412
            name = inheritItemInfo->getName();
413
        // TRANSLATORS: item info name
414

108
        itemInfo->setName(name.empty() ? _("unnamed") : name);
415
36
        if (nameEn.empty())
416
        {
417
            // TRANSLATORS: item info name
418
            itemInfo->setNameEn(name.empty() ? _("unnamed") : name);
419
        }
420
        else
421
        {
422
            itemInfo->setNameEn(nameEn);
423
        }
424
425

36
        if (description.empty() && (inheritItemInfo != nullptr))
426
            description = inheritItemInfo->getDescription();
427
36
        itemInfo->setDescription(description);
428
36
        if (typeStr.empty())
429
        {
430
            if (inheritItemInfo != nullptr)
431
                itemInfo->setType(inheritItemInfo->getType());
432
            else
433
                itemInfo->setType(itemTypeFromString("other"));
434
        }
435
        else
436
        {
437
36
            itemInfo->setType(itemTypeFromString(typeStr));
438
        }
439
72
        itemInfo->setType(itemTypeFromString(typeStr));
440

36
        if (useButton.empty() && (inheritItemInfo != nullptr))
441
            useButton = inheritItemInfo->getUseButton();
442
36
        if (useButton.empty())
443
60
            useButton = useButtonFromItemType(itemInfo->getType());
444
36
        itemInfo->setUseButton(useButton);
445

36
        if (useButton2.empty() && (inheritItemInfo != nullptr))
446
            useButton2 = inheritItemInfo->getUseButton();
447
36
        if (useButton2.empty())
448
72
            useButton2 = useButton2FromItemType(itemInfo->getType());
449
36
        itemInfo->setUseButton2(useButton2);
450

180
        itemInfo->addTag(mTags["All"]);
451
36
        itemInfo->setProtected(XML::getBoolProperty(
452
36
            node, "sellProtected", false));
453
36
        if (cardColor != -1)
454
6
            itemInfo->setCardColor(fromInt(cardColor, ItemColor));
455
30
        else if (inheritItemInfo != nullptr)
456
            itemInfo->setCardColor(inheritItemInfo->getCardColor());
457
458
36
        switch (itemInfo->getType())
459
        {
460
            case ItemDbType::USABLE:
461
                itemInfo->addTag(mTags["Usable"]);
462
                break;
463
            case ItemDbType::CARD:
464
            case ItemDbType::UNUSABLE:
465

30
                itemInfo->addTag(mTags["Unusable"]);
466
6
                break;
467
            default:
468
            case ItemDbType::EQUIPMENT_ONE_HAND_WEAPON:
469
            case ItemDbType::EQUIPMENT_TWO_HANDS_WEAPON:
470
            case ItemDbType::EQUIPMENT_TORSO:
471
            case ItemDbType::EQUIPMENT_ARMS:
472
            case ItemDbType::EQUIPMENT_HEAD:
473
            case ItemDbType::EQUIPMENT_LEGS:
474
            case ItemDbType::EQUIPMENT_SHIELD:
475
            case ItemDbType::EQUIPMENT_RING:
476
            case ItemDbType::EQUIPMENT_NECKLACE:
477
            case ItemDbType::EQUIPMENT_FEET:
478
            case ItemDbType::EQUIPMENT_AMMO:
479
            case ItemDbType::EQUIPMENT_CHARM:
480
            case ItemDbType::SPRITE_RACE:
481
            case ItemDbType::SPRITE_HAIR:
482

150
                itemInfo->addTag(mTags["Equipment"]);
483
30
                break;
484
        }
485
252
        for (int f = 0; f < 3; f++)
486
        {
487
216
            if (!tags[f].empty())
488
            {
489
                if (mTags.find(tags[f]) == mTags.end())
490
                {
491
                    mTagNames.push_back(tags[f]);
492
                    mTags[tags[f]] = tagNum ++;
493
                }
494
                itemInfo->addTag(mTags[tags[f]]);
495
            }
496
        }
497
498
72
        std::string effect;
499
36
        readItemStatsString(effect, node, requiredFields);
500
36
        readItemStatsString(effect, node, addFields);
501

180
        std::string temp = XML::langProperty(node, "effect", "");
502

36
        if (!effect.empty() && !temp.empty())
503
            effect.append(" / ");
504
36
        effect.append(temp);
505
506
36
        if (inheritItemInfo != nullptr)
507
        {
508
            if (view == 0)
509
                view = inheritItemInfo->getView();
510
            if (weight == 0)
511
                weight = inheritItemInfo->getWeight();
512
            if (attackAction.empty())
513
                attackAction = inheritItemInfo->getAttackAction();
514
            if (skyAttackAction.empty())
515
                skyAttackAction = inheritItemInfo->getSkyAttackAction();
516
            if (waterAttackAction.empty())
517
                waterAttackAction = inheritItemInfo->getWaterAttackAction();
518
            if (rideAttackAction.empty())
519
                rideAttackAction = inheritItemInfo->getRideAttackAction();
520
            if (attackRange == 0)
521
                attackRange = inheritItemInfo->getAttackRange();
522
            if (hitEffectId == 0)
523
                hitEffectId = inheritItemInfo->getHitEffectId();
524
            if (criticalEffectId == 0)
525
                criticalEffectId = inheritItemInfo->getCriticalHitEffectId();
526
            if (missEffectId == 0)
527
                missEffectId = inheritItemInfo->getMissEffectId();
528
            if (colors.empty())
529
                colors = inheritItemInfo->getColorsListName();
530
            if (iconColors.empty())
531
                iconColors = inheritItemInfo->getIconColorsListName();
532
            if (effect.empty())
533
                effect = inheritItemInfo->getEffect();
534
535
            const MissileInfo &inheritMissile =
536
                inheritItemInfo->getMissileConst();
537
            if (missileParticle.empty())
538
                missileParticle = inheritMissile.particle;
539
            if (missileZ == 32.0F)
540
                missileZ = inheritMissile.z;
541
            if (missileLifeTime == 500)
542
                missileLifeTime = inheritMissile.lifeTime;
543
            if (missileSpeed == 7.0F)
544
                missileSpeed = inheritMissile.speed;
545
            if (missileDieDistance == 8.0F)
546
                missileDieDistance = inheritMissile.dieDistance;
547
        }
548
549
72
        itemInfo->setView(view);
550
72
        itemInfo->setWeight(weight);
551
36
        itemInfo->setAttackAction(attackAction);
552
36
        itemInfo->setSkyAttackAction(skyAttackAction);
553
36
        itemInfo->setWaterAttackAction(waterAttackAction);
554
36
        itemInfo->setRideAttackAction(rideAttackAction);
555
72
        itemInfo->setAttackRange(attackRange);
556
72
        itemInfo->setHitEffectId(hitEffectId);
557
72
        itemInfo->setCriticalHitEffectId(criticalEffectId);
558
72
        itemInfo->setMissEffectId(missEffectId);
559

36
        itemInfo->setDrawBefore(-1, parseSpriteName(drawBefore));
560

36
        itemInfo->setDrawAfter(-1, parseSpriteName(drawAfter));
561
36
        itemInfo->setDrawPriority(-1, drawPriority);
562
36
        itemInfo->setColorsList(colors);
563
36
        itemInfo->setIconColorsList(iconColors);
564
72
        itemInfo->setMaxFloorOffsetX(maxFloorOffsetX);
565
72
        itemInfo->setMaxFloorOffsetY(maxFloorOffsetY);
566

180
        itemInfo->setPickupCursor(XML::getProperty(
567
36
            node, "pickupCursor", "pickup"));
568
569
36
        MissileInfo &missile = itemInfo->getMissile();
570
36
        missile.particle = STD_MOVE(missileParticle);
571
36
        missile.z = missileZ;
572
36
        missile.lifeTime = missileLifeTime;
573
36
        missile.speed = missileSpeed;
574
36
        missile.dieDistance = missileDieDistance;
575
576
264
        for_each_xml_child_node(itemChild, node)
577
        {
578

228
            if (xmlNameEqual(itemChild, "sprite"))
579
            {
580
51
                loadSpriteRef(itemInfo, itemChild);
581
            }
582

177
            else if (xmlNameEqual(itemChild, "particlefx"))
583
            {
584
                if (XmlHaveChildContent(itemChild))
585
                    display.particles.push_back(XmlChildContent(itemChild));
586
            }
587

177
            else if (xmlNameEqual(itemChild, "sound"))
588
            {
589
48
                loadSoundRef(itemInfo, itemChild);
590
            }
591

129
            else if (xmlNameEqual(itemChild, "floor"))
592
            {
593
                loadFloorSprite(display, itemChild);
594
            }
595

129
            else if (xmlNameEqual(itemChild, "replace"))
596
            {
597
                loadReplaceSprite(itemInfo, itemChild);
598
            }
599

129
            else if (xmlNameEqual(itemChild, "drawAfter"))
600
            {
601
                loadOrderSprite(itemInfo, itemChild, true);
602
            }
603

129
            else if (xmlNameEqual(itemChild, "drawBefore"))
604
            {
605
                loadOrderSprite(itemInfo, itemChild, false);
606
            }
607

129
            else if (xmlNameEqual(itemChild, "inventory"))
608
            {
609
                loadMenu(itemChild, itemInfo->getInventoryMenu());
610
            }
611

129
            else if (xmlNameEqual(itemChild, "storage"))
612
            {
613
                loadMenu(itemChild, itemInfo->getStorageMenu());
614
            }
615

129
            else if (xmlNameEqual(itemChild, "cart"))
616
            {
617
                loadMenu(itemChild, itemInfo->getCartMenu());
618
            }
619

129
            else if (xmlNameEqual(itemChild, "addStats"))
620
            {
621
                readItemStatsString(effect, itemChild, addFields);
622
            }
623

129
            else if (xmlNameEqual(itemChild, "requireStats"))
624
            {
625
                readItemStatsString(effect, itemChild, requiredFields);
626
            }
627
        }
628
36
        itemInfo->setEffect(effect);
629
630
/*
631
        logger->log("start dump item: %d", id);
632
        if (itemInfo->isRemoveSprites())
633
        {
634
            for (int f = 0; f < 10; f ++)
635
            {
636
                logger->log("dir: %d", f);
637
                SpriteToItemMap *const spriteToItems
638
                    = itemInfo->getSpriteToItemReplaceMap(f);
639
                if (!spriteToItems)
640
                {
641
                    logger->log("null");
642
                    continue;
643
                }
644
                for (SpriteToItemMapCIter itr = spriteToItems->begin(),
645
                     itr_end = spriteToItems->end(); itr != itr_end; ++ itr)
646
                {
647
                    const int remSprite = itr->first;
648
                    const IntMap &itemReplacer = itr->second;
649
                    logger->log("sprite: %d", remSprite);
650
651
                    for (IntMapCIter repIt = itemReplacer.begin(),
652
                         repIt_end = itemReplacer.end();
653
                         repIt != repIt_end; ++ repIt)
654
                    {
655
                        logger->log("from %d to %d", repIt->first,
656
                            repIt->second);
657
                    }
658
                }
659
            }
660
        }
661
662
        logger->log("--------------------------------");
663
        logger->log("end dump item");
664
*/
665
666
36
        itemInfo->setDisplay(display);
667
668
36
        mItemInfos[id] = itemInfo;
669
36
        if (!name.empty())
670
        {
671
72
            temp = normalize(name);
672
36
            mNamedItemInfos[temp] = itemInfo;
673
        }
674
36
        if (!nameEn.empty())
675
        {
676
72
            temp = normalize(nameEn);
677
36
            mNamedItemInfos[temp] = itemInfo;
678
        }
679
680
36
        if (!attackAction.empty())
681
        {
682
            if (attackRange == 0)
683
            {
684
                reportAlways("ItemDB: Missing attack range from weapon %i!",
685
                    id)
686
            }
687
        }
688
689
36
        STD_VECTOR<ItemMenuItem> &inventoryMenu = itemInfo->getInventoryMenu();
690
691
36
        if (inventoryMenu.empty())
692
        {
693
108
            std::string name1 = itemInfo->getUseButton();
694
108
            std::string name2 = itemInfo->getUseButton2();
695

36
            const bool isEquipment = getIsEquipment(itemInfo->getType());
696
697
36
            if (isEquipment)
698
            {
699
                if (name1.empty())
700
                {
701
                    // TRANSLATORS: popup menu item
702
                    name1 = _("Equip");
703
                }
704
                if (name2.empty())
705
                {
706
                    // TRANSLATORS: popup menu item
707
                    name2 = _("Unequip");
708
                }
709
            }
710
            else
711
            {
712
36
                if (name1.empty())
713
                {
714
                    // TRANSLATORS: popup menu item
715
30
                    name1 = _("Use");
716
                }
717
36
                if (name2.empty())
718
                {
719
                    // TRANSLATORS: popup menu item
720
36
                    name2 = _("Use");
721
                }
722
            }
723

288
            inventoryMenu.push_back(ItemMenuItem(
724
                name1,
725
                name2,
726
                "useinv 'INVINDEX'",
727
36
                "useinv 'INVINDEX'"));
728
        }
729
730
#define CHECK_PARAM(param) \
731
        if (param.empty()) \
732
        { \
733
            logger->log("ItemDB: Missing " #param " attribute for item %i!", \
734
                id); \
735
        }
736
737

42
        if (id >= 0 && typeStr != "other")
738
        {
739

6
            CHECK_PARAM(name)
740

6
            CHECK_PARAM(description)
741

6
            CHECK_PARAM(image)
742
        }
743
#undef CHECK_PARAM
744
    }
745
746
3
    mLoaded = true;
747
}
748
749
1
const StringVect &ItemDB::getTags()
750
{
751
1
    return mTagNames;
752
}
753
754
int ItemDB::getTagId(const std::string &tagName)
755
{
756
    return mTags[tagName];
757
}
758
759
204
void ItemDB::unload()
760
{
761
204
    logger->log1("Unloading item database...");
762
763
204
    delete2(mUnknown)
764
765
204
    delete_all(mItemInfos);
766
204
    mItemInfos.clear();
767
204
    mNamedItemInfos.clear();
768
204
    mTags.clear();
769
204
    mTagNames.clear();
770
204
    mLoaded = false;
771
204
}
772
773
bool ItemDB::exists(const int id)
774
{
775


33
    if (!mLoaded)
776
        return false;
777
778
66
    const ItemInfos::const_iterator i = mItemInfos.find(id);
779
66
    return i != mItemInfos.end();
780
}
781
782
bool ItemDB::exists(const std::string &name)
783
{
784
    if (!mLoaded)
785
        return false;
786
787
    const NamedItemInfos::const_iterator i = mNamedItemInfos.find(
788
        normalize(name));
789
    return i != mNamedItemInfos.end();
790
}
791
792
32
const ItemInfo &ItemDB::get(const int id)
793
{
794
32
    if (!mLoaded)
795
2
        load();
796
797
64
    const ItemInfos::const_iterator i = mItemInfos.find(id);
798
799
32
    if (i == mItemInfos.end())
800
    {
801
        reportAlways("ItemDB: Warning, unknown item ID# %d", id)
802
        return *mUnknown;
803
    }
804
805
32
    return *(i->second);
806
}
807
808
19
const ItemInfo &ItemDB::get(const std::string &name)
809
{
810
19
    if (!mLoaded)
811
1
        load();
812
813
38
    const NamedItemInfos::const_iterator i = mNamedItemInfos.find(
814
57
        normalize(name));
815
816
19
    if (i == mNamedItemInfos.end())
817
    {
818
        if (!name.empty())
819
        {
820
            reportAlways("ItemDB: Warning, unknown item name \"%s\"",
821
                name.c_str())
822
        }
823
        return *mUnknown;
824
    }
825
826
19
    return *(i->second);
827
}
828
829
1
const ItemDB::ItemInfos &ItemDB::getItemInfos()
830
{
831
1
    return mItemInfos;
832
}
833
834
const ItemInfo &ItemDB::getEmpty()
835
{
836
    return *mUnknown;
837
}
838
839
72
static int parseSpriteName(const std::string &name)
840
{
841
72
    int id = -1;
842

144
    if (name == "race" || name == "type")
843
    {
844
        id = 0;
845
    }
846


216
    else if (name == "shoes" || name == "boot" || name == "boots")
847
    {
848
        id = 1;
849
    }
850


216
    else if (name == "bottomclothes" || name == "bottom" || name == "pants")
851
    {
852
        id = 2;
853
    }
854

216
    else if (name == "topclothes" || name == "top"
855

216
             || name == "torso" || name == "body")
856
    {
857
        id = 3;
858
    }
859
72
    else if (name == "misc1")
860
    {
861
        id = 4;
862
    }
863


216
    else if (name == "misc2" || name == "scarf" || name == "scarfs")
864
    {
865
        id = 5;
866
    }
867
72
    else if (name == "hair")
868
    {
869
        id = 6;
870
    }
871

144
    else if (name == "hat" || name == "hats")
872
    {
873
        id = 7;
874
    }
875
72
    else if (name == "wings")
876
    {
877
        id = 8;
878
    }
879

144
    else if (name == "glove" || name == "gloves")
880
    {
881
        id = 9;
882
    }
883

144
    else if (name == "weapon" || name == "weapons")
884
    {
885
        id = 10;
886
    }
887

144
    else if (name == "shield" || name == "shields")
888
    {
889
        id = 11;
890
    }
891

144
    else if (name == "amulet" || name == "amulets")
892
    {
893
        id = 12;
894
    }
895

144
    else if (name == "ring" || name == "rings")
896
    {
897
        id = 13;
898
    }
899
900
72
    return id;
901
}
902
903
static int parseDirectionName(const std::string &name)
904
{
905
    int id = -1;
906
    if (name == "down")
907
    {
908
#ifdef TMWA_SUPPORT
909
        if (Net::getNetworkType() == ServerType::TMWATHENA)
910
            id = -2;
911
        else
912
#endif
913
            id = SpriteDirection::DOWN;
914
    }
915
    else if (name == "downleft" || name == "leftdown")
916
    {
917
        id = SpriteDirection::DOWNLEFT;
918
    }
919
    else if (name == "left")
920
    {
921
        id = SpriteDirection::LEFT;
922
    }
923
    else if (name == "upleft" || name == "leftup")
924
    {
925
        id = SpriteDirection::UPLEFT;
926
    }
927
    else if (name == "up")
928
    {
929
#ifdef TMWA_SUPPORT
930
        if (Net::getNetworkType() == ServerType::TMWATHENA)
931
            id = -3;
932
        else
933
#endif
934
            id = SpriteDirection::UP;
935
    }
936
    else if (name == "upright" || name == "rightup")
937
    {
938
        id = SpriteDirection::UPRIGHT;
939
    }
940
    else if (name == "right")
941
    {
942
        id = SpriteDirection::RIGHT;
943
    }
944
    else if (name == "downright" || name == "rightdown")
945
    {
946
        id = SpriteDirection::DOWNRIGHT;
947
    }
948
    else if (name == "downall")
949
    {
950
        id = -2;
951
    }
952
    else if (name == "upall")
953
    {
954
        id = -3;
955
    }
956
    // hack for died action.
957
    else if (name == "died")
958
    {
959
        id = 9;
960
    }
961
962
    return id;
963
}
964
965
51
static void loadSpriteRef(ItemInfo *const itemInfo, XmlNodeConstPtr node)
966
{
967
255
    const std::string gender = XML::getProperty(node, "gender", "unisex");
968

51
    if ((node == nullptr) || !XmlHaveChildContent(node))
969
        return;
970
971
204
    const std::string filename = XmlChildContent(node);
972
973
51
    const int race = XML::getProperty(node, "race", 0);
974

102
    if (gender == "male" || gender == "unisex")
975
30
        itemInfo->setSprite(filename, Gender::MALE, race);
976

81
    if (gender == "female" || gender == "unisex")
977
51
        itemInfo->setSprite(filename, Gender::FEMALE, race);
978
}
979
980
48
static void loadSoundRef(ItemInfo *const itemInfo, XmlNodeConstPtr node)
981
{
982

96
    if (node == nullptr ||
983
96
        !XmlHaveChildContent(node))
984
    {
985
        return;
986
    }
987
240
    const std::string event = XML::getProperty(node, "event", "");
988
    const std::map<std::string, ItemSoundEvent::Type>::const_iterator
989
96
        it = mSoundNames.find(event);
990
48
    if (it != mSoundNames.end())
991
    {
992
48
        const int delay = XML::getProperty(node, "delay", 0);
993
192
        const std::string filename = XmlChildContent(node);
994
48
        itemInfo->addSound((*it).second, filename, delay);
995
    }
996
    else
997
    {
998
        reportAlways("ItemDB: Ignoring unknown sound event '%s'",
999
            event.c_str())
1000
    }
1001
}
1002
1003
static void loadFloorSprite(SpriteDisplay &display,
1004
                            XmlNodeConstPtrConst floorNode)
1005
{
1006
    if (floorNode == nullptr)
1007
        return;
1008
    for_each_xml_child_node(spriteNode, floorNode)
1009
    {
1010
        if (!XmlHaveChildContent(spriteNode))
1011
            continue;
1012
        if (xmlNameEqual(spriteNode, "sprite"))
1013
        {
1014
            SpriteReference *const currentSprite = new SpriteReference;
1015
            currentSprite->sprite = XmlChildContent(spriteNode);
1016
            currentSprite->variant
1017
                = XML::getProperty(spriteNode, "variant", 0);
1018
            display.sprites.push_back(currentSprite);
1019
        }
1020
        else if (xmlNameEqual(spriteNode, "particlefx"))
1021
        {
1022
            display.particles.push_back(XmlChildContent(spriteNode));
1023
        }
1024
    }
1025
}
1026
1027
static void loadReplaceSprite(ItemInfo *const itemInfo,
1028
                              XmlNodeConstPtr replaceNode)
1029
{
1030
    if (replaceNode == nullptr)
1031
        return;
1032
    const std::string removeSprite = XML::getProperty(
1033
        replaceNode, "sprite", "");
1034
    const int direction = parseDirectionName(XML::getProperty(
1035
        replaceNode, "direction", "all"));
1036
1037
    itemInfo->setRemoveSprites();
1038
1039
    switch (direction)
1040
    {
1041
        case -1:
1042
        {
1043
            if (removeSprite.empty())
1044
            {  // remove all sprites
1045
                for (int f = 0; f < 10; f ++)
1046
                {
1047
                    for (int sprite = 0; sprite < 13; sprite ++)
1048
                        itemInfo->addReplaceSprite(sprite, f);
1049
                }
1050
            }
1051
            else
1052
            {  // replace only given sprites
1053
                for (int f = 0; f < 10; f ++)
1054
                {
1055
                    IntMap *const mapList = itemInfo->addReplaceSprite(
1056
                        parseSpriteName(removeSprite), f);
1057
                    if (mapList == nullptr)
1058
                        continue;
1059
                    for_each_xml_child_node(itemNode, replaceNode)
1060
                    {
1061
                        if (xmlNameEqual(itemNode, "item"))
1062
                        {
1063
                            const int from = XML::getProperty(
1064
                                itemNode, "from", 0);
1065
                            const int to = XML::getProperty(
1066
                                itemNode, "to", 1);
1067
                            (*mapList)[from] = to;
1068
                        }
1069
                    }
1070
                }
1071
            }
1072
            break;
1073
        }
1074
        case -2:
1075
        {
1076
            itemInfo->addReplaceSprite(parseSpriteName(
1077
                removeSprite), SpriteDirection::DOWN);
1078
            itemInfo->addReplaceSprite(parseSpriteName(
1079
                removeSprite), SpriteDirection::DOWNLEFT);
1080
            itemInfo->addReplaceSprite(parseSpriteName(
1081
                removeSprite), SpriteDirection::DOWNRIGHT);
1082
1083
            for_each_xml_child_node(itemNode, replaceNode)
1084
            {
1085
                if (xmlNameEqual(itemNode, "item"))
1086
                {
1087
                    const int from = XML::getProperty(itemNode, "from", 0);
1088
                    const int to = XML::getProperty(itemNode, "to", 1);
1089
                    IntMap *mapList = itemInfo->addReplaceSprite(
1090
                        parseSpriteName(removeSprite), SpriteDirection::DOWN);
1091
                    if (mapList != nullptr)
1092
                        (*mapList)[from] = to;
1093
1094
                    mapList = itemInfo->addReplaceSprite(parseSpriteName(
1095
                        removeSprite), SpriteDirection::DOWNLEFT);
1096
                    if (mapList != nullptr)
1097
                        (*mapList)[from] = to;
1098
1099
                    mapList = itemInfo->addReplaceSprite(parseSpriteName(
1100
                        removeSprite), SpriteDirection::DOWNRIGHT);
1101
                    if (mapList != nullptr)
1102
                        (*mapList)[from] = to;
1103
                }
1104
            }
1105
            break;
1106
        }
1107
        case -3:
1108
        {
1109
            itemInfo->addReplaceSprite(parseSpriteName(
1110
                removeSprite), SpriteDirection::UP);
1111
            itemInfo->addReplaceSprite(parseSpriteName(
1112
                removeSprite), SpriteDirection::UPLEFT);
1113
            itemInfo->addReplaceSprite(parseSpriteName(
1114
                removeSprite), SpriteDirection::UPRIGHT);
1115
1116
            for_each_xml_child_node(itemNode, replaceNode)
1117
            {
1118
                if (xmlNameEqual(itemNode, "item"))
1119
                {
1120
                    const int from = XML::getProperty(itemNode, "from", 0);
1121
                    const int to = XML::getProperty(itemNode, "to", 1);
1122
                    IntMap *mapList = itemInfo->addReplaceSprite(
1123
                        parseSpriteName(removeSprite), SpriteDirection::UP);
1124
                    if (mapList != nullptr)
1125
                        (*mapList)[from] = to;
1126
1127
                    mapList = itemInfo->addReplaceSprite(parseSpriteName(
1128
                        removeSprite), SpriteDirection::UPLEFT);
1129
                    if (mapList != nullptr)
1130
                        (*mapList)[from] = to;
1131
1132
                    mapList = itemInfo->addReplaceSprite(parseSpriteName(
1133
                        removeSprite), SpriteDirection::UPRIGHT);
1134
                    if (mapList != nullptr)
1135
                        (*mapList)[from] = to;
1136
                }
1137
            }
1138
            break;
1139
        }
1140
        default:
1141
        {
1142
            IntMap *const mapList = itemInfo->addReplaceSprite(
1143
                parseSpriteName(removeSprite), direction);
1144
            if (mapList == nullptr)
1145
                return;
1146
            for_each_xml_child_node(itemNode, replaceNode)
1147
            {
1148
                if (xmlNameEqual(itemNode, "item"))
1149
                {
1150
                    const int from = XML::getProperty(itemNode, "from", 0);
1151
                    const int to = XML::getProperty(itemNode, "to", 1);
1152
                    (*mapList)[from] = to;
1153
                }
1154
            }
1155
            break;
1156
        }
1157
    }
1158
}
1159
1160
static void loadOrderSprite(ItemInfo *const itemInfo,
1161
                            XmlNodeConstPtr node,
1162
                            const bool drawAfter)
1163
{
1164
    const int sprite = parseSpriteName(XML::getProperty(node, "name", ""));
1165
    const int priority = XML::getProperty(node, "priority", 0);
1166
1167
    const int direction = parseDirectionName(XML::getProperty(
1168
        node, "direction", "all"));
1169
    if (drawAfter)
1170
        itemInfo->setDrawAfter(direction, sprite);
1171
    else
1172
        itemInfo->setDrawBefore(direction, sprite);
1173
    itemInfo->setDrawPriority(direction, priority);
1174
}
1175
1176
std::string ItemDB::getNamesStr(const STD_VECTOR<int> &parts)
1177
{
1178
    std::string str;
1179
    FOR_EACH (STD_VECTOR<int>::const_iterator, it, parts)
1180
    {
1181
        const int id = *it;
1182
        if (exists(id))
1183
        {
1184
            if (!str.empty())
1185
                str.append(",");
1186
            str.append(get(id).getName());
1187
        }
1188
    }
1189
    return str;
1190
}
1191
1192
int ItemDB::getNumOfHairstyles()
1193
{
1194
    return mNumberOfHairstyles;
1195
}
1196
1197
#ifdef UNITTESTS
1198
5
ItemDB::NamedItemInfos &ItemDB::getNamedItemInfosTest()
1199
{
1200
5
    return mNamedItemInfos;
1201
}
1202
1203
5
ItemDB::ItemInfos &ItemDB::getItemInfosTest()
1204
{
1205
5
    return mItemInfos;
1206

3
}
1207
#endif  // UNITTESTS