1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2008  Aethyra Development Team
4  *  Copyright (C) 2011-2019  The ManaPlus Developers
5  *  Copyright (C) 2019-2021  Andrei Karas
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/colordb.h"
24 
25 #include "configuration.h"
26 
27 #include "utils/cast.h"
28 #include "utils/checkutils.h"
29 
30 #include "resources/beingcommon.h"
31 
32 #include "debug.h"
33 
34 namespace
35 {
36     int mHairColorsSize = 0;
37     bool mLoaded = false;
38     std::string mFail("#ffffff");
39     ColorDB::ColorLists mColorLists;
40 }  // namespace
41 
load()42 void ColorDB::load()
43 {
44     if (mLoaded)
45         unload();
46 
47     logger->log1("Initializing color database...");
48 
49     std::map<ItemColor, ItemColorData> colors;
50     ColorListsIterator it = mColorLists.find("hair");
51     if (it != mColorLists.end())
52         colors = it->second;
53     loadHair(paths.getStringValue("hairColorFile"),
54         colors,
55         SkipError_true);
56     loadHair(paths.getStringValue("hairColorPatchFile"),
57         colors,
58         SkipError_true);
59     StringVect list;
60     VirtFs::getFilesInDir(paths.getStringValue(
61         "hairColorPatchDir"), list, ".xml");
62     FOR_EACH (StringVectCIter, it2, list)
63         loadHair(*it2, colors, SkipError_true);
64 
65     mColorLists["hair"] = colors;
66 
67     loadColorLists(paths.getStringValue("itemColorsFile"),
68         SkipError_false);
69     loadColorLists(paths.getStringValue("itemColorsPatchFile"),
70         SkipError_true);
71     loadXmlDir("itemColorsPatchDir", loadColorLists)
72 
73     it = mColorLists.find("hair");
74     if (it != mColorLists.end())
75         mHairColorsSize = CAST_S32((*it).second.size());
76     else
77         mHairColorsSize = 0;
78     mLoaded = true;
79 }
80 
loadHair(const std::string & fileName,std::map<ItemColor,ItemColorData> & colors,const SkipError skipError)81 void ColorDB::loadHair(const std::string &fileName,
82                        std::map<ItemColor, ItemColorData> &colors,
83                        const SkipError skipError)
84 {
85     XML::Document *doc = new XML::Document(fileName,
86         UseVirtFs_true,
87         skipError);
88     XmlNodeConstPtrConst root = doc->rootNode();
89 
90     if ((root == nullptr) || !xmlNameEqual(root, "colors"))
91     {
92         logger->log("ColorDB: Failed to find hair colors file.");
93         if (colors.find(ItemColor_zero) == colors.end())
94             colors[ItemColor_zero] = ItemColorData(ItemColor_zero, "", "");
95         delete doc;
96         return;
97     }
98 
99     reportAlways("Found legacy hair.xml")
100     for_each_xml_child_node(node, root)
101     {
102         if (xmlNameEqual(node, "include"))
103         {
104             const std::string name = XML::getProperty(node, "name", "");
105             if (!name.empty())
106                 loadHair(name, colors, skipError);
107             continue;
108         }
109         else if (xmlNameEqual(node, "color"))
110         {
111             const ItemColor id = fromInt(XML::getProperty(
112                 node, "id", 0), ItemColor);
113 
114             if (colors.find(id) != colors.end())
115             {
116                 reportAlways("ColorDB: Redefinition of dye ID %d",
117                     toInt(id, int))
118             }
119 
120             colors[id] = ItemColorData(id, XML::langProperty(node, "name", ""),
121                 XML::getProperty(node, "value", "#FFFFFF"));
122         }
123     }
124 
125     delete doc;
126 }
127 
loadColorLists(const std::string & fileName,const SkipError skipError)128 void ColorDB::loadColorLists(const std::string &fileName,
129                              const SkipError skipError)
130 {
131     XML::Document *doc = new XML::Document(fileName,
132         UseVirtFs_true,
133         skipError);
134     XmlNodeConstPtrConst root = doc->rootNode();
135     if (root == nullptr)
136     {
137         delete doc;
138         return;
139     }
140 
141     for_each_xml_child_node(node, root)
142     {
143         if (xmlNameEqual(node, "include"))
144         {
145             const std::string name = XML::getProperty(node, "name", "");
146             if (!name.empty())
147                 loadColorLists(name, skipError);
148             continue;
149         }
150         else if (xmlNameEqual(node, "list"))
151         {
152             const std::string name = XML::getProperty(node, "name", "");
153             if (name.empty())
154                 continue;
155 
156             std::map <ItemColor, ItemColorData> colors;
157             const ColorListsIterator it = mColorLists.find(name);
158 
159             if (it != mColorLists.end())
160                 colors = it->second;
161 
162             for_each_xml_child_node(colorNode, node)
163             {
164                 if (xmlNameEqual(colorNode, "color"))
165                 {
166                     const int id = XML::getProperty(colorNode, "id", -1);
167                     if (id > -1)
168                     {
169                         ItemColorData c(fromInt(id, ItemColor),
170                             XML::langProperty(colorNode, "name", ""),
171                             XML::getProperty(colorNode, "value", ""));
172                             colors[c.id] = c;
173                     }
174                 }
175             }
176             mColorLists[name] = colors;
177         }
178     }
179     delete doc;
180 }
181 
unload()182 void ColorDB::unload()
183 {
184     logger->log1("Unloading color database...");
185 
186     mColorLists.clear();
187     mLoaded = false;
188 }
189 
getHairColorName(const ItemColor id)190 std::string &ColorDB::getHairColorName(const ItemColor id)
191 {
192     if (!mLoaded)
193         load();
194 
195     const ColorListsIterator it = mColorLists.find("hair");
196     if (it == mColorLists.end())
197     {
198         reportAlways("ColorDB: Error, hair colors list empty")
199         return mFail;
200     }
201 
202     const ColorIterator i = (*it).second.find(id);
203 
204     if (i == (*it).second.end())
205     {
206         reportAlways("ColorDB: Error, unknown dye ID# %d",
207             toInt(id, int))
208         return mFail;
209     }
210     return i->second.name;
211 }
212 
getHairSize()213 int ColorDB::getHairSize()
214 {
215     return mHairColorsSize;
216 }
217 
218 const std::map <ItemColor, ItemColorData>
getColorsList(const std::string & name)219      *ColorDB::getColorsList(const std::string &name)
220 {
221     const ColorListsIterator it = mColorLists.find(name);
222 
223     if (it != mColorLists.end())
224         return &it->second;
225     return nullptr;
226 }
227