GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/db/npcdb.cpp Lines: 8 87 9.2 %
Date: 2021-03-17 Branches: 0 188 0.0 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2008-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "resources/db/npcdb.h"
25
26
#include "configuration.h"
27
28
#include "resources/beingcommon.h"
29
#include "resources/beinginfo.h"
30
31
#include "resources/db/unitsdb.h"
32
33
#include "resources/sprite/spritereference.h"
34
35
#include "utils/cast.h"
36
#include "utils/checkutils.h"
37
#include "utils/dtor.h"
38
#include "utils/gettext.h"
39
40
#include "debug.h"
41
42
namespace
43
{
44
1
    BeingInfos mNPCInfos;
45
    bool mLoaded = false;
46
}  // namespace
47
48
void NPCDB::load()
49
{
50
    if (mLoaded)
51
        unload();
52
53
    logger->log1("Initializing NPC database...");
54
55
    loadXmlFile(paths.getStringValue("npcsFile"), SkipError_false);
56
    loadXmlFile(paths.getStringValue("npcsPatchFile"), SkipError_true);
57
    loadXmlDir("npcsPatchDir", loadXmlFile)
58
59
    mLoaded = true;
60
}
61
62
void NPCDB::loadXmlFile(const std::string &fileName,
63
                        const SkipError skipError)
64
{
65
    XML::Document doc(fileName, UseVirtFs_true, skipError);
66
    XmlNodeConstPtrConst rootNode = doc.rootNode();
67
68
    if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "npcs"))
69
    {
70
        logger->log("NPC Database: Error while loading %s!",
71
            paths.getStringValue("npcsFile").c_str());
72
        mLoaded = true;
73
        return;
74
    }
75
76
    // iterate <npc>s
77
    for_each_xml_child_node(npcNode, rootNode)
78
    {
79
        if (xmlNameEqual(npcNode, "include"))
80
        {
81
            const std::string name = XML::getProperty(npcNode, "name", "");
82
            if (!name.empty())
83
                loadXmlFile(name, skipError);
84
            continue;
85
        }
86
87
        if (!xmlNameEqual(npcNode, "npc"))
88
            continue;
89
90
        const BeingTypeId id = fromInt(XML::getProperty(
91
            npcNode, "id", 0), BeingTypeId);
92
        BeingInfo *currentInfo = nullptr;
93
        if (id == BeingTypeId_zero)
94
        {
95
            reportAlways("NPC Database: NPC with missing ID in %s!",
96
                paths.getStringValue("npcsFile").c_str())
97
            continue;
98
        }
99
        else if (mNPCInfos.find(id) != mNPCInfos.end())
100
        {
101
            logger->log("NpcDB: Redefinition of npc ID %d", toInt(id, int));
102
            currentInfo = mNPCInfos[id];
103
        }
104
        if (currentInfo == nullptr)
105
            currentInfo = new BeingInfo;
106
107
        currentInfo->setTargetSelection(XML::getBoolProperty(npcNode,
108
            "targetSelection", true));
109
110
        BeingCommon::readBasicAttributes(currentInfo, npcNode, "talk");
111
        BeingCommon::readWalkingAttributes(currentInfo, npcNode, 0);
112
113
        currentInfo->setDeadSortOffsetY(XML::getProperty(npcNode,
114
            "deadSortOffsetY", 31));
115
116
        currentInfo->setAvatarId(fromInt(XML::getProperty(
117
            npcNode, "avatar", 0), BeingTypeId));
118
119
        currentInfo->setAllowDelete(XML::getBoolProperty(npcNode,
120
            "allowDelete", true));
121
122
        currentInfo->setAllowEquipment(XML::getBoolProperty(npcNode,
123
            "allowEquipment", false));
124
125
        const std::string currency = XML::getProperty(npcNode,
126
            "currency", "default");
127
        if (UnitsDb::existsCurrency(currency) == false)
128
        {
129
            reportAlways("Not found currency '%s' for npc %d",
130
                currency.c_str(),
131
                CAST_S32(id))
132
        }
133
        currentInfo->setCurrency(currency);
134
135
        SpriteDisplay display;
136
        for_each_xml_child_node(spriteNode, npcNode)
137
        {
138
            if (xmlNameEqual(spriteNode, "sprite"))
139
            {
140
                if (!XmlHaveChildContent(spriteNode))
141
                    continue;
142
143
                SpriteReference *const currentSprite = new SpriteReference;
144
                currentSprite->sprite = XmlChildContent(spriteNode);
145
                currentSprite->variant =
146
                    XML::getProperty(spriteNode, "variant", 0);
147
                display.sprites.push_back(currentSprite);
148
            }
149
            else if (xmlNameEqual(spriteNode, "particlefx"))
150
            {
151
                if (!XmlHaveChildContent(spriteNode))
152
                    continue;
153
154
                display.particles.push_back(XmlChildContent(spriteNode));
155
            }
156
            else if (xmlNameEqual(spriteNode, "menu"))
157
            {
158
                std::string name = XML::langProperty(spriteNode, "name", "");
159
                std::string command = XML::getProperty(spriteNode,
160
                    "command", "");
161
                currentInfo->addMenu(name, command);
162
            }
163
        }
164
165
        currentInfo->setDisplay(display);
166
        if (currentInfo->getMenu().empty())
167
        {
168
            // TRANSLATORS: npc context menu item
169
            currentInfo->addMenu(_("Talk"), "talk 'NAME'");
170
            // TRANSLATORS: npc context menu item
171
            currentInfo->addMenu(_("Buy"), "buy 'NAME'");
172
            // TRANSLATORS: npc context menu item
173
            currentInfo->addMenu(_("Sell"), "sell 'NAME'");
174
        }
175
        mNPCInfos[id] = currentInfo;
176
    }
177
}
178
179
215
void NPCDB::unload()
180
{
181
215
    logger->log1("Unloading NPC database...");
182
215
    delete_all(mNPCInfos);
183
215
    mNPCInfos.clear();
184
185
215
    mLoaded = false;
186
215
}
187
188
BeingInfo *NPCDB::get(const BeingTypeId id)
189
{
190
    const BeingInfoIterator i = mNPCInfos.find(id);
191
192
    if (i == mNPCInfos.end())
193
    {
194
        reportAlways("NPCDB: Warning, unknown NPC ID %d requested",
195
            toInt(id, int))
196
        return BeingInfo::unknown;
197
    }
198
    return i->second;
199
}
200
201
BeingTypeId NPCDB::getAvatarFor(const BeingTypeId id)
202
{
203
    const BeingInfo *const info = get(id);
204
    if (info == nullptr)
205
        return BeingTypeId_zero;
206
    return info->getAvatarId();
207
2
}