GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/db/itemdb.cpp Lines: 320 556 57.6 %
Date: 2018-06-18 21:15:20 Branches: 363 1045 34.7 %

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-2018  The ManaPlus Developers
6
 *
7
 *  This file is part of The ManaPlus Client.
8
 *
9
 *  This program is free software; you can redistribute it and/or modify
10
 *  it under the terms of the GNU General Public License as published by
11
 *  the Free Software Foundation; either version 2 of the License, or
12
 *  any later version.
13
 *
14
 *  This program is distributed in the hope that it will be useful,
15
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 *  GNU General Public License for more details.
18
 *
19
 *  You should have received a copy of the GNU General Public License
20
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22
23
#include "resources/db/itemdb.h"
24
25
#include "const/resources/map/map.h"
26
27
#include "configuration.h"
28
29
#include "enums/resources/spritedirection.h"
30
31
#include "fs/virtfs/tools.h"
32
33
#include "resources/iteminfo.h"
34
#include "resources/itemmenuitem.h"
35
#include "resources/itemtypemapdata.h"
36
37
#include "resources/db/itemfielddb.h"
38
39
#include "resources/sprite/spritereference.h"
40
41
#ifdef TMWA_SUPPORT
42
#include "net/net.h"
43
#endif  // TMWA_SUPPORT
44
45
#include "utils/checkutils.h"
46
#include "utils/delete2.h"
47
#include "utils/dtor.h"
48
#include "utils/foreach.h"
49
#include "utils/itemxmlutils.h"
50
#include "utils/stdmove.h"
51
#include "utils/stringmap.h"
52
53
#include "debug.h"
54
55
namespace
56
{
57
2
    ItemDB::ItemInfos mItemInfos;
58
2
    ItemDB::NamedItemInfos mNamedItemInfos;
59
    ItemInfo *mUnknown = nullptr;
60
    bool mLoaded = false;
61
    bool mConstructed = false;
62
2
    StringVect mTagNames;
63
2
    StringIntMap mTags;
64
2
    std::map<std::string, ItemSoundEvent::Type> mSoundNames;
65
    int mNumberOfHairstyles = 1;
66
}  // namespace
67
68
// Forward declarations
69
static void loadSpriteRef(ItemInfo *const itemInfo,
70
                          XmlNodeConstPtr node) A_NONNULL(1);
71
static void loadSoundRef(ItemInfo *const itemInfo,
72
                         XmlNodeConstPtr node) A_NONNULL(1);
73
static void loadFloorSprite(SpriteDisplay &display,
74
                            XmlNodeConstPtrConst node);
75
static void loadReplaceSprite(ItemInfo *const itemInfo,
76
                              XmlNodeConstPtr replaceNode) A_NONNULL(1);
77
static void loadOrderSprite(ItemInfo *const itemInfo,
78
                            XmlNodeConstPtr node,
79
                            const bool drawAfter) A_NONNULL(1);
80
static int parseSpriteName(const std::string &name);
81
static int parseDirectionName(const std::string &name);
82
83
144
static ItemDbTypeT itemTypeFromString(const std::string &name)
84
{
85
144
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
86
2592
    for (size_t f = 0; f < sz; f ++)
87
    {
88
2592
        const ItemTypeMap &type = itemTypeMap[f];
89
2736
        if (type.name == name)
90
144
            return type.type;
91
    }
92
    logger->log("Unknown item type: " + name);
93
    return ItemDbType::UNUSABLE;
94
}
95
96
60
static std::string useButtonFromItemType(const ItemDbTypeT &type)
97
{
98
60
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
99
1092
    for (size_t f = 0; f < sz; f ++)
100
    {
101
1092
        const ItemTypeMap &item = itemTypeMap[f];
102
1092
        if (item.type == type)
103
        {
104
120
            if (item.useButton.empty())
105
                return std::string();
106
            return gettext(item.useButton.c_str());
107
        }
108
    }
109
    logger->log("Unknown item type");
110
    return std::string();
111
}
112
113
72
static std::string useButton2FromItemType(const ItemDbTypeT &type)
114
{
115
72
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
116
1296
    for (size_t f = 0; f < sz; f ++)
117
    {
118
1296
        const ItemTypeMap &item = itemTypeMap[f];
119
1296
        if (item.type == type)
120
        {
121
144
            if (item.useButton2.empty())
122
                return std::string();
123
            return gettext(item.useButton2.c_str());
124
        }
125
    }
126
    logger->log("Unknown item type");
127
    return std::string();
128
}
129
130
2
static void initStatic()
131
{
132
2
    mConstructed = true;
133

8
    mSoundNames["hit"] = ItemSoundEvent::HIT;
134

8
    mSoundNames["strike"] = ItemSoundEvent::MISS;
135

8
    mSoundNames["miss"] = ItemSoundEvent::MISS;
136

8
    mSoundNames["use"] = ItemSoundEvent::USE;
137

8
    mSoundNames["equip"] = ItemSoundEvent::EQUIP;
138

8
    mSoundNames["unequip"] = ItemSoundEvent::UNEQUIP;
139

6
    mSoundNames["drop"] = ItemSoundEvent::DROP;
140

8
    mSoundNames["pickup"] = ItemSoundEvent::PICKUP;
141

8
    mSoundNames["take"] = ItemSoundEvent::TAKE;
142

8
    mSoundNames["put"] = ItemSoundEvent::PUT;
143

8
    mSoundNames["usecard"] = ItemSoundEvent::USECARD;
144
2
}
145
146
6
void ItemDB::load()
147
{
148
6
    if (mLoaded)
149
        unload();
150
151
6
    logger->log1("Initializing item database...");
152
153
6
    if (!mConstructed)
154
2
        initStatic();
155
156
6
    int tagNum = 0;
157
158
6
    mTags.clear();
159
6
    mTagNames.clear();
160
30
    mTagNames.push_back("All");
161
30
    mTagNames.push_back("Usable");
162
30
    mTagNames.push_back("Unusable");
163
30
    mTagNames.push_back("Equipment");
164

24
    mTags["All"] = tagNum ++;
165

24
    mTags["Usable"] = tagNum ++;
166

24
    mTags["Unusable"] = tagNum ++;
167

24
    mTags["Equipment"] = tagNum ++;
168
169
6
    mUnknown = new ItemInfo;
170
    // TRANSLATORS: item name
171
30
    mUnknown->setName(_("Unknown item"));
172
18
    mUnknown->setDisplay(SpriteDisplay());
173

30
    std::string errFile = paths.getStringValue("spriteErrorFile");
174
6
    mUnknown->setSprite(errFile, Gender::MALE, 0);
175
6
    mUnknown->setSprite(errFile, Gender::FEMALE, 0);
176
6
    mUnknown->setSprite(errFile, Gender::OTHER, 0);
177

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

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

30
    loadXmlFile(paths.getStringValue("itemsPatchFile"),
182
        tagNum,
183
        SkipError_true);
184
185
12
    StringVect list;
186


48
    VirtFs::getFilesInDir(paths.getStringValue("itemsPatchDir"),
187
        list,
188
        ".xml");
189
30
    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

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



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

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

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



228
           paths.getStringValue("spriteErrorFile"))
207
    {
208
42
        races ++;
209
    }
210
6
}
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
12
void ItemDB::loadXmlFile(const std::string &fileName,
263
                         int &tagNum,
264
                         const SkipError skipError)
265
{
266
12
    if (fileName.empty())
267
    {
268
        mLoaded = true;
269
6
        return;
270
    }
271
272
    XML::Document doc(fileName,
273
        UseVirtFs_true,
274
18
        skipError);
275
12
    XmlNodeConstPtrConst rootNode = doc.rootNode();
276
277

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

198
        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

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

72
            itemInfo = new ItemInfo;
317
318

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

288
        int missEffectId = XML::getProperty(node, "miss-effect-id",
385
72
            paths.getIntValue("missEffectId"));
386
387
144
        SpriteDisplay display;
388
72
        display.image = image;
389
72
        if (!floor.empty())
390
            display.floor = STD_MOVE(floor);
391
        else
392
            display.floor = image;
393
394
72
        const ItemInfo *inheritItemInfo = nullptr;
395
396
72
        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
144
        itemInfo->setId(id);
411

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

216
        itemInfo->setName(name.empty() ? _("unnamed") : name);
415
72
        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

72
        if (description.empty() && (inheritItemInfo != nullptr))
426
            description = inheritItemInfo->getDescription();
427
72
        itemInfo->setDescription(description);
428
72
        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
72
            itemInfo->setType(itemTypeFromString(typeStr));
438
        }
439
144
        itemInfo->setType(itemTypeFromString(typeStr));
440

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

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

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

60
                itemInfo->addTag(mTags["Unusable"]);
466
12
                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

300
                itemInfo->addTag(mTags["Equipment"]);
483
60
                break;
484
        }
485
504
        for (int f = 0; f < 3; f++)
486
        {
487
432
            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
144
        std::string effect;
499
72
        readItemStatsString(effect, node, requiredFields);
500
72
        readItemStatsString(effect, node, addFields);
501

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

72
        if (!effect.empty() && !temp.empty())
503
            effect.append(" / ");
504
72
        effect.append(temp);
505
506
72
        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
144
        itemInfo->setView(view);
550
144
        itemInfo->setWeight(weight);
551
72
        itemInfo->setAttackAction(attackAction);
552
72
        itemInfo->setSkyAttackAction(skyAttackAction);
553
72
        itemInfo->setWaterAttackAction(waterAttackAction);
554
72
        itemInfo->setRideAttackAction(rideAttackAction);
555
144
        itemInfo->setAttackRange(attackRange);
556
144
        itemInfo->setHitEffectId(hitEffectId);
557
144
        itemInfo->setCriticalHitEffectId(criticalEffectId);
558
144
        itemInfo->setMissEffectId(missEffectId);
559
72
        itemInfo->setDrawBefore(-1, parseSpriteName(drawBefore));
560
72
        itemInfo->setDrawAfter(-1, parseSpriteName(drawAfter));
561
72
        itemInfo->setDrawPriority(-1, drawPriority);
562
72
        itemInfo->setColorsList(colors);
563
72
        itemInfo->setIconColorsList(iconColors);
564
144
        itemInfo->setMaxFloorOffsetX(maxFloorOffsetX);
565
144
        itemInfo->setMaxFloorOffsetY(maxFloorOffsetY);
566

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

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

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

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

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

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

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

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

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

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

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

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

258
            else if (xmlNameEqual(itemChild, "requireStats"))
624
            {
625
                readItemStatsString(effect, itemChild, requiredFields);
626
            }
627
        }
628
72
        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
72
        itemInfo->setDisplay(display);
667
668
72
        mItemInfos[id] = itemInfo;
669
72
        if (!name.empty())
670
        {
671
144
            temp = normalize(name);
672
72
            mNamedItemInfos[temp] = itemInfo;
673
        }
674
72
        if (!nameEn.empty())
675
        {
676
144
            temp = normalize(nameEn);
677
72
            mNamedItemInfos[temp] = itemInfo;
678
        }
679
680
72
        if (!attackAction.empty())
681
        {
682
            if (attackRange == 0)
683
            {
684
                reportAlways("ItemDB: Missing attack range from weapon %i!",
685
                    id);
686
            }
687
        }
688
689
72
        STD_VECTOR<ItemMenuItem> &inventoryMenu = itemInfo->getInventoryMenu();
690
691
72
        if (inventoryMenu.empty())
692
        {
693
216
            std::string name1 = itemInfo->getUseButton();
694
216
            std::string name2 = itemInfo->getUseButton2();
695

72
            const bool isEquipment = getIsEquipment(itemInfo->getType());
696
697
            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
72
                if (name1.empty())
713
                {
714
                    // TRANSLATORS: popup menu item
715
60
                    name1 = _("Use");
716
                }
717
72
                if (name2.empty())
718
                {
719
                    // TRANSLATORS: popup menu item
720
72
                    name2 = _("Use");
721
                }
722
            }
723

576
            inventoryMenu.push_back(ItemMenuItem(
724
                name1,
725
                name2,
726
                "useinv 'INVINDEX'",
727
                "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

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

12
            CHECK_PARAM(name)
740

12
            CHECK_PARAM(description)
741

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


66
    if (!mLoaded)
776
        return false;
777
778
132
    const ItemInfos::const_iterator i = mItemInfos.find(id);
779
132
    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
64
const ItemInfo &ItemDB::get(const int id)
793
{
794
64
    if (!mLoaded)
795
4
        load();
796
797
128
    const ItemInfos::const_iterator i = mItemInfos.find(id);
798
799
64
    if (i == mItemInfos.end())
800
    {
801
        reportAlways("ItemDB: Warning, unknown item ID# %d", id);
802
        return *mUnknown;
803
    }
804
805
64
    return *(i->second);
806
}
807
808
38
const ItemInfo &ItemDB::get(const std::string &name)
809
{
810
38
    if (!mLoaded)
811
2
        load();
812
813
76
    const NamedItemInfos::const_iterator i = mNamedItemInfos.find(
814
114
        normalize(name));
815
816
38
    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
38
    return *(i->second);
827
}
828
829
2
const ItemDB::ItemInfos &ItemDB::getItemInfos()
830
{
831
2
    return mItemInfos;
832
}
833
834
const ItemInfo &ItemDB::getEmpty()
835
{
836
    return *mUnknown;
837
}
838
839
144
static int parseSpriteName(const std::string &name)
840
{
841
144
    int id = -1;
842

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

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

432
    else if (name == "bottomclothes" || name == "bottom" || name == "pants")
851
    {
852
        id = 2;
853
    }
854
288
    else if (name == "topclothes" || name == "top"
855

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

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

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

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

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

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

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

288
    else if (name == "ring" || name == "rings")
896
    {
897
        id = 13;
898
    }
899
900
144
    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
102
static void loadSpriteRef(ItemInfo *const itemInfo, XmlNodeConstPtr node)
966
{
967

510
    const std::string gender = XML::getProperty(node, "gender", "unisex");
968

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

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

162
    if (gender == "female" || gender == "unisex")
977
102
        itemInfo->setSprite(filename, Gender::FEMALE, race);
978

204
    if (gender == "other" || gender == "unisex")
979
60
        itemInfo->setSprite(filename, Gender::OTHER, race);
980
}
981
982
96
static void loadSoundRef(ItemInfo *const itemInfo, XmlNodeConstPtr node)
983
{
984

192
    if (node == nullptr ||
985
192
        !XmlHaveChildContent(node))
986
    {
987
        return;
988
    }
989

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

6
}
1210
#endif  // UNITTESTS