GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/resources/db/unitsdb.cpp Lines: 77 160 48.1 %
Date: 2021-03-17 Branches: 79 266 29.7 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 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/unitsdb.h"
25
26
#include "configuration.h"
27
28
#include "const/resources/currency.h"
29
30
#include "utils/cast.h"
31
#include "utils/checkutils.h"
32
#include "utils/stdmove.h"
33
34
#include "resources/beingcommon.h"
35
36
#include <climits>
37
38
#include "debug.h"
39
40
namespace
41
{
42
11595
    struct UnitLevel final
43
    {
44
        A_DEFAULT_COPY(UnitLevel)
45
46
        std::string symbol;
47
        int count;
48
        int round;
49
        std::string separator;
50
    };
51
52



1546
    struct UnitDescription final
53
    {
54
        A_DEFAULT_COPY(UnitDescription)
55
56
        STD_VECTOR<UnitLevel> levels;
57
        double conversion;
58
        bool mix;
59
    };
60
61
1
    UnitDescription defaultCurrency;
62
1
    UnitDescription defaultWeight;
63
64
1
    std::map<std::string, UnitDescription> mCurrencies;
65
}  // namespace
66
67
static std::string formatUnit(const int value,
68
                              const UnitDescription &ud);
69
70
static std::string splitNumber(std::string str,
71
                               const std::string &separator);
72
73
77
void UnitsDb::load()
74
{
75
77
    logger->log1("Initializing unit database...");
76
    {   // Setup default weight
77
154
        UnitDescription ud;
78
79
77
        ud.conversion = 1.0;
80
77
        ud.mix = false;
81
82
        const UnitLevel bu =
83
        {
84
            "g",
85
            1,
86
            0,
87
            ""
88

462
        };
89
77
        ud.levels.push_back(bu);
90
91
        const UnitLevel ul =
92
        {
93
            "kg",
94
            1000,
95
            2,
96
            ""
97

462
        };
98
77
        ud.levels.push_back(ul);
99
100
77
        defaultWeight = ud;
101
    }
102
103
    {   // Setup default currency
104
154
        UnitDescription ud;
105
106
77
        ud.conversion = 1.0;
107
77
        ud.mix = false;
108
109

462
        const UnitLevel bu = {"¤", 1, 0, ""};
110
77
        ud.levels.push_back(bu);
111
112
77
        defaultCurrency = ud;
113
    }
114
115

385
    loadXmlFile(paths.getStringValue("unitsFile"), SkipError_false);
116

385
    loadXmlFile(paths.getStringValue("unitsPatchFile"), SkipError_true);
117



1078
    loadXmlDir("unitsPatchDir", loadXmlFile)
118
77
}
119
120
281
void UnitsDb::unload()
121
{
122
281
    logger->log1("Unloading unit database...");
123
281
    mCurrencies.clear();
124
281
}
125
126
154
static UnitDescription loadUnit(XmlNodePtr node)
127
{
128
154
    UnitDescription ud;
129
154
    int level = 1;
130
154
    ud.conversion = XML::getProperty(node, "conversion", 1);
131

770
    ud.mix = XML::getProperty(node, "mix", "no") == "yes";
132
133
308
    UnitLevel bu;
134

770
    bu.symbol = XML::getProperty(node, "base", "¤");
135
154
    bu.count = 1;
136
154
    bu.round = XML::getProperty(node, "round", 2);
137

770
    bu.separator = XML::getProperty(node, "separator", " ");
138
139
154
    ud.levels.push_back(bu);
140
141
385
    for_each_xml_child_node(uLevel, node)
142
    {
143

231
        if (xmlNameEqual(uLevel, "level"))
144
        {
145
            const UnitLevel ul =
146
            {
147
                XML::getProperty(uLevel, "symbol",
148
154
                                 strprintf("¤%d", level)),
149
77
                XML::getProperty(uLevel, "count", -1),
150
77
                XML::getProperty(uLevel, "round", bu.round),
151
                XML::getProperty(uLevel, "separator", bu.separator)
152

308
            };
153
154
77
            if (ul.count > 0)
155
            {
156
77
                ud.levels.push_back(ul);
157
77
                level++;
158
            }
159
            else
160
            {
161
                logger->log("Error bad unit count: %d for %s in %s",
162
                    ul.count,
163
                    ul.symbol.c_str(),
164
                    bu.symbol.c_str());
165
            }
166
        }
167
    }
168
169
    // Add one more level for saftey
170
    const UnitLevel lev =
171
    {
172
        "",
173
        INT_MAX,
174
        0,
175
        ""
176

924
    };
177
154
    ud.levels.push_back(lev);
178
154
    return ud;
179
}
180
181
static void loadCurrencies(XmlNodePtr parentNode)
182
{
183
    for_each_xml_child_node(node, parentNode)
184
    {
185
        if (xmlNameEqual(node, "unit"))
186
        {
187
            const std::string name = XML::getProperty(node, "name", "");
188
            if (name.empty())
189
            {
190
                reportAlways("Error: unknown currency name.")
191
                continue;
192
            }
193
            mCurrencies[name] = loadUnit(node);
194
            if (name == DEFAULT_CURRENCY)
195
                defaultCurrency = mCurrencies[name];
196
        }
197
    }
198
}
199
200
154
void UnitsDb::loadXmlFile(const std::string &fileName,
201
                          const SkipError skipError)
202
{
203
231
    XML::Document doc(fileName, UseVirtFs_true, skipError);
204
154
    XmlNodeConstPtrConst root = doc.rootNode();
205
206


154
    if ((root == nullptr) || !xmlNameEqual(root, "units"))
207
    {
208
77
        logger->log("Error loading unit definition file: "
209


539
            + paths.getStringValue("unitsFile"));
210
77
        return;
211
    }
212
213
462
    for_each_xml_child_node(node, root)
214
    {
215

385
        if (xmlNameEqual(node, "include"))
216
        {
217
            const std::string name = XML::getProperty(node, "name", "");
218
            if (!name.empty())
219
                loadXmlFile(name, skipError);
220
            continue;
221
        }
222

385
        else if (xmlNameEqual(node, "unit"))
223
        {
224

770
            const std::string type = XML::getProperty(node, "type", "");
225
308
            UnitDescription ud = loadUnit(node);
226
154
            if (type == "weight")
227
            {
228
                defaultWeight = ud;
229
            }
230
77
            else if (type == "currency")
231
            {
232
77
                defaultCurrency = ud;
233
77
                mCurrencies[DEFAULT_CURRENCY] = ud;
234
            }
235
            else
236
            {
237
                logger->log("Error unknown unit type: %s", type.c_str());
238
            }
239
        }
240

231
        else if (xmlNameEqual(node, "currency"))
241
        {
242
            loadCurrencies(node);
243
        }
244
    }
245
}
246
247
3
static std::string formatUnit(const int value,
248
                              const UnitDescription &ud)
249
{
250
6
    UnitLevel ul;
251
252
    // Shortcut for 0; do the same for values less than 0  (for now)
253
3
    if (value <= 0)
254
    {
255
3
        ul = ud.levels[0];
256
3
        return strprintf("0%s", ul.symbol.c_str());
257
    }
258
259
    double amount = ud.conversion * value;
260
    const unsigned int sz = CAST_U32(ud.levels.size());
261
262
    // If only the first level is needed, act like mix if false
263
    if (ud.mix && !ud.levels.empty() && ud.levels[1].count < amount)
264
    {
265
        std::string output;
266
        UnitLevel pl = ud.levels[0];
267
        ul = ud.levels[1];
268
        int levelAmount = CAST_S32(amount);
269
        int nextAmount = 0;
270
271
        if (ul.count != 0)
272
            levelAmount /= ul.count;
273
274
        amount -= static_cast<double>(levelAmount * ul.count);
275
276
        if (amount > 0)
277
        {
278
            output = splitNumber(strprintf("%.*f", pl.round,
279
                amount), pl.separator).append(pl.symbol);
280
        }
281
282
        for (unsigned int i = 2; i < sz; i++)
283
        {
284
            pl = ul;
285
            ul = ud.levels[i];
286
287
            if (ul.count != 0)
288
            {
289
                nextAmount = levelAmount / ul.count;
290
                levelAmount %= ul.count;
291
            }
292
293
            if (levelAmount > 0)
294
            {
295
                output = splitNumber(strprintf("%d", levelAmount),
296
                    pl.separator).append(pl.symbol).append(output);
297
            }
298
299
            if (nextAmount == 0)
300
                break;
301
            levelAmount = nextAmount;
302
        }
303
304
        return output;
305
    }
306
307
    ul.round = 0;
308
    for (unsigned int i = 0; i < sz; i++)
309
    {
310
        ul = ud.levels[i];
311
        if (amount < ul.count && ul.count > 0)
312
        {
313
            ul = ud.levels[i - 1];
314
            break;
315
        }
316
        if (ul.count != 0)
317
            amount /= ul.count;
318
    }
319
320
    return splitNumber(strprintf("%.*f", ul.round, amount),
321
        ul.separator).append(ul.symbol);
322
}
323
324
3
std::string UnitsDb::formatCurrency(const int value)
325
{
326
3
    return formatUnit(value, defaultCurrency);
327
}
328
329
std::string UnitsDb::formatCurrency64(const int64_t value)
330
{
331
    return formatUnit(CAST_S32(value),
332
        defaultCurrency);
333
}
334
335
std::string UnitsDb::formatCurrency(std::string name,
336
                                    const int value)
337
{
338
    if (mCurrencies.find(name) == mCurrencies.end())
339
        name = DEFAULT_CURRENCY;
340
    return formatUnit(value, mCurrencies[name]);
341
}
342
343
std::string UnitsDb::formatCurrency64(std::string name,
344
                                      const int64_t value)
345
{
346
    if (mCurrencies.find(name) == mCurrencies.end())
347
        name = DEFAULT_CURRENCY;
348
    return formatUnit(CAST_S32(value),
349
        mCurrencies[name]);
350
}
351
352
std::string UnitsDb::formatWeight(const int value)
353
{
354
    return formatUnit(value, defaultWeight);
355
}
356
357
bool UnitsDb::existsCurrency(const std::string &name)
358
{
359
    return mCurrencies.find(name) != mCurrencies.end();
360
}
361
362
static std::string splitNumber(std::string str,
363
                               const std::string &separator)
364
{
365
    std::string lastPart;
366
    const size_t point = str.find('.');
367
    if (point != std::string::npos)
368
    {
369
        lastPart = str.substr(point);
370
        str = str.substr(0, point);
371
    }
372
    std::string result;
373
374
    if (!str.empty())
375
    {
376
        size_t sz = str.size();
377
        while (sz >= 3)
378
        {
379
            if (sz >= 6)
380
            {
381
                result = std::string(separator).append(str.substr(
382
                    sz - 3)).append(result);
383
            }
384
            else
385
            {
386
                result = str.substr(sz - 3).append(result);
387
            }
388
            str = str.substr(0, str.size() - 3);
389
            sz = str.size();
390
        }
391
        if (!str.empty())
392
        {
393
            if (!result.empty())
394
                result = std::string(str).append(separator).append(result);
395
            else
396
                result = STD_MOVE(str);
397
        }
398
    }
399
400
    return result + lastPart;
401

3
}