1
/*
2
 *  The Mana Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *
6
 *  This file is part of The Mana Client.
7
 *
8
 *  This program is free software; you can redistribute it and/or modify
9
 *  it under the terms of the GNU General Public License as published by
10
 *  the Free Software Foundation; either version 2 of the License, or
11
 *  any later version.
12
 *
13
 *  This program is distributed in the hope that it will be useful,
14
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 *  GNU General Public License for more details.
17
 *
18
 *  You should have received a copy of the GNU General Public License
19
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
#include "resources/itemdb.h"
23
24
#include "log.h"
25
26
#include "net/net.h"
27
28
#include "resources/iteminfo.h"
29
#include "resources/resourcemanager.h"
30
31
#include "utils/dtor.h"
32
#include "utils/gettext.h"
33
#include "utils/stringutils.h"
34
#include "utils/xml.h"
35
#include "configuration.h"
36
37
#include <libxml/tree.h>
38
39
#include <cassert>
40
41
void setStatsList(const std::list<ItemStat> &stats)
42
{
43
    extraStats = stats;
44
}
45
46
static ItemType itemTypeFromString(const std::string &name, int id = 0)
47
{
48
    if      (name=="generic")           return ITEM_UNUSABLE;
49
    else if (name=="usable")            return ITEM_USABLE;
50
    else if (name=="equip-1hand")       return ITEM_EQUIPMENT_ONE_HAND_WEAPON;
51
    else if (name=="equip-2hand")       return ITEM_EQUIPMENT_TWO_HANDS_WEAPON;
52
    else if (name=="equip-torso")       return ITEM_EQUIPMENT_TORSO;
53
    else if (name=="equip-arms")        return ITEM_EQUIPMENT_ARMS;
54
    else if (name=="equip-head")        return ITEM_EQUIPMENT_HEAD;
55
    else if (name=="equip-legs")        return ITEM_EQUIPMENT_LEGS;
56
    else if (name=="equip-shield")      return ITEM_EQUIPMENT_SHIELD;
57
    else if (name=="equip-ring")        return ITEM_EQUIPMENT_RING;
58
    else if (name=="equip-charm")       return ITEM_EQUIPMENT_CHARM;
59
    else if (name=="equip-necklace")    return ITEM_EQUIPMENT_NECKLACE;
60
    else if (name=="equip-feet")        return ITEM_EQUIPMENT_FEET;
61
    else if (name=="equip-ammo")        return ITEM_EQUIPMENT_AMMO;
62
    else if (name=="racesprite")        return ITEM_SPRITE_RACE;
63
    else if (name=="hairsprite")        return ITEM_SPRITE_HAIR;
64
    else return ITEM_UNUSABLE;
65
}
66
67
void ItemDB::loadEmptyItemDefinition()
68
{
69
    mUnknown->mName = _("Unknown item");
70
    mUnknown->mDisplay = SpriteDisplay();
71
    std::string errFile = paths.getStringValue("spriteErrorFile");
72
    mUnknown->setSprite(errFile, GENDER_MALE);
73
    mUnknown->setSprite(errFile, GENDER_FEMALE);
74
    mUnknown->setHitEffectId(paths.getIntValue("hitEffectId"));
75
    mUnknown->setCriticalHitEffectId(paths.getIntValue("criticalHitEffectId"));
76
}
77
78
/*
79
 * Common itemDB functions
80
 */
81
82
bool ItemDB::exists(int id)
83
{
84
    assert(mLoaded);
85
86
    ItemInfos::const_iterator i = mItemInfos.find(id);
87
88
    return i != mItemInfos.end();
89
}
90
91
const ItemInfo &ItemDB::get(int id)
92
{
93
    assert(mLoaded);
94
95
    ItemInfos::const_iterator i = mItemInfos.find(id);
96
97
    if (i == mItemInfos.end())
98
    {
99
        logger->log("ItemDB: Warning, unknown item ID# %d", id);
100
        return *mUnknown;
101
    }
102
103
    return *(i->second);
104
}
105
106
const ItemInfo &ItemDB::get(const std::string &name)
107
{
108
    assert(mLoaded);
109
110
    NamedItemInfos::const_iterator i = mNamedItemInfos.find(normalize(name));
111
112
    if (i == mNamedItemInfos.end())
113
    {
114
        if (!name.empty())
115
        {
116
            logger->log("ItemDB: Warning, unknown item name \"%s\"",
117
                        name.c_str());
118
        }
119
        return *mUnknown;
120
    }
121
122
    return *(i->second);
123
}
124
125
void ItemDB::loadSpriteRef(ItemInfo *itemInfo, xmlNodePtr node)
126
{
127
    std::string gender = XML::getProperty(node, "gender", "unisex");
128
    std::string filename = (const char*) node->xmlChildrenNode->content;
129
130
    if (gender == "male" || gender == "unisex")
131
    {
132
        itemInfo->setSprite(filename, GENDER_MALE);
133
    }
134
    if (gender == "female" || gender == "unisex")
135
    {
136
        itemInfo->setSprite(filename, GENDER_FEMALE);
137
    }
138
}
139
140
void ItemDB::loadSoundRef(ItemInfo *itemInfo, xmlNodePtr node)
141
{
142
    std::string event = XML::getProperty(node, "event", "");
143
    std::string filename = (const char*) node->xmlChildrenNode->content;
144
145
    if (event == "hit")
146
    {
147
        itemInfo->addSound(EQUIP_EVENT_HIT, filename);
148
    }
149
    else if (event == "strike")
150
    {
151
        itemInfo->addSound(EQUIP_EVENT_STRIKE, filename);
152
    }
153
    else
154
    {
155
        logger->log("ItemDB: Ignoring unknown sound event '%s'",
156
                event.c_str());
157
    }
158
}
159
160
void ItemDB::loadFloorSprite(SpriteDisplay *display, xmlNodePtr floorNode)
161
{
162
    for_each_xml_child_node(spriteNode, floorNode)
163
    {
164
        if (xmlStrEqual(spriteNode->name, BAD_CAST "sprite"))
165
        {
166
            SpriteReference *currentSprite = new SpriteReference;
167
            currentSprite->sprite = (const char*)spriteNode->xmlChildrenNode->content;
168
            currentSprite->variant = XML::getProperty(spriteNode, "variant", 0);
169
            display->sprites.push_back(currentSprite);
170
        }
171
        else if (xmlStrEqual(spriteNode->name, BAD_CAST "particlefx"))
172
        {
173
            std::string particlefx = (const char*)spriteNode->xmlChildrenNode->content;
174
            display->particles.push_back(particlefx);
175
        }
176
    }
177
}
178
179
void ItemDB::unload()
180
{
181
    logger->log("Unloading item database...");
182
183
    delete mUnknown;
184
    mUnknown = NULL;
185
186
    delete_all(mItemInfos);
187
    mItemInfos.clear();
188
    mNamedItemInfos.clear();
189
    mLoaded = false;
190
}
191
192
void ItemDB::loadCommonRef(ItemInfo *itemInfo, xmlNodePtr node)
193
{
194
        int id = XML::getProperty(node, "id", 0);
195
196
        if (!id)
197
        {
198
            logger->log("ItemDB: Invalid or missing item Id in "
199
                        ITEMS_DB_FILE "!");
200
            return;
201
        }
202
        else if (mItemInfos.find(id) != mItemInfos.end())
203
            logger->log("ItemDB: Redefinition of item Id %d", id);
204
205
        int view = XML::getProperty(node, "view", 0);
206
207
        std::string name = XML::getProperty(node, "name", "");
208
        std::string image = XML::getProperty(node, "image", "");
209
        std::string description = XML::getProperty(node, "description", "");
210
        std::string attackAction = XML::getProperty(node, "attack-action",
211
                                                    SpriteAction::INVALID);
212
        int attackRange = XML::getProperty(node, "attack-range", 0);
213
        std::string missileParticleFile = XML::getProperty(node,
214
                                                           "missile-particle",
215
                                                           "");
216
        int hitEffectId = XML::getProperty(node, "hit-effect-id",
217
                                           paths.getIntValue("hitEffectId"));
218
        int criticalEffectId = XML::getProperty(node, "critical-hit-effect-id",
219
                                      paths.getIntValue("criticalHitEffectId"));
220
221
        // Load Ta Item Type
222
        std::string typeStr = XML::getProperty(node, "type", "other");
223
        itemInfo->mType = itemTypeFromString(typeStr);
224
225
        int weight = XML::getProperty(node, "weight", 0);
226
        itemInfo->mWeight = weight > 0 ? weight : 0;
227
228
        SpriteDisplay display;
229
        display.image = image;
230
231
        itemInfo->mId = id;
232
        itemInfo->mName = name;
233
        itemInfo->mDescription = description;
234
        itemInfo->mView = view;
235
        itemInfo->mWeight = weight;
236
        itemInfo->mAttackAction = attackAction;
237
        itemInfo->mAttackRange = attackRange;
238
        itemInfo->setMissileParticleFile(missileParticleFile);
239
        itemInfo->setHitEffectId(hitEffectId);
240
        itemInfo->setCriticalHitEffectId(criticalEffectId);
241
242
        // Load <sprite>, <sound>, and <floor>
243
        for_each_xml_child_node(itemChild, node)
244
        {
245
            if (xmlStrEqual(itemChild->name, BAD_CAST "sprite"))
246
            {
247
                std::string attackParticle = XML::getProperty(
248
                    itemChild, "particle-effect", "");
249
                itemInfo->mParticle = attackParticle;
250
251
                loadSpriteRef(itemInfo, itemChild);
252
            }
253
            else if (xmlStrEqual(itemChild->name, BAD_CAST "sound"))
254
            {
255
                loadSoundRef(itemInfo, itemChild);
256
            }
257
            else if (xmlStrEqual(itemChild->name, BAD_CAST "floor"))
258
            {
259
                loadFloorSprite(&display, itemChild);
260
            }
261
262
        }
263
264
        // If the item has got a floor image, we bind the good reference.
265
        itemInfo->mDisplay = display;
266
}
267
268
void ItemDB::addItem(ItemInfo *itemInfo)
269
{
270
    std::string itemName = itemInfo->mName;
271
    itemInfo->mName = itemName.empty() ? _("unnamed") : itemName;
272
    mItemInfos[itemInfo->mId] = itemInfo;
273
    if (!itemName.empty())
274
    {
275
        std::string temp = normalize(itemName);
276
277
        NamedItemInfos::const_iterator itr = mNamedItemInfos.find(temp);
278
        if (itr == mNamedItemInfos.end())
279
            mNamedItemInfos[temp] = itemInfo;
280
        else
281
            logger->log("ItemDB: Duplicate name (%s) for item id %d found.",
282
                        temp.c_str(), itemInfo->mId);
283
284
    }
285
}
286
287
template <class T>
288
static void checkParameter(int id, const T param, const T errorValue)
289
{
290
    if (param == errorValue)
291
    {
292
        std::stringstream errMsg;
293
        errMsg << "ItemDB: Missing " << param << " attribute for item id "
294
               << id << "!";
295
        logger->log("%s", errMsg.str().c_str());
296
    }
297
}
298
299
void ItemDB::checkItemInfo(ItemInfo* itemInfo)
300
{
301
    int id = itemInfo->mId;
302
    if (!itemInfo->getAttackAction().empty())
303
        if (itemInfo->mAttackRange == 0)
304
            logger->log("ItemDB: Missing attack range from weapon %i!", id);
305
306
    if (id >= 0)
307
    {
308
        checkParameter(id, itemInfo->mName, std::string());
309
        checkParameter(id, itemInfo->mDescription, std::string());
310
        checkParameter(id, itemInfo->mDisplay.image, std::string());
311
        checkParameter(id, itemInfo->mWeight, 0);
312
    }
313
}
314
315
namespace TmwAthena {
316
317
// Description fields used by TaItemDB *itemInfo->mEffect.
318
319
static char const *const fields[][2] =
320
{
321
    { "attack",    N_("Attack %+d")    },
322
    { "defense",   N_("Defense %+d")   },
323
    { "hp",        N_("HP %+d")        },
324
    { "mp",        N_("MP %+d")        }
325
};
326
327
void TaItemDB::load()
328
{
329
    if (mLoaded)
330
        unload();
331
332
    logger->log("Initializing TmwAthena item database...");
333
334
    mUnknown = new TaItemInfo;
335
    loadEmptyItemDefinition();
336
337
    XML::Document doc(ITEMS_DB_FILE);
338
    xmlNodePtr rootNode = doc.rootNode();
339
340
    if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "items"))
341
    {
342
        logger->error("ItemDB: Error while loading " ITEMS_DB_FILE "!");
343
        return;
344
    }
345
346
    for_each_xml_child_node(node, rootNode)
347
    {
348
        if (!xmlStrEqual(node->name, BAD_CAST "item"))
349
            continue;
350
351
        TaItemInfo *itemInfo = new TaItemInfo;
352
353
        loadCommonRef(itemInfo, node);
354
355
        // Everything not unusable or usable is equippable by the Ta type system.
356
        itemInfo->mEquippable  = itemInfo->mType != ITEM_UNUSABLE
357
                                 && itemInfo->mType != ITEM_USABLE;
358
359
        // Load nano description
360
        std::vector<std::string> effect;
361
        for (int i = 0; i < int(sizeof(fields) / sizeof(fields[0])); ++i)
362
        {
363
            int value = XML::getProperty(node, fields[i][0], 0);
364
            if (!value)
365
                continue;
366
            effect.push_back(strprintf(gettext(fields[i][1]), value));
367
        }
368
        for (std::list<ItemStat>::iterator it = extraStats.begin();
369
                it != extraStats.end(); it++)
370
        {
371
            int value = XML::getProperty(node, it->mTag.c_str(), 0);
372
            if (!value)
373
                continue;
374
            effect.push_back(strprintf(it->mFormat.c_str(), value));
375
        }
376
        std::string temp = XML::getProperty(node, "effect", "");
377
        if (!temp.empty())
378
            effect.push_back(temp);
379
380
        itemInfo->mEffect = effect;
381
382
        checkItemInfo(itemInfo);
383
384
        addItem(itemInfo);
385
    }
386
387
    checkHairWeaponsRacesSpecialIds();
388
389
    mLoaded = true;
390
}
391
392
void TaItemDB::checkItemInfo(ItemInfo* itemInfo)
393
{
394
    ItemDB::checkItemInfo(itemInfo);
395
396
    // Check for unusable items?
397
    //checkParameter(id, itemInfo->mType, 0);
398
}
399
400
}; // namespace TmwAthena
401
402
namespace ManaServ {
403
404
static std::map<std::string, const char* > triggerTable;
405
406
static void initTriggerTable()
407
{
408
    if (triggerTable.empty())
409
    {
410
        // FIXME: This should ideally be softcoded via XML or similar.
411
        logger->log("Initializing ManaServ trigger table...");
412
        triggerTable["existence"]       = " when it is in the inventory";
413
        triggerTable["activation"]      = " upon activation";
414
        triggerTable["equip"]           = " upon successful equip";
415
        triggerTable["leave-inventory"] = " when it leaves the inventory";
416
        triggerTable["unequip"]         = " when it is unequipped";
417
        triggerTable["equip-change"]    = " when it changes the way it is equipped";
418
    }
419
}
420
421
void ManaServItemDB::load()
422
{
423
    if (mLoaded)
424
        unload();
425
426
    // Initialize the trigger table for effect descriptions
427
    initTriggerTable();
428
429
    logger->log("Initializing ManaServ item database...");
430
431
    mUnknown = new ManaServItemInfo;
432
    loadEmptyItemDefinition();
433
434
    XML::Document doc(ITEMS_DB_FILE);
435
    xmlNodePtr rootNode = doc.rootNode();
436
437
    if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "items"))
438
    {
439
        logger->log("ItemDB: Error while loading " ITEMS_DB_FILE "!");
440
        return;
441
    }
442
443
    for_each_xml_child_node(node, rootNode)
444
    {
445
        if (!xmlStrEqual(node->name, BAD_CAST "item"))
446
            continue;
447
448
        ManaServItemInfo *itemInfo = new ManaServItemInfo;
449
450
        loadCommonRef(itemInfo, node);
451
452
        // We default eqippable and activatable to false as their actual value will be set
453
        // within the <equip> and <effect> sub-nodes..
454
        itemInfo->mActivatable = false;
455
        itemInfo->mEquippable = false;
456
457
        // Load <equip>, and <effect> sub nodes.
458
        std::vector<std::string> effect;
459
        for_each_xml_child_node(itemChild, node)
460
        {
461
            if (xmlStrEqual(itemChild->name, BAD_CAST "equip"))
462
            {
463
                // The fact that there is a way to equip is enough.
464
                // Discard any details, but mark the item as equippable.
465
                itemInfo->mEquippable = true;
466
            }
467
            else if (xmlStrEqual(itemChild->name, BAD_CAST "effect"))
468
            {
469
                std::string trigger = XML::getProperty(
470
                        itemChild, "trigger", "");
471
                if (trigger.empty())
472
                {
473
                    logger->log("Found empty trigger effect label, skipping.");
474
                    continue;
475
                }
476
477
                if (trigger == "activation")
478
                    itemInfo->mActivatable = true;
479
480
                std::map<std::string, const char* >::const_iterator triggerLabel =
481
                        triggerTable.find(trigger);
482
                if (triggerLabel == triggerTable.end())
483
                {
484
                    logger->log("Warning: unknown trigger %s in item %d!",
485
                                trigger.c_str(), itemInfo->mId);
486
                    continue;
487
                }
488
489
                for_each_xml_child_node(effectChild, itemChild)
490
                {
491
                    if (xmlStrEqual(effectChild->name, BAD_CAST "modifier"))
492
                    {
493
                        std::string attribute = XML::getProperty(
494
                                effectChild, "attribute", "");
495
                        double value = XML::getFloatProperty(
496
                                effectChild, "value", 0.0);
497
                        int duration = XML::getProperty(
498
                                effectChild, "duration", 0);
499
                        if (attribute.empty() || !value)
500
                        {
501
                            logger->log("Warning: incomplete modifier definition, skipping.");
502
                            continue;
503
                        }
504
                        std::list<ItemStat>::const_iterator
505
                                it = extraStats.begin(),
506
                                it_end = extraStats.end();
507
                        while (it != it_end && !(*it == attribute))
508
                            ++it;
509
                        if (it == extraStats.end())
510
                        {
511
                            logger->log("Warning: unknown modifier tag %s, skipping.", attribute.c_str());
512
                            continue;
513
                        }
514
                        effect.push_back(
515
                                strprintf(strprintf(
516
                                        duration ?
517
                                        strprintf("%%s%%s. This effect lasts %d ticks.", duration).c_str()
518
                                        : "%s%s.", it->mFormat.c_str(), triggerLabel->second).c_str(), value));
519
                    }
520
                    else if (xmlStrEqual(effectChild->name, BAD_CAST "modifier"))
521
                        effect.push_back(strprintf("Provides an autoattack%s.",
522
                                                   triggerLabel->second));
523
                    else if (xmlStrEqual(effectChild->name, BAD_CAST "consumes"))
524
                        effect.push_back(strprintf("This will be consumed%s.",
525
                                                   triggerLabel->second));
526
                    else if (xmlStrEqual(effectChild->name, BAD_CAST "label"))
527
                        effect.push_back(
528
                                (const char*)effectChild->xmlChildrenNode->content);
529
                }
530
            }
531
            // Set Item Type based on subnodes info
532
            // TODO: Improve it once the itemTypes are loaded through xml
533
            itemInfo->mType = ITEM_UNUSABLE;
534
            if (itemInfo->mActivatable)
535
                itemInfo->mType = ITEM_USABLE;
536
            else if (itemInfo->mEquippable)
537
                itemInfo->mType = ITEM_EQUIPMENT_TORSO;
538
        } // end for_each_xml_child_node(itemChild, node)
539
540
        itemInfo->mEffect = effect;
541
542
        checkItemInfo(itemInfo);
543
544
        addItem(itemInfo);
545
    }
546
547
    mLoaded = true;
548
}
549
550
void ManaServItemDB::checkItemInfo(ItemInfo* itemInfo)
551
{
552
    ItemDB::checkItemInfo(itemInfo);
553
554
    // Add specific Manaserv checks here
555
}
556
557
} // namespace ManaServ