1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "creatures.h"
24#include "creature.h"
25#include "map.h"
26
27#include <framework/xml/tinyxml.h>
28#include <framework/core/resourcemanager.h>
29
30CreatureManager g_creatures;
31
32static bool isInZone(const Position& pos/* placePos*/,
33 const Position& centerPos,
34 int radius)
35{
36 if(radius == -1)
37 return true;
38 return ((pos.x >= centerPos.x - radius) && (pos.x <= centerPos.x + radius) &&
39 (pos.y >= centerPos.y - radius) && (pos.y <= centerPos.y + radius)
40 );
41}
42
43void CreatureManager::terminate()
44{
45 clearSpawns();
46 clear();
47 m_nullCreature = nullptr;
48}
49
50void Spawn::load(TiXmlElement* node)
51{
52 Position centerPos;
53 centerPos.x = node->readType<int>("centerx");
54 centerPos.y = node->readType<int>("centery");
55 centerPos.z = node->readType<int>("centerz");
56
57 setCenterPos(centerPos);
58 setRadius(node->readType<int32>("radius"));
59
60 CreatureTypePtr cType(nullptr);
61 for(TiXmlElement* cNode = node->FirstChildElement(); cNode; cNode = cNode->NextSiblingElement()) {
62 if(cNode->ValueStr() != "monster" && cNode->ValueStr() != "npc")
63 stdext::throw_exception(stdext::format("invalid spawn-subnode %s", cNode->ValueStr()));
64
65 std::string cName = cNode->Attribute("name");
66 stdext::tolower(cName);
67 stdext::trim(cName);
68 stdext::ucwords(cName);
69
70 if (!(cType = g_creatures.getCreatureByName(cName)))
71 continue;
72
73 cType->setSpawnTime(cNode->readType<int>("spawntime"));
74 Otc::Direction dir = Otc::North;
75 int16 dir_ = cNode->readType<int16>("direction");
76 if(dir_ >= Otc::East && dir_ <= Otc::West)
77 dir = (Otc::Direction)dir_;
78 cType->setDirection(dir);
79
80 Position placePos;
81 placePos.x = centerPos.x + cNode->readType<int>("x");
82 placePos.y = centerPos.y + cNode->readType<int>("y");
83 placePos.z = cNode->readType<int>("z");
84
85 cType->setRace(cNode->ValueStr() == "npc" ? CreatureRaceNpc : CreatureRaceMonster);
86 addCreature(placePos, cType);
87 }
88}
89
90void Spawn::save(TiXmlElement* node)
91{
92 const Position& c = getCenterPos();
93 node->SetAttribute("centerx", c.x);
94 node->SetAttribute("centery", c.y);
95 node->SetAttribute("centerz", c.z);
96
97 node->SetAttribute("radius", getRadius());
98
99 TiXmlElement* creatureNode = nullptr;
100
101 for(const auto& pair : m_creatures) {
102 const CreatureTypePtr& creature = pair.second;
103 if(!(creatureNode = new TiXmlElement(creature->getRace() == CreatureRaceNpc ? "npc" : "monster")))
104 stdext::throw_exception("Spawn::save: Ran out of memory while allocating XML element! Terminating now.");
105
106 creatureNode->SetAttribute("name", creature->getName());
107 creatureNode->SetAttribute("spawntime", creature->getSpawnTime());
108 creatureNode->SetAttribute("direction", creature->getDirection());
109
110 const Position& placePos = pair.first;
111 VALIDATE(placePos.isValid());
112
113 creatureNode->SetAttribute("x", placePos.x - c.x);
114 creatureNode->SetAttribute("y", placePos.y - c.y);
115 creatureNode->SetAttribute("z", placePos.z);
116
117 node->LinkEndChild(creatureNode);
118 }
119}
120
121void Spawn::addCreature(const Position& placePos, const CreatureTypePtr& cType)
122{
123 const Position& centerPos = getCenterPos();
124 int m_radius = getRadius();
125 if(!isInZone(placePos, centerPos, m_radius)) {
126 g_logger.warning(stdext::format("cannot place creature at %s (spawn's center position: %s, spawn radius: %d) (increment radius)",
127 stdext::to_string(placePos), stdext::to_string(centerPos),
128 m_radius
129 ));
130 return;
131 }
132
133 g_map.addThing(cType->cast(), placePos, 4);
134 m_creatures.insert(std::make_pair(placePos, cType));
135}
136
137void Spawn::removeCreature(const Position& pos)
138{
139 auto iterator = m_creatures.find(pos);
140 if(iterator != m_creatures.end()) {
141 VALIDATE(iterator->first.isValid());
142 VALIDATE(g_map.removeThingByPos(iterator->first, 4));
143 m_creatures.erase(iterator);
144 }
145}
146
147std::vector<CreatureTypePtr> Spawn::getCreatures()
148{
149 std::vector<CreatureTypePtr> creatures;
150 for (auto p : m_creatures)
151 creatures.push_back(p.second);
152 return creatures;
153}
154
155CreaturePtr CreatureType::cast()
156{
157 CreaturePtr ret(new Creature);
158
159 std::string cName = getName();
160 stdext::tolower(cName);
161 stdext::trim(cName);
162 stdext::ucwords(cName);
163 ret->setName(cName);
164
165 ret->setDirection(getDirection());
166 ret->setOutfit(getOutfit());
167 return ret;
168}
169
170CreatureManager::CreatureManager()
171{
172 m_nullCreature = CreatureTypePtr(new CreatureType);
173}
174
175void CreatureManager::clearSpawns()
176{
177 for(auto pair : m_spawns)
178 pair.second->clear();
179 m_spawns.clear();
180}
181
182void CreatureManager::loadMonsters(const std::string& file)
183{
184 TiXmlDocument doc;
185 doc.Parse(g_resources.readFileContents(file).c_str());
186 if(doc.Error())
187 stdext::throw_exception(stdext::format("cannot open monsters file '%s': '%s'", file, doc.ErrorDesc()));
188
189 TiXmlElement* root = doc.FirstChildElement();
190 if(!root || root->ValueStr() != "monsters")
191 stdext::throw_exception("malformed monsters xml file");
192
193 for(TiXmlElement* monster = root->FirstChildElement(); monster; monster = monster->NextSiblingElement()) {
194 std::string fname = file.substr(0, file.find_last_of('/')) + '/' + monster->Attribute("file");
195 if(fname.substr(fname.length() - 4) != ".xml")
196 fname += ".xml";
197
198 loadSingleCreature(fname);
199 }
200
201 doc.Clear();
202 m_loaded = true;
203}
204
205void CreatureManager::loadSingleCreature(const std::string& file)
206{
207 loadCreatureBuffer(g_resources.readFileContents(file));
208}
209
210void CreatureManager::loadNpcs(const std::string& folder)
211{
212 std::string tmp = folder;
213 if(!stdext::ends_with(tmp, "/"))
214 tmp += "/";
215
216 if(!g_resources.directoryExists(tmp))
217 stdext::throw_exception(stdext::format("NPCs folder '%s' was not found.", folder));
218
219 const auto& fileList = g_resources.listDirectoryFiles(tmp);
220 for(const std::string& file : fileList)
221 loadCreatureBuffer(g_resources.readFileContents(tmp + file));
222}
223
224void CreatureManager::loadSpawns(const std::string& fileName)
225{
226 if(!isLoaded()) {
227 g_logger.warning("creatures aren't loaded yet to load spawns.");
228 return;
229 }
230
231 if(m_spawnLoaded) {
232 g_logger.warning("attempt to reload spawns.");
233 return;
234 }
235
236 try {
237 TiXmlDocument doc;
238 doc.Parse(g_resources.readFileContents(fileName).c_str());
239 if(doc.Error())
240 stdext::throw_exception(stdext::format("cannot load spawns xml file '%s: '%s'", fileName, doc.ErrorDesc()));
241
242 TiXmlElement* root = doc.FirstChildElement();
243 if(!root || root->ValueStr() != "spawns")
244 stdext::throw_exception("malformed spawns file");
245
246 for(TiXmlElement* node = root->FirstChildElement(); node; node = node->NextSiblingElement()) {
247 if(node->ValueTStr() != "spawn")
248 stdext::throw_exception("invalid spawn node");
249
250 SpawnPtr spawn(new Spawn);
251 spawn->load(node);
252 m_spawns.insert(std::make_pair(spawn->getCenterPos(), spawn));
253 }
254 doc.Clear();
255 m_spawnLoaded = true;
256 } catch(std::exception& e) {
257 g_logger.error(stdext::format("Failed to load '%s': %s", fileName, e.what()));
258 }
259}
260
261void CreatureManager::saveSpawns(const std::string& fileName)
262{
263 try {
264 TiXmlDocument doc;
265 doc.SetTabSize(2);
266
267 TiXmlDeclaration* decl = new TiXmlDeclaration("1.0", "UTF-8", "");
268 doc.LinkEndChild(decl);
269
270 TiXmlElement* root = new TiXmlElement("spawns");
271 doc.LinkEndChild(root);
272
273 for(auto pair : m_spawns) {
274 TiXmlElement* elem = new TiXmlElement("spawn");
275 pair.second->save(elem);
276 root->LinkEndChild(elem);
277 }
278
279 if(!doc.SaveFile("data"+fileName))
280 stdext::throw_exception(stdext::format("failed to save spawns XML %s: %s", fileName, doc.ErrorDesc()));
281 } catch(std::exception& e) {
282 g_logger.error(stdext::format("Failed to save '%s': %s", fileName, e.what()));
283 }
284}
285
286void CreatureManager::loadCreatureBuffer(const std::string& buffer)
287{
288 TiXmlDocument doc;
289 doc.Parse(buffer.c_str());
290 if(doc.Error())
291 stdext::throw_exception(stdext::format("cannot load creature buffer: %s", doc.ErrorDesc()));
292
293 TiXmlElement* root = doc.FirstChildElement();
294 if(!root || (root->ValueStr() != "monster" && root->ValueStr() != "npc"))
295 stdext::throw_exception("invalid root tag name");
296
297 std::string cName = root->Attribute("name");
298 stdext::tolower(cName);
299 stdext::trim(cName);
300 stdext::ucwords(cName);
301
302 CreatureTypePtr newType(new CreatureType(cName));
303 for(TiXmlElement* attrib = root->FirstChildElement(); attrib; attrib = attrib->NextSiblingElement()) {
304 if(attrib->ValueStr() != "look")
305 continue;
306
307 internalLoadCreatureBuffer(attrib, newType);
308 break;
309 }
310
311 doc.Clear();
312}
313
314void CreatureManager::internalLoadCreatureBuffer(TiXmlElement* attrib, const CreatureTypePtr& m)
315{
316 if(std::find(m_creatures.begin(), m_creatures.end(), m) != m_creatures.end())
317 return;
318
319 Outfit out;
320
321 int32 type = attrib->readType<int32>("type");
322 if(type > 0) {
323 out.setCategory(ThingCategoryCreature);
324 out.setId(type);
325 } else {
326 out.setCategory(ThingCategoryItem);
327 out.setAuxId(attrib->readType<int32>("typeex"));
328 }
329
330 {
331 out.setHead(attrib->readType<int>(("head")));
332 out.setBody(attrib->readType<int>(("body")));
333 out.setLegs(attrib->readType<int>(("legs")));
334 out.setFeet(attrib->readType<int>(("feet")));
335 out.setAddons(attrib->readType<int>(("addons")));
336 out.setMount(attrib->readType<int>(("mount")));
337 }
338
339 m->setOutfit(out);
340 m_creatures.push_back(m);
341}
342
343const CreatureTypePtr& CreatureManager::getCreatureByName(std::string name)
344{
345 stdext::tolower(name);
346 stdext::trim(name);
347 stdext::ucwords(name);
348 auto it = std::find_if(m_creatures.begin(), m_creatures.end(),
349 [=] (const CreatureTypePtr& m) -> bool { return m->getName() == name; });
350 if(it != m_creatures.end())
351 return *it;
352 g_logger.warning(stdext::format("could not find creature with name: %s", name));
353 return m_nullCreature;
354}
355
356const CreatureTypePtr& CreatureManager::getCreatureByLook(int look)
357{
358 auto findFun = [=] (const CreatureTypePtr& c) -> bool
359 {
360 const Outfit& o = c->getOutfit();
361 return o.getId() == look || o.getAuxId() == look;
362 };
363 auto it = std::find_if(m_creatures.begin(), m_creatures.end(), findFun);
364 if(it != m_creatures.end())
365 return *it;
366 g_logger.warning(stdext::format("could not find creature with looktype: %d", look));
367 return m_nullCreature;
368}
369
370SpawnPtr CreatureManager::getSpawn(const Position& centerPos)
371{
372 auto it = m_spawns.find(centerPos);
373 if(it != m_spawns.end())
374 return it->second;
375 g_logger.debug(stdext::format("failed to find spawn at center %s",stdext::to_string(centerPos)));
376 return nullptr;
377}
378
379SpawnPtr CreatureManager::getSpawnForPlacePos(const Position& pos)
380{
381 for (const auto& pair : m_spawns) {
382 const Position& centerPos = pair.first;
383 const SpawnPtr& spawn = pair.second;
384
385 if (isInZone(pos, centerPos, spawn->getRadius()))
386 return spawn;
387 }
388
389 return nullptr;
390}
391
392SpawnPtr CreatureManager::addSpawn(const Position& centerPos, int radius)
393{
394 auto iter = m_spawns.find(centerPos);
395 if(iter != m_spawns.end()) {
396 if(iter->second->getRadius() != radius)
397 iter->second->setRadius(radius);
398 return iter->second;
399 }
400
401 SpawnPtr ret(new Spawn);
402
403 ret->setRadius(radius);
404 ret->setCenterPos(centerPos);
405
406 m_spawns.insert(std::make_pair(centerPos, ret));
407 return ret;
408}
409
410void CreatureManager::deleteSpawn(const SpawnPtr& spawn)
411{
412 const Position& centerPos = spawn->getCenterPos();
413 auto it = m_spawns.find(centerPos);
414 if(it != m_spawns.end())
415 m_spawns.erase(it);
416}
417
418std::vector<SpawnPtr> CreatureManager::getSpawns()
419{
420 std::vector<SpawnPtr> spawns;
421 for (auto p : m_spawns)
422 spawns.push_back(p.second);
423 return spawns;
424}
425
426/* vim: set ts=4 sw=4 et: */
427