GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/db/itemdb.cpp Lines: 321 566 56.7 %
Date: 2017-11-29 Branches: 362 1059 34.2 %

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-2017  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/item/itemfieldtype.h"
40
41
#include "resources/sprite/spritereference.h"
42
43
#ifdef TMWA_SUPPORT
44
#include "net/net.h"
45
#endif  // TMWA_SUPPORT
46
47
#include "utils/checkutils.h"
48
#include "utils/delete2.h"
49
#include "utils/dtor.h"
50
#include "utils/foreach.h"
51
#include "utils/stdmove.h"
52
#include "utils/stringmap.h"
53
54
#include "utils/translation/podict.h"
55
56
#include "debug.h"
57
58
namespace
59
{
60
2
    ItemDB::ItemInfos mItemInfos;
61
2
    ItemDB::NamedItemInfos mNamedItemInfos;
62
    ItemInfo *mUnknown = nullptr;
63
    bool mLoaded = false;
64
    bool mConstructed = false;
65
2
    StringVect mTagNames;
66
2
    StringIntMap mTags;
67
2
    std::map<std::string, ItemSoundEvent::Type> mSoundNames;
68
    int mNumberOfHairstyles = 1;
69
}  // namespace
70
71
// Forward declarations
72
static void loadSpriteRef(ItemInfo *const itemInfo,
73
                          XmlNodeConstPtr node) A_NONNULL(1);
74
static void loadSoundRef(ItemInfo *const itemInfo,
75
                         XmlNodeConstPtr node) A_NONNULL(1);
76
static void loadFloorSprite(SpriteDisplay &display,
77
                            XmlNodeConstPtrConst node);
78
static void loadReplaceSprite(ItemInfo *const itemInfo,
79
                              XmlNodeConstPtr replaceNode) A_NONNULL(1);
80
static void loadOrderSprite(ItemInfo *const itemInfo,
81
                            XmlNodeConstPtr node,
82
                            const bool drawAfter) A_NONNULL(1);
83
static int parseSpriteName(const std::string &name);
84
static int parseDirectionName(const std::string &name);
85
86
456
static ItemDbTypeT itemTypeFromString(const std::string &name)
87
{
88
456
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
89
8520
    for (size_t f = 0; f < sz; f ++)
90
    {
91
8520
        const ItemTypeMap &type = itemTypeMap[f];
92
8976
        if (type.name == name)
93
456
            return type.type;
94
    }
95
    logger->log("Unknown item type: " + name);
96
    return ItemDbType::UNUSABLE;
97
}
98
99
216
static std::string useButtonFromItemType(const ItemDbTypeT &type)
100
{
101
216
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
102
4056
    for (size_t f = 0; f < sz; f ++)
103
    {
104
4056
        const ItemTypeMap &item = itemTypeMap[f];
105
4056
        if (item.type == type)
106
        {
107
432
            if (item.useButton.empty())
108
                return std::string();
109
            return gettext(item.useButton.c_str());
110
        }
111
    }
112
    logger->log("Unknown item type");
113
    return std::string();
114
}
115
116
228
static std::string useButton2FromItemType(const ItemDbTypeT &type)
117
{
118
228
    const size_t sz = sizeof(itemTypeMap) / sizeof(itemTypeMap[0]);
119
4260
    for (size_t f = 0; f < sz; f ++)
120
    {
121
4260
        const ItemTypeMap &item = itemTypeMap[f];
122
4260
        if (item.type == type)
123
        {
124
456
            if (item.useButton2.empty())
125
                return std::string();
126
            return gettext(item.useButton2.c_str());
127
        }
128
    }
129
    logger->log("Unknown item type");
130
    return std::string();
131
}
132
133
456
static void readFields(std::string &effect,
134
                       XmlNodeConstPtr node,
135
                       const ItemFieldDb::FieldInfos &fields)
136
{
137
456
    if (translator == nullptr)
138
        return;
139
140
608
    FOR_EACH (ItemFieldDb::FieldInfos::const_iterator, it, fields)
141
    {
142
        const std::string fieldName = (*it).first;
143
        const ItemFieldType *const field = (*it).second;
144
145
        std::string value = XML::getProperty(node,
146
            fieldName.c_str(),
147
            "");
148
        if (value.empty())
149
            continue;
150
        if (!effect.empty())
151
            effect.append(" / ");
152
        if (field->sign && isDigit(value))
153
            value = std::string("+").append(value);
154
        const std::string format = translator->getStr(field->description);
155
        effect.append(strprintf(format.c_str(),
156
            value.c_str()));
157
    }
158
}
159
160
2
static void initStatic()
161
{
162
2
    mConstructed = true;
163

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

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

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

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

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

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

8
    mSoundNames["drop"] = ItemSoundEvent::DROP;
170

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

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

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

8
    mSoundNames["usecard"] = ItemSoundEvent::USECARD;
174
2
}
175
176
6
void ItemDB::load()
177
{
178
6
    if (mLoaded)
179
        unload();
180
181
6
    logger->log1("Initializing item database...");
182
183
6
    if (!mConstructed)
184
2
        initStatic();
185
186
6
    int tagNum = 0;
187
188
6
    mTags.clear();
189
6
    mTagNames.clear();
190
30
    mTagNames.push_back("All");
191
30
    mTagNames.push_back("Usable");
192
30
    mTagNames.push_back("Unusable");
193
30
    mTagNames.push_back("Equipment");
194

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

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

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

24
    mTags["Equipment"] = tagNum ++;
198
199
6
    mUnknown = new ItemInfo;
200
    // TRANSLATORS: item name
201
30
    mUnknown->setName(_("Unknown item"));
202
18
    mUnknown->setDisplay(SpriteDisplay());
203

30
    std::string errFile = paths.getStringValue("spriteErrorFile");
204
6
    mUnknown->setSprite(errFile, Gender::MALE, 0);
205
6
    mUnknown->setSprite(errFile, Gender::FEMALE, 0);
206
6
    mUnknown->setSprite(errFile, Gender::OTHER, 0);
207

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

30
    loadXmlFile(paths.getStringValue("itemsFile"),
209
        tagNum,
210
        SkipError_false);
211

30
    loadXmlFile(paths.getStringValue("itemsPatchFile"),
212
        tagNum,
213
        SkipError_true);
214
215
12
    StringVect list;
216


48
    VirtFs::getFilesInDir(paths.getStringValue("itemsPatchDir"),
217
        list,
218
        ".xml");
219
30
    FOR_EACH (StringVectCIter, it, list)
220
        loadXmlFile(*it, tagNum, SkipError_true);
221
222
    // Hairstyles are encoded as negative numbers. Count how far negative
223
    // we can go.
224
    int hairstyles = 1;
225

858
    while (ItemDB::exists(-hairstyles) &&
226
168
           ItemDB::get(-hairstyles).getSprite(Gender::MALE,
227



1026
           BeingTypeId_zero) != paths.getStringValue("spriteErrorFile"))
228
    {
229
168
        hairstyles ++;
230
    }
231
6
    mNumberOfHairstyles = hairstyles;
232
233
6
    int races = 100;
234

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

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



228
           paths.getStringValue("spriteErrorFile"))
237
    {
238
42
        races ++;
239
    }
240
6
}
241
242
static void loadMenu(XmlNodePtrConst parentNode,
243
                     STD_VECTOR<ItemMenuItem> &menu)
244
{
245
    for_each_xml_child_node(node, parentNode)
246
    {
247
        if (xmlNameEqual(node, "menu"))
248
        {
249
            const std::string name1 = XML::langProperty(node,
250
                "name1", "");
251
            const std::string name2 = XML::langProperty(node,
252
                "name2", "");
253
            const std::string command1 = XML::getProperty(node,
254
                "command1", "");
255
            const std::string command2 = XML::getProperty(node,
256
                "command2", command1);
257
            menu.push_back(ItemMenuItem(name1,
258
                name2,
259
                command1,
260
                command2));
261
        }
262
    }
263
}
264
265
static bool getIsEquipment(const ItemDbTypeT type)
266
{
267
    switch (type)
268
    {
269
        case ItemDbType::EQUIPMENT_ONE_HAND_WEAPON:
270
        case ItemDbType::EQUIPMENT_TWO_HANDS_WEAPON:
271
        case ItemDbType::EQUIPMENT_TORSO:
272
        case ItemDbType::EQUIPMENT_ARMS:
273
        case ItemDbType::EQUIPMENT_HEAD:
274
        case ItemDbType::EQUIPMENT_LEGS:
275
        case ItemDbType::EQUIPMENT_SHIELD:
276
        case ItemDbType::EQUIPMENT_RING:
277
        case ItemDbType::EQUIPMENT_NECKLACE:
278
        case ItemDbType::EQUIPMENT_FEET:
279
        case ItemDbType::EQUIPMENT_AMMO:
280
        case ItemDbType::EQUIPMENT_CHARM:
281
            return true;
282
        case ItemDbType::UNUSABLE:
283
        case ItemDbType::USABLE:
284
        case ItemDbType::CARD:
285
        case ItemDbType::SPRITE_RACE:
286
        case ItemDbType::SPRITE_HAIR:
287
        default:
288
            return false;
289
    }
290
}
291
292
12
void ItemDB::loadXmlFile(const std::string &fileName,
293
                         int &tagNum,
294
                         const SkipError skipError)
295
{
296
12
    if (fileName.empty())
297
    {
298
        mLoaded = true;
299
6
        return;
300
    }
301
302
    XML::Document doc(fileName,
303
        UseVirtFs_true,
304
18
        skipError);
305
12
    XmlNodeConstPtrConst rootNode = doc.rootNode();
306
307

12
    if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "items"))
308
    {
309
12
        logger->log("ItemDB: Error while loading %s!", fileName.c_str());
310
6
        mLoaded = true;
311
6
        return;
312
    }
313
314
    const ItemFieldDb::FieldInfos &requiredFields =
315
12
        ItemFieldDb::getRequiredFields();
316
    const ItemFieldDb::FieldInfos &addFields =
317
6
        ItemFieldDb::getAddFields();
318
319
516
    for_each_xml_child_node(node, rootNode)
320
    {
321

510
        if (xmlNameEqual(node, "include"))
322
        {
323
            const std::string name = XML::getProperty(node, "name", "");
324
            if (!name.empty())
325
                loadXmlFile(name, tagNum, skipError);
326
            continue;
327
        }
328

510
        if (!xmlNameEqual(node, "item"))
329
282
            continue;
330
331
228
        const int id = XML::getProperty(node, "id", 0);
332
228
        ItemInfo *itemInfo = nullptr;
333
334
228
        if (id == 0)
335
        {
336
            reportAlways("ItemDB: Invalid or missing item ID in %s!",
337
                fileName.c_str());
338
            continue;
339
        }
340
456
        else if (mItemInfos.find(id) != mItemInfos.end())
341
        {
342
            logger->log("ItemDB: Redefinition of item ID %d", id);
343
            itemInfo = mItemInfos[id];
344
        }
345
        if (itemInfo == nullptr)
346

228
            itemInfo = new ItemInfo;
347
348

1140
        const std::string typeStr = XML::getProperty(node, "type", "");
349
228
        int weight = XML::getProperty(node, "weight", 0);
350
228
        int view = XML::getProperty(node, "view", 0);
351
228
        const int cardColor = XML::getProperty(node, "cardColor", -1);
352
228
        const int inherit = XML::getProperty(node, "inherit", -1);
353
354

1140
        std::string name = XML::langProperty(node, "name", "");
355

1140
        std::string nameEn = XML::getProperty(node, "name", "");
356

1140
        std::string image = XML::getProperty(node, "image", "");
357

1140
        std::string floor = XML::getProperty(node, "floor", "");
358

1140
        std::string description = XML::langProperty(node, "description", "");
359

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

1140
            node, "skyattack-action", "");
362
        std::string waterAttackAction = XML::getProperty(
363

1140
            node, "waterattack-action", "");
364
        std::string rideAttackAction = XML::getProperty(
365

1140
            node, "rideattack-action", "");
366

1140
        std::string drawBefore = XML::getProperty(node, "drawBefore", "");
367

1140
        std::string drawAfter = XML::getProperty(node, "drawAfter", "");
368
        const int maxFloorOffset = XML::getIntProperty(
369
228
            node, "maxFloorOffset", mapTileSize, 0, mapTileSize);
370
        const int maxFloorOffsetX = XML::getIntProperty(
371
228
            node, "maxFloorOffsetX", maxFloorOffset, 0, mapTileSize);
372
        const int maxFloorOffsetY = XML::getIntProperty(
373
228
            node, "maxFloorOffsetY", maxFloorOffset, 0, mapTileSize);
374

1140
        std::string useButton = XML::langProperty(node, "useButton", "");
375

1140
        std::string useButton2 = XML::langProperty(node, "useButton2", "");
376

1140
        std::string colors = XML::getProperty(node, "colors", "");
377

1140
        std::string iconColors = XML::getProperty(node, "iconColors", "");
378
228
        if (iconColors.empty())
379
            iconColors = colors;
380
381
        // check for empty hair palete
382
228
        if (id <= -1 && id > -100)
383
        {
384
168
            if (colors.empty())
385
                colors = "hair";
386
168
            if (iconColors.empty())
387
                iconColors = "hair";
388
        }
389
390

2508
        std::string tags[3];
391
456
        tags[0] = XML::getProperty(node, "tag",
392

1140
            XML::getProperty(node, "tag1", ""));
393

1140
        tags[1] = XML::getProperty(node, "tag2", "");
394

1140
        tags[2] = XML::getProperty(node, "tag3", "");
395
396
228
        const int drawPriority = XML::getProperty(node, "drawPriority", 0);
397
398
228
        int attackRange = XML::getProperty(node, "attack-range", 0);
399
        std::string missileParticle = XML::getProperty(
400

1140
            node, "missile-particle", "");
401
        float missileZ = XML::getFloatProperty(
402
228
            node, "missile-z", 32.0f);
403
        int missileLifeTime = XML::getProperty(
404
228
            node, "missile-lifetime", 500);
405
        float missileSpeed = XML::getFloatProperty(
406
228
            node, "missile-speed", 7.0f);
407
        float missileDieDistance = XML::getFloatProperty(
408
228
            node, "missile-diedistance", 8.0f);
409

912
        int hitEffectId = XML::getProperty(node, "hit-effect-id",
410
228
            paths.getIntValue("hitEffectId"));
411

912
        int criticalEffectId = XML::getProperty(
412
            node, "critical-hit-effect-id",
413
228
            paths.getIntValue("criticalHitEffectId"));
414

912
        int missEffectId = XML::getProperty(node, "miss-effect-id",
415
228
            paths.getIntValue("missEffectId"));
416
417
456
        SpriteDisplay display;
418
228
        display.image = image;
419
228
        if (!floor.empty())
420
            display.floor = STD_MOVE(floor);
421
        else
422
            display.floor = image;
423
424
228
        const ItemInfo *inheritItemInfo = nullptr;
425
426
228
        if (inherit >= 0)
427
        {
428
            if (mItemInfos.find(inherit) != mItemInfos.end())
429
            {
430
                inheritItemInfo = mItemInfos[inherit];
431
            }
432
            else
433
            {
434
                reportAlways("Inherit item %d from not existing item %d",
435
                    id,
436
                    inherit);
437
            }
438
        }
439
440
456
        itemInfo->setId(id);
441

228
        if (name.empty() && (inheritItemInfo != nullptr))
442
            name = inheritItemInfo->getName();
443
        // TRANSLATORS: item info name
444

684
        itemInfo->setName(name.empty() ? _("unnamed") : name);
445
228
        if (nameEn.empty())
446
        {
447
            // TRANSLATORS: item info name
448
            itemInfo->setNameEn(name.empty() ? _("unnamed") : name);
449
        }
450
        else
451
        {
452
            itemInfo->setNameEn(nameEn);
453
        }
454
455

228
        if (description.empty() && (inheritItemInfo != nullptr))
456
            description = inheritItemInfo->getDescription();
457
228
        itemInfo->setDescription(description);
458
228
        if (typeStr.empty())
459
        {
460
            if (inheritItemInfo != nullptr)
461
                itemInfo->setType(inheritItemInfo->getType());
462
            else
463
                itemInfo->setType(itemTypeFromString("other"));
464
        }
465
        else
466
        {
467
228
            itemInfo->setType(itemTypeFromString(typeStr));
468
        }
469
456
        itemInfo->setType(itemTypeFromString(typeStr));
470

228
        if (useButton.empty() && (inheritItemInfo != nullptr))
471
            useButton = inheritItemInfo->getUseButton();
472
228
        if (useButton.empty())
473
432
            useButton = useButtonFromItemType(itemInfo->getType());
474
228
        itemInfo->setUseButton(useButton);
475

228
        if (useButton2.empty() && (inheritItemInfo != nullptr))
476
            useButton2 = inheritItemInfo->getUseButton();
477
228
        if (useButton2.empty())
478
456
            useButton2 = useButton2FromItemType(itemInfo->getType());
479
228
        itemInfo->setUseButton2(useButton2);
480

1140
        itemInfo->addTag(mTags["All"]);
481
456
        itemInfo->setProtected(XML::getBoolProperty(
482
            node, "sellProtected", false));
483
228
        if (cardColor != -1)
484
12
            itemInfo->setCardColor(fromInt(cardColor, ItemColor));
485
216
        else if (inheritItemInfo != nullptr)
486
            itemInfo->setCardColor(inheritItemInfo->getCardColor());
487
488
228
        switch (itemInfo->getType())
489
        {
490
            case ItemDbType::USABLE:
491
                itemInfo->addTag(mTags["Usable"]);
492
                break;
493
            case ItemDbType::CARD:
494
            case ItemDbType::UNUSABLE:
495

60
                itemInfo->addTag(mTags["Unusable"]);
496
12
                break;
497
            default:
498
            case ItemDbType::EQUIPMENT_ONE_HAND_WEAPON:
499
            case ItemDbType::EQUIPMENT_TWO_HANDS_WEAPON:
500
            case ItemDbType::EQUIPMENT_TORSO:
501
            case ItemDbType::EQUIPMENT_ARMS:
502
            case ItemDbType::EQUIPMENT_HEAD:
503
            case ItemDbType::EQUIPMENT_LEGS:
504
            case ItemDbType::EQUIPMENT_SHIELD:
505
            case ItemDbType::EQUIPMENT_RING:
506
            case ItemDbType::EQUIPMENT_NECKLACE:
507
            case ItemDbType::EQUIPMENT_FEET:
508
            case ItemDbType::EQUIPMENT_AMMO:
509
            case ItemDbType::EQUIPMENT_CHARM:
510
            case ItemDbType::SPRITE_RACE:
511
            case ItemDbType::SPRITE_HAIR:
512

1080
                itemInfo->addTag(mTags["Equipment"]);
513
216
                break;
514
        }
515
1596
        for (int f = 0; f < 3; f++)
516
        {
517
1368
            if (!tags[f].empty())
518
            {
519
                if (mTags.find(tags[f]) == mTags.end())
520
                {
521
                    mTagNames.push_back(tags[f]);
522
                    mTags[tags[f]] = tagNum ++;
523
                }
524
                itemInfo->addTag(mTags[tags[f]]);
525
            }
526
        }
527
528
456
        std::string effect;
529
228
        readFields(effect, node, requiredFields);
530
228
        readFields(effect, node, addFields);
531

1140
        std::string temp = XML::langProperty(node, "effect", "");
532

228
        if (!effect.empty() && !temp.empty())
533
            effect.append(" / ");
534
228
        effect.append(temp);
535
536
228
        if (inheritItemInfo != nullptr)
537
        {
538
            if (view == 0)
539
                view = inheritItemInfo->getView();
540
            if (weight == 0)
541
                weight = inheritItemInfo->getWeight();
542
            if (attackAction.empty())
543
                attackAction = inheritItemInfo->getAttackAction();
544
            if (skyAttackAction.empty())
545
                skyAttackAction = inheritItemInfo->getSkyAttackAction();
546
            if (waterAttackAction.empty())
547
                waterAttackAction = inheritItemInfo->getWaterAttackAction();
548
            if (rideAttackAction.empty())
549
                rideAttackAction = inheritItemInfo->getRideAttackAction();
550
            if (attackRange == 0)
551
                attackRange = inheritItemInfo->getAttackRange();
552
            if (hitEffectId == 0)
553
                hitEffectId = inheritItemInfo->getHitEffectId();
554
            if (criticalEffectId == 0)
555
                criticalEffectId = inheritItemInfo->getCriticalHitEffectId();
556
            if (missEffectId == 0)
557
                missEffectId = inheritItemInfo->getMissEffectId();
558
            if (colors.empty())
559
                colors = inheritItemInfo->getColorsListName();
560
            if (iconColors.empty())
561
                iconColors = inheritItemInfo->getIconColorsListName();
562
            if (effect.empty())
563
                effect = inheritItemInfo->getEffect();
564
565
            const MissileInfo &inheritMissile =
566
                inheritItemInfo->getMissileConst();
567
            if (missileParticle.empty())
568
                missileParticle = inheritMissile.particle;
569
            if (missileZ == 32.0F)
570
                missileZ = inheritMissile.z;
571
            if (missileLifeTime == 500)
572
                missileLifeTime = inheritMissile.lifeTime;
573
            if (missileSpeed == 7.0F)
574
                missileSpeed = inheritMissile.speed;
575
            if (missileDieDistance == 8.0F)
576
                missileDieDistance = inheritMissile.dieDistance;
577
        }
578
579
456
        itemInfo->setView(view);
580
456
        itemInfo->setWeight(weight);
581
228
        itemInfo->setAttackAction(attackAction);
582
228
        itemInfo->setSkyAttackAction(skyAttackAction);
583
228
        itemInfo->setWaterAttackAction(waterAttackAction);
584
228
        itemInfo->setRideAttackAction(rideAttackAction);
585
456
        itemInfo->setAttackRange(attackRange);
586
456
        itemInfo->setHitEffectId(hitEffectId);
587
456
        itemInfo->setCriticalHitEffectId(criticalEffectId);
588
456
        itemInfo->setMissEffectId(missEffectId);
589
228
        itemInfo->setDrawBefore(-1, parseSpriteName(drawBefore));
590
228
        itemInfo->setDrawAfter(-1, parseSpriteName(drawAfter));
591
228
        itemInfo->setDrawPriority(-1, drawPriority);
592
228
        itemInfo->setColorsList(colors);
593
228
        itemInfo->setIconColorsList(iconColors);
594
456
        itemInfo->setMaxFloorOffsetX(maxFloorOffsetX);
595
456
        itemInfo->setMaxFloorOffsetY(maxFloorOffsetY);
596

1368
        itemInfo->setPickupCursor(XML::getProperty(
597
            node, "pickupCursor", "pickup"));
598
228
        itemInfo->setEffect(effect);
599
600
228
        MissileInfo &missile = itemInfo->getMissile();
601
228
        missile.particle = STD_MOVE(missileParticle);
602
228
        missile.z = missileZ;
603
228
        missile.lifeTime = missileLifeTime;
604
228
        missile.speed = missileSpeed;
605
228
        missile.dieDistance = missileDieDistance;
606
607
1152
        for_each_xml_child_node(itemChild, node)
608
        {
609

924
            if (xmlNameEqual(itemChild, "sprite"))
610
            {
611
258
                loadSpriteRef(itemInfo, itemChild);
612
            }
613

666
            else if (xmlNameEqual(itemChild, "particlefx"))
614
            {
615
                if (XmlHaveChildContent(itemChild))
616
                    display.particles.push_back(XmlChildContent(itemChild));
617
            }
618

666
            else if (xmlNameEqual(itemChild, "sound"))
619
            {
620
96
                loadSoundRef(itemInfo, itemChild);
621
            }
622

570
            else if (xmlNameEqual(itemChild, "floor"))
623
            {
624
                loadFloorSprite(display, itemChild);
625
            }
626

570
            else if (xmlNameEqual(itemChild, "replace"))
627
            {
628
                loadReplaceSprite(itemInfo, itemChild);
629
            }
630

570
            else if (xmlNameEqual(itemChild, "drawAfter"))
631
            {
632
                loadOrderSprite(itemInfo, itemChild, true);
633
            }
634

570
            else if (xmlNameEqual(itemChild, "drawBefore"))
635
            {
636
                loadOrderSprite(itemInfo, itemChild, false);
637
            }
638

570
            else if (xmlNameEqual(itemChild, "inventory"))
639
            {
640
                loadMenu(itemChild, itemInfo->getInventoryMenu());
641
            }
642

570
            else if (xmlNameEqual(itemChild, "storage"))
643
            {
644
                loadMenu(itemChild, itemInfo->getStorageMenu());
645
            }
646

570
            else if (xmlNameEqual(itemChild, "cart"))
647
            {
648
                loadMenu(itemChild, itemInfo->getCartMenu());
649
            }
650
        }
651
652
/*
653
        logger->log("start dump item: %d", id);
654
        if (itemInfo->isRemoveSprites())
655
        {
656
            for (int f = 0; f < 10; f ++)
657
            {
658
                logger->log("dir: %d", f);
659
                SpriteToItemMap *const spriteToItems
660
                    = itemInfo->getSpriteToItemReplaceMap(f);
661
                if (!spriteToItems)
662
                {
663
                    logger->log("null");
664
                    continue;
665
                }
666
                for (SpriteToItemMapCIter itr = spriteToItems->begin(),
667
                     itr_end = spriteToItems->end(); itr != itr_end; ++ itr)
668
                {
669
                    const int remSprite = itr->first;
670
                    const IntMap &itemReplacer = itr->second;
671
                    logger->log("sprite: %d", remSprite);
672
673
                    for (IntMapCIter repIt = itemReplacer.begin(),
674
                         repIt_end = itemReplacer.end();
675
                         repIt != repIt_end; ++ repIt)
676
                    {
677
                        logger->log("from %d to %d", repIt->first,
678
                            repIt->second);
679
                    }
680
                }
681
            }
682
        }
683
684
        logger->log("--------------------------------");
685
        logger->log("end dump item");
686
*/
687
688
228
        itemInfo->setDisplay(display);
689
690
228
        mItemInfos[id] = itemInfo;
691
228
        if (!name.empty())
692
        {
693
456
            temp = normalize(name);
694
228
            mNamedItemInfos[temp] = itemInfo;
695
        }
696
228
        if (!nameEn.empty())
697
        {
698
456
            temp = normalize(nameEn);
699
228
            mNamedItemInfos[temp] = itemInfo;
700
        }
701
702
228
        if (!attackAction.empty())
703
        {
704
            if (attackRange == 0)
705
            {
706
                reportAlways("ItemDB: Missing attack range from weapon %i!",
707
                    id);
708
            }
709
        }
710
711
228
        STD_VECTOR<ItemMenuItem> &inventoryMenu = itemInfo->getInventoryMenu();
712
713
228
        if (inventoryMenu.empty())
714
        {
715
684
            std::string name1 = itemInfo->getUseButton();
716
684
            std::string name2 = itemInfo->getUseButton2();
717

228
            const bool isEquipment = getIsEquipment(itemInfo->getType());
718
719
            if (isEquipment)
720
            {
721
                if (name1.empty())
722
                {
723
                    // TRANSLATORS: popup menu item
724
                    name1 = _("Equip");
725
                }
726
                if (name2.empty())
727
                {
728
                    // TRANSLATORS: popup menu item
729
                    name2 = _("Unequip");
730
                }
731
            }
732
            else
733
            {
734
228
                if (name1.empty())
735
                {
736
                    // TRANSLATORS: popup menu item
737
216
                    name1 = _("Use");
738
                }
739
228
                if (name2.empty())
740
                {
741
                    // TRANSLATORS: popup menu item
742
228
                    name2 = _("Use");
743
                }
744
            }
745

1824
            inventoryMenu.push_back(ItemMenuItem(
746
                name1,
747
                name2,
748
                "useinv 'INVINDEX'",
749
                "useinv 'INVINDEX'"));
750
        }
751
752
#define CHECK_PARAM(param) \
753
        if (param.empty()) \
754
        { \
755
            logger->log("ItemDB: Missing " #param " attribute for item %i!", \
756
                id); \
757
        }
758
759

240
        if (id >= 0 && typeStr != "other")
760
        {
761

12
            CHECK_PARAM(name)
762

12
            CHECK_PARAM(description)
763

12
            CHECK_PARAM(image)
764
        }
765
#undef CHECK_PARAM
766
    }
767
768
6
    mLoaded = true;
769
}
770
771
2
const StringVect &ItemDB::getTags()
772
{
773
2
    return mTagNames;
774
}
775
776
int ItemDB::getTagId(const std::string &tagName)
777
{
778
    return mTags[tagName];
779
}
780
781
384
void ItemDB::unload()
782
{
783
384
    logger->log1("Unloading item database...");
784
785
384
    delete2(mUnknown);
786
787
384
    delete_all(mItemInfos);
788
384
    mItemInfos.clear();
789
384
    mNamedItemInfos.clear();
790
384
    mTags.clear();
791
384
    mTagNames.clear();
792
384
    mLoaded = false;
793
384
}
794
795
bool ItemDB::exists(const int id)
796
{
797


222
    if (!mLoaded)
798
        return false;
799
800
444
    const ItemInfos::const_iterator i = mItemInfos.find(id);
801
444
    return i != mItemInfos.end();
802
}
803
804
bool ItemDB::exists(const std::string &name)
805
{
806
    if (!mLoaded)
807
        return false;
808
809
    const NamedItemInfos::const_iterator i = mNamedItemInfos.find(
810
        normalize(name));
811
    return i != mNamedItemInfos.end();
812
}
813
814
220
const ItemInfo &ItemDB::get(const int id)
815
{
816
220
    if (!mLoaded)
817
4
        load();
818
819
440
    const ItemInfos::const_iterator i = mItemInfos.find(id);
820
821
220
    if (i == mItemInfos.end())
822
    {
823
        reportAlways("ItemDB: Warning, unknown item ID# %d", id);
824
        return *mUnknown;
825
    }
826
827
220
    return *(i->second);
828
}
829
830
38
const ItemInfo &ItemDB::get(const std::string &name)
831
{
832
38
    if (!mLoaded)
833
2
        load();
834
835
76
    const NamedItemInfos::const_iterator i = mNamedItemInfos.find(
836
114
        normalize(name));
837
838
38
    if (i == mNamedItemInfos.end())
839
    {
840
        if (!name.empty())
841
        {
842
            reportAlways("ItemDB: Warning, unknown item name \"%s\"",
843
                name.c_str());
844
        }
845
        return *mUnknown;
846
    }
847
848
38
    return *(i->second);
849
}
850
851
2
const ItemDB::ItemInfos &ItemDB::getItemInfos()
852
{
853
2
    return mItemInfos;
854
}
855
856
const ItemInfo &ItemDB::getEmpty()
857
{
858
    return *mUnknown;
859
}
860
861
456
static int parseSpriteName(const std::string &name)
862
{
863
456
    int id = -1;
864

912
    if (name == "race" || name == "type")
865
    {
866
        id = 0;
867
    }
868

1368
    else if (name == "shoes" || name == "boot" || name == "boots")
869
    {
870
        id = 1;
871
    }
872

1368
    else if (name == "bottomclothes" || name == "bottom" || name == "pants")
873
    {
874
        id = 2;
875
    }
876
912
    else if (name == "topclothes" || name == "top"
877

1368
             || name == "torso" || name == "body")
878
    {
879
        id = 3;
880
    }
881
456
    else if (name == "misc1")
882
    {
883
        id = 4;
884
    }
885

1368
    else if (name == "misc2" || name == "scarf" || name == "scarfs")
886
    {
887
        id = 5;
888
    }
889
456
    else if (name == "hair")
890
    {
891
        id = 6;
892
    }
893

912
    else if (name == "hat" || name == "hats")
894
    {
895
        id = 7;
896
    }
897
456
    else if (name == "wings")
898
    {
899
        id = 8;
900
    }
901

912
    else if (name == "glove" || name == "gloves")
902
    {
903
        id = 9;
904
    }
905

912
    else if (name == "weapon" || name == "weapons")
906
    {
907
        id = 10;
908
    }
909

912
    else if (name == "shield" || name == "shields")
910
    {
911
        id = 11;
912
    }
913

912
    else if (name == "amulet" || name == "amulets")
914
    {
915
        id = 12;
916
    }
917

912
    else if (name == "ring" || name == "rings")
918
    {
919
        id = 13;
920
    }
921
922
456
    return id;
923
}
924
925
static int parseDirectionName(const std::string &name)
926
{
927
    int id = -1;
928
    if (name == "down")
929
    {
930
#ifdef TMWA_SUPPORT
931
        if (Net::getNetworkType() == ServerType::TMWATHENA)
932
            id = -2;
933
        else
934
#endif
935
            id = SpriteDirection::DOWN;
936
    }
937
    else if (name == "downleft" || name == "leftdown")
938
    {
939
        id = SpriteDirection::DOWNLEFT;
940
    }
941
    else if (name == "left")
942
    {
943
        id = SpriteDirection::LEFT;
944
    }
945
    else if (name == "upleft" || name == "leftup")
946
    {
947
        id = SpriteDirection::UPLEFT;
948
    }
949
    else if (name == "up")
950
    {
951
#ifdef TMWA_SUPPORT
952
        if (Net::getNetworkType() == ServerType::TMWATHENA)
953
            id = -3;
954
        else
955
#endif
956
            id = SpriteDirection::UP;
957
    }
958
    else if (name == "upright" || name == "rightup")
959
    {
960
        id = SpriteDirection::UPRIGHT;
961
    }
962
    else if (name == "right")
963
    {
964
        id = SpriteDirection::RIGHT;
965
    }
966
    else if (name == "downright" || name == "rightdown")
967
    {
968
        id = SpriteDirection::DOWNRIGHT;
969
    }
970
    else if (name == "downall")
971
    {
972
        id = -2;
973
    }
974
    else if (name == "upall")
975
    {
976
        id = -3;
977
    }
978
    // hack for died action.
979
    else if (name == "died")
980
    {
981
        id = 9;
982
    }
983
984
    return id;
985
}
986
987
258
static void loadSpriteRef(ItemInfo *const itemInfo, XmlNodeConstPtr node)
988
{
989

1290
    const std::string gender = XML::getProperty(node, "gender", "unisex");
990

258
    if ((node == nullptr) || !XmlHaveChildContent(node))
991
        return;
992
993
1032
    const std::string filename = XmlChildContent(node);
994
995
258
    const int race = XML::getProperty(node, "race", 0);
996

516
    if (gender == "male" || gender == "unisex")
997
216
        itemInfo->setSprite(filename, Gender::MALE, race);
998

474
    if (gender == "female" || gender == "unisex")
999
258
        itemInfo->setSprite(filename, Gender::FEMALE, race);
1000

516
    if (gender == "other" || gender == "unisex")
1001
216
        itemInfo->setSprite(filename, Gender::OTHER, race);
1002
}
1003
1004
96
static void loadSoundRef(ItemInfo *const itemInfo, XmlNodeConstPtr node)
1005
{
1006

192
    if (node == nullptr ||
1007
192
        !XmlHaveChildContent(node))
1008
    {
1009
        return;
1010
    }
1011

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

6
}
1232
#endif  // UNITTESTS