1
/*
2
 *  The Mana Server
3
 *  Copyright (C) 2004-2010  The Mana World Development Team
4
 *
5
 *  This file is part of The Mana Server.
6
 *
7
 *  The Mana Server is free software; you can redistribute it and/or modify
8
 *  it under the terms of the GNU General Public License as published by
9
 *  the Free Software Foundation; either version 2 of the License, or
10
 *  any later version.
11
 *
12
 *  The Mana Server is distributed in the hope that it will be useful,
13
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 *  GNU General Public License for more details.
16
 *
17
 *  You should have received a copy of the GNU General Public License
18
 *  along with The Mana Server.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "game-server/itemmanager.h"
22
23
#include "common/defines.h"
24
#include "common/resourcemanager.h"
25
#include "game-server/attributemanager.h"
26
#include "game-server/item.h"
27
#include "game-server/skillmanager.h"
28
#include "scripting/script.h"
29
#include "utils/logger.h"
30
31
#include <map>
32
#include <set>
33
#include <sstream>
34
35
void ItemManager::initialize()
36
{
37
    reload();
38
}
39
40
void ItemManager::reload()
41
{
42
    mVisibleEquipSlotCount = 0;
43
    readEquipSlotsFile();
44
    readItemsFile();
45
}
46
47
void ItemManager::deinitialize()
48
{
49
    for (ItemClasses::iterator i = mItemClasses.begin(),
50
         i_end = mItemClasses.end(); i != i_end; ++i)
51
    {
52
        delete i->second;
53
    }
54
    mItemClasses.clear();
55
    mItemClassesByName.clear();
56
}
57
58
ItemClass *ItemManager::getItem(int itemId) const
59
{
60
    ItemClasses::const_iterator i = mItemClasses.find(itemId);
61
    return i != mItemClasses.end() ? i->second : 0;
62
}
63
64
ItemClass *ItemManager::getItemByName(const std::string &name) const
65
{
66
    return mItemClassesByName.find(name);
67
}
68
69
unsigned int ItemManager::getDatabaseVersion() const
70
{
71
    return mItemDatabaseVersion;
72
}
73
74
const std::string &ItemManager::getEquipNameFromId(unsigned int id) const
75
{
76
    return mEquipSlots.at(id).first;
77
}
78
79
unsigned int ItemManager::getEquipIdFromName(const std::string &name) const
80
{
81
    for (unsigned int i = 0; i < mEquipSlots.size(); ++i)
82
        if (name == mEquipSlots.at(i).first)
83
            return i;
84
    LOG_WARN("Item Manager: attempt to find equip id from name \"" <<
85
             name << "\" not found, defaulting to 0!");
86
    return 0;
87
}
88
89
unsigned int ItemManager::getMaxSlotsFromId(unsigned int id) const
90
{
91
    return mEquipSlots.at(id).second;
92
}
93
94
unsigned int ItemManager::getVisibleSlotCount() const
95
{
96
    if (!mVisibleEquipSlotCount)
97
    {
98
        for (VisibleEquipSlots::const_iterator it = mVisibleEquipSlots.begin(),
99
                                               it_end = mVisibleEquipSlots.end();
100
             it != it_end;
101
             ++it)
102
        {
103
            mVisibleEquipSlotCount += mEquipSlots.at(*it).second;
104
        }
105
    }
106
    return mVisibleEquipSlotCount;
107
}
108
109
bool ItemManager::isEquipSlotVisible(unsigned int id) const
110
{
111
    for (VisibleEquipSlots::const_iterator it = mVisibleEquipSlots.begin(),
112
                                           it_end = mVisibleEquipSlots.end();
113
         it != it_end;
114
         ++it)
115
    {
116
        if (*it == id)
117
            return true;
118
    }
119
    return false;
120
}
121
122
void ItemManager::readEquipSlotsFile()
123
{
124
    XML::Document doc(mEquipSlotsFile);
125
    xmlNodePtr rootNode = doc.rootNode();
126
127
    if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "equip-slots"))
128
    {
129
        LOG_ERROR("Item Manager: Error while parsing equip slots database ("
130
                  << mEquipSlotsFile << ")!");
131
        return;
132
    }
133
134
    LOG_INFO("Loading equip slots: " << mEquipSlotsFile);
135
136
    unsigned totalCount = 0;
137
    unsigned slotCount = 0;
138
    unsigned visibleSlotCount = 0;
139
140
    for_each_xml_child_node(node, rootNode)
141
    {
142
        if (xmlStrEqual(node->name, BAD_CAST "slot"))
143
        {
144
            const std::string name = XML::getProperty(node, "name",
145
                                                      std::string());
146
            const int count = XML::getProperty(node, "count", 0);
147
148
            if (name.empty() || count <= 0)
149
            {
150
                LOG_WARN("Item Manager: equip slot has no name or zero count");
151
            }
152
            else
153
            {
154
                bool visible = XML::getProperty(node, "visible", "false") != "false";
155
                if (visible)
156
                {
157
                    mVisibleEquipSlots.push_back(mEquipSlots.size());
158
                    if (++visibleSlotCount > 7)
159
                        LOG_WARN("Item Manager: More than 7 visible equip slot!"
160
                                 "This will not work with current netcode!");
161
                }
162
                mEquipSlots.push_back(std::pair<std::string, unsigned int>
163
                                     (name, count));
164
                totalCount += count;
165
                ++slotCount;
166
            }
167
        }
168
    }
169
170
    LOG_INFO("Loaded '" << slotCount << "' slot types with '"
171
             << totalCount << "' slots.");
172
}
173
174
void ItemManager::readItemsFile()
175
{
176
    XML::Document doc2(mItemsFile);
177
    xmlNodePtr rootNode = doc2.rootNode();
178
179
    if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "items"))
180
    {
181
        LOG_ERROR("Item Manager: Error while parsing item database ("
182
                  << mItemsFile << ")!");
183
        return;
184
    }
185
186
    LOG_INFO("Loading item reference: " << mItemsFile);
187
188
    for_each_xml_child_node(node, rootNode)
189
    {
190
        if (xmlStrEqual(node->name, BAD_CAST "item"))
191
        {
192
            readItemNode(node);
193
        }
194
    }
195
196
    LOG_INFO("Loaded " << mItemClasses.size() << " items from "
197
             << mItemsFile << ".");
198
}
199
200
void ItemManager::readItemNode(xmlNodePtr itemNode)
201
{
202
    const int id = XML::getProperty(itemNode, "id", 0);
203
    if (id < 1)
204
    {
205
        LOG_WARN("Item Manager: Item ID: " << id << " is invalid in "
206
                 << mItemsFile << ", and will be ignored.");
207
        return;
208
    }
209
210
    // Type is mostly unused, but still serves for hairsheets and race sheets
211
    const std::string type = XML::getProperty(itemNode, "type", std::string());
212
    if (type == "hairsprite" || type == "racesprite")
213
        return;
214
215
    ItemClasses::iterator i = mItemClasses.find(id);
216
217
    if (i != mItemClasses.end())
218
    {
219
        LOG_WARN("Item Manager: Ignoring duplicate definition of item '" << id
220
                 << "'!");
221
        return;
222
    }
223
224
    unsigned int maxPerSlot = XML::getProperty(itemNode, "max-per-slot", 0);
225
    if (!maxPerSlot)
226
    {
227
        LOG_WARN("Item Manager: Missing max-per-slot property for "
228
                 "item " << id << " in " << mItemsFile << '.');
229
        maxPerSlot = 1;
230
    }
231
232
    ItemClass *item = new ItemClass(id, maxPerSlot);
233
    mItemClasses.insert(std::make_pair(id, item));
234
235
    const std::string name = XML::getProperty(itemNode, "name", std::string());
236
    if (!name.empty())
237
    {
238
        item->setName(name);
239
240
        if (mItemClassesByName.contains(name))
241
            LOG_WARN("Item Manager: Name not unique for item " << id);
242
        else
243
            mItemClassesByName.insert(name, item);
244
    }
245
246
    int value = XML::getProperty(itemNode, "value", 0);
247
    // Should have multiple value definitions for multiple currencies?
248
    item->mCost = value;
249
250
    for_each_xml_child_node(subNode, itemNode)
251
    {
252
        if (xmlStrEqual(subNode->name, BAD_CAST "equip"))
253
        {
254
            readEquipNode(subNode, item);
255
        }
256
        else if (xmlStrEqual(subNode->name, BAD_CAST "effect"))
257
        {
258
            readEffectNode(subNode, item);
259
        }
260
        // More properties go here
261
    }
262
}
263
264
void ItemManager::readEquipNode(xmlNodePtr equipNode, ItemClass *item)
265
{
266
    ItemEquipInfo req;
267
    for_each_xml_child_node(subNode, equipNode)
268
    {
269
        if (xmlStrEqual(subNode->name, BAD_CAST "slot"))
270
        {
271
            std::string slot = XML::getProperty(subNode, "type", std::string());
272
            if (slot.empty())
273
            {
274
                LOG_WARN("Item Manager: empty equip slot definition!");
275
                continue;
276
            }
277
            req.push_back(std::make_pair(getEquipIdFromName(slot),
278
                           XML::getProperty(subNode, "required", 1)));
279
        }
280
    }
281
    if (req.empty())
282
    {
283
        LOG_WARN("Item Manager: empty equip requirement "
284
                 "definition for item " << item->getDatabaseID() << "!");
285
        return;
286
    }
287
    item->mEquip.push_back(req);
288
}
289
290
void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item)
291
{
292
    std::pair<ItemTriggerType, ItemTriggerType> triggerTypes;
293
    {
294
        const std::string triggerName = XML::getProperty(
295
                    effectNode, "trigger", std::string());
296
        const std::string dispellTrigger = XML::getProperty(
297
                    effectNode, "dispell", std::string());
298
        // label -> { trigger (apply), trigger (cancel (default)) }
299
        // The latter can be overridden.
300
        static std::map<const std::string,
301
                        std::pair<ItemTriggerType, ItemTriggerType> >
302
                        triggerTable;
303
        if (triggerTable.empty())
304
        {
305
            /*
306
             * The following is a table of all triggers for item
307
             *     effects.
308
             * The first element defines the trigger used for this
309
             *     trigger, and the second defines the default
310
             *     trigger to use for dispelling.
311
             */
312
            triggerTable["existence"].first         = ITT_IN_INVY;
313
            triggerTable["existence"].second        = ITT_LEAVE_INVY;
314
            triggerTable["activation"].first        = ITT_ACTIVATE;
315
            triggerTable["activation"].second       = ITT_NULL;
316
            triggerTable["equip"].first             = ITT_EQUIP;
317
            triggerTable["equip"].second            = ITT_UNEQUIP;
318
            triggerTable["leave-inventory"].first   = ITT_LEAVE_INVY;
319
            triggerTable["leave-inventory"].second  = ITT_NULL;
320
            triggerTable["unequip"].first           = ITT_UNEQUIP;
321
            triggerTable["unequip"].second          = ITT_NULL;
322
            triggerTable["equip-change"].first      = ITT_EQUIPCHG;
323
            triggerTable["equip-change"].second     = ITT_NULL;
324
            triggerTable["null"].first              = ITT_NULL;
325
            triggerTable["null"].second             = ITT_NULL;
326
        }
327
        std::map<const std::string, std::pair<ItemTriggerType,
328
                                    ItemTriggerType> >::iterator
329
                 it = triggerTable.find(triggerName);
330
331
        if (it == triggerTable.end()) {
332
            LOG_WARN("Item Manager: Unable to find effect trigger type \""
333
                     << triggerName << "\", skipping!");
334
            return;
335
        }
336
        triggerTypes = it->second;
337
        if (!dispellTrigger.empty())
338
        {
339
            if ((it = triggerTable.find(dispellTrigger)) == triggerTable.end())
340
                LOG_WARN("Item Manager: Unable to find dispell effect "
341
                         "trigger type \"" << dispellTrigger << "\"!");
342
            else
343
                triggerTypes.second = it->second.first;
344
        }
345
    }
346
347
    for_each_xml_child_node(subNode, effectNode)
348
    {
349
        if (xmlStrEqual(subNode->name, BAD_CAST "modifier"))
350
        {
351
            std::string tag = XML::getProperty(subNode, "attribute", std::string());
352
            if (tag.empty())
353
            {
354
                LOG_WARN("Item Manager: Warning, modifier found "
355
                         "but no attribute specified!");
356
                continue;
357
            }
358
            unsigned int duration = XML::getProperty(subNode,
359
                                                     "duration",
360
                                                     0);
361
            ModifierLocation location = attributeManager->getLocation(tag);
362
            double value = XML::getFloatProperty(subNode, "value", 0.0);
363
            item->addEffect(new ItemEffectAttrMod(location.attributeId,
364
                                                  location.layer,
365
                                                  value,
366
                                                  item->getDatabaseID(),
367
                                                  duration),
368
                            triggerTypes.first, triggerTypes.second);
369
        }
370
        else if (xmlStrEqual(subNode->name, BAD_CAST "autoattack"))
371
        {
372
            // TODO - URGENT
373
        }
374
        // Having a dispell for the next three is nonsensical.
375
        else if (xmlStrEqual(subNode->name, BAD_CAST "cooldown"))
376
        {
377
            LOG_WARN("Item Manager: Cooldown property not implemented yet!");
378
            // TODO: Also needs unique items before this action will work
379
        }
380
        else if (xmlStrEqual(subNode->name, BAD_CAST "g-cooldown"))
381
        {
382
            LOG_WARN("Item Manager: G-Cooldown property not implemented yet!");
383
            // TODO
384
        }
385
        else if (xmlStrEqual(subNode->name, BAD_CAST "consumes"))
386
        {
387
            item->addEffect(new ItemEffectConsumes(), triggerTypes.first);
388
        }
389
        else if (xmlStrEqual(subNode->name, BAD_CAST "script"))
390
        {
391
            std::string activateFunctionName = XML::getProperty(subNode,
392
                                                                "function",
393
                                                                std::string());
394
            if (activateFunctionName.empty())
395
            {
396
                LOG_WARN("Item Manager: Empty function definition "
397
                         "for script effect, skipping!");
398
                continue;
399
            }
400
401
            std::string src = XML::getProperty(subNode, "src", std::string());
402
            if (src.empty())
403
            {
404
                LOG_WARN("Item Manager: Empty src definition for script effect,"
405
                         " skipping!");
406
                continue;
407
            }
408
            std::stringstream filename;
409
            filename << "scripts/items/" << src;
410
            if (!ResourceManager::exists(filename.str()))
411
            {
412
                LOG_WARN("Could not find script file \"" << filename.str()
413
                         << "\" for item #" << item->mDatabaseID);
414
                continue;
415
            }
416
417
            LOG_INFO("Loading item script: " << filename.str());
418
            Script *script = Script::create("lua");
419
            if (!script->loadFile(filename.str()))
420
            {
421
                // Delete the script as it's invalid.
422
                delete script;
423
424
                LOG_WARN("Could not load script file \"" << filename.str()
425
                          << "\" for item #" << item->mDatabaseID);
426
                continue;
427
            }
428
429
            for_each_xml_child_node(scriptSubNode, subNode)
430
            {
431
                // TODO: Load variables from variable subnodes
432
            }
433
            std::string dispellFunctionName = XML::getProperty(subNode,
434
                                                             "dispell-function",
435
                                                             std::string());
436
437
            item->addEffect(new ItemEffectScript(item->mDatabaseID, script,
438
                                                 activateFunctionName,
439
                                                 dispellFunctionName),
440
                                                 triggerTypes.first,
441
                                                 triggerTypes.second);
442
        }
443
    }
444
}