1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "combat.h"
23#include "configmanager.h"
24#include "game.h"
25#include "monster.h"
26#include "monsters.h"
27#include "spells.h"
28#include "weapons.h"
29
30#include "pugicast.h"
31
32extern Game g_game;
33extern Spells *g_spells;
34extern Monsters g_monsters;
35extern ConfigManager g_config;
36
37spellBlock_t::~spellBlock_t() {
38 if (combatSpell) {
39 delete spell;
40 }
41}
42
43void MonsterType::loadLoot(MonsterType *monsterType, LootBlock lootBlock) {
44 if (lootBlock.childLoot.empty()) {
45 bool isContainer = Item::items[lootBlock.id].isContainer();
46 if (isContainer) {
47 for (LootBlock child : lootBlock.childLoot) {
48 lootBlock.childLoot.push_back(child);
49 }
50 }
51 monsterType->info.lootItems.push_back(lootBlock);
52 } else {
53 monsterType->info.lootItems.push_back(lootBlock);
54 }
55}
56
57bool Monsters::loadFromXml(bool reloading /*= false*/) {
58 unloadedMonsters = {};
59 pugi::xml_document doc;
60 pugi::xml_parse_result result = doc.load_file("data/monster/monsters.xml");
61 if (!result) {
62 printXMLError("Error - Monsters::loadFromXml", "data/monster/monsters.xml",
63 result);
64 return false;
65 }
66
67 loaded = true;
68
69 for (auto monsterNode : doc.child("monsters").children()) {
70 std::string name =
71 asLowerCaseString(monsterNode.attribute("name").as_string());
72 std::string file = "data/monster/" +
73 std::string(monsterNode.attribute("file").as_string());
74 auto forceLoad = g_config.getBoolean(ConfigManager::FORCE_MONSTERTYPE_LOAD);
75 if (forceLoad) {
76 loadMonster(file, name, true);
77 continue;
78 }
79
80 if (reloading && monsters.find(name) != monsters.end()) {
81 loadMonster(file, name, true);
82 } else {
83 unloadedMonsters.emplace(name, file);
84 }
85 }
86 return true;
87}
88
89bool Monsters::reload() {
90 loaded = false;
91
92 scriptInterface.reset();
93
94 return loadFromXml(true);
95}
96
97ConditionDamage *Monsters::getDamageCondition(ConditionType_t conditionType,
98 int32_t maxDamage,
99 int32_t minDamage,
100 int32_t startDamage,
101 uint32_t tickInterval) {
102 ConditionDamage *condition = static_cast<ConditionDamage *>(
103 Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0));
104 condition->setParam(CONDITION_PARAM_TICKINTERVAL, tickInterval);
105 condition->setParam(CONDITION_PARAM_MINVALUE, minDamage);
106 condition->setParam(CONDITION_PARAM_MAXVALUE, maxDamage);
107 condition->setParam(CONDITION_PARAM_STARTVALUE, startDamage);
108 condition->setParam(CONDITION_PARAM_DELAYED, 1);
109 return condition;
110}
111
112bool Monsters::deserializeSpell(const pugi::xml_node &node, spellBlock_t &sb,
113 const std::string &description) {
114 std::string name;
115 std::string scriptName;
116 bool isScripted;
117
118 pugi::xml_attribute attr;
119 if ((attr = node.attribute("script"))) {
120 scriptName = attr.as_string();
121 isScripted = true;
122 } else if ((attr = node.attribute("name"))) {
123 name = attr.as_string();
124 isScripted = false;
125 } else {
126 return false;
127 }
128
129 if ((attr = node.attribute("speed")) || (attr = node.attribute("interval"))) {
130 sb.speed = std::max<int32_t>(1, pugi::cast<int32_t>(attr.value()));
131 }
132
133 if ((attr = node.attribute("chance"))) {
134 uint32_t chance = pugi::cast<uint32_t>(attr.value());
135 if (chance > 100) {
136 chance = 100;
137 }
138 sb.chance = chance;
139 }
140
141 if ((attr = node.attribute("range"))) {
142 uint32_t range = pugi::cast<uint32_t>(attr.value());
143 if (range > (Map::maxViewportX * 2)) {
144 range = Map::maxViewportX * 2;
145 }
146 sb.range = range;
147 }
148
149 if ((attr = node.attribute("min"))) {
150 sb.minCombatValue = pugi::cast<int32_t>(attr.value());
151 }
152
153 if ((attr = node.attribute("max"))) {
154 sb.maxCombatValue = pugi::cast<int32_t>(attr.value());
155
156 // normalize values
157 if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) {
158 int32_t value = sb.maxCombatValue;
159 sb.maxCombatValue = sb.minCombatValue;
160 sb.minCombatValue = value;
161 }
162 }
163
164 if (auto spell = g_spells->getSpellByName(name)) {
165 sb.spell = spell;
166 return true;
167 }
168
169 CombatSpell *combatSpell = nullptr;
170 bool needTarget = false;
171 bool needDirection = false;
172
173 if (isScripted) {
174 if ((attr = node.attribute("direction"))) {
175 needDirection = attr.as_bool();
176 }
177
178 if ((attr = node.attribute("target"))) {
179 needTarget = attr.as_bool();
180 }
181
182 std::unique_ptr<CombatSpell> combatSpellPtr(
183 new CombatSpell(nullptr, needTarget, needDirection));
184 if (!combatSpellPtr->loadScript("data/" + g_spells->getScriptBaseName() +
185 "/scripts/" + scriptName)) {
186 return false;
187 }
188
189 if (!combatSpellPtr->loadScriptCombat()) {
190 return false;
191 }
192
193 combatSpell = combatSpellPtr.release();
194 combatSpell->getCombat()->setPlayerCombatValues(
195 COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0);
196 } else {
197 Combat *combat = new Combat;
198 if ((attr = node.attribute("length"))) {
199 int32_t length = pugi::cast<int32_t>(attr.value());
200 if (length > 0) {
201 int32_t spread = 3;
202
203 // need direction spell
204 if ((attr = node.attribute("spread"))) {
205 spread = std::max<int32_t>(0, pugi::cast<int32_t>(attr.value()));
206 }
207
208 AreaCombat *area = new AreaCombat();
209 area->setupArea(length, spread);
210 combat->setArea(area);
211
212 needDirection = true;
213 }
214 }
215
216 if ((attr = node.attribute("radius"))) {
217 int32_t radius = pugi::cast<int32_t>(attr.value());
218
219 // target spell
220 if ((attr = node.attribute("target"))) {
221 needTarget = attr.as_bool();
222 }
223
224 AreaCombat *area = new AreaCombat();
225 area->setupArea(radius);
226 combat->setArea(area);
227 }
228
229 std::string tmpName = asLowerCaseString(name);
230
231 if (tmpName == "melee") {
232 sb.isMelee = true;
233
234 pugi::xml_attribute attackAttribute, skillAttribute;
235 if ((attackAttribute = node.attribute("attack")) &&
236 (skillAttribute = node.attribute("skill"))) {
237 sb.minCombatValue = 0;
238 sb.maxCombatValue = -Weapons::getMaxMeleeDamage(
239 pugi::cast<int32_t>(skillAttribute.value()),
240 pugi::cast<int32_t>(attackAttribute.value()));
241 }
242
243 ConditionType_t conditionType = CONDITION_NONE;
244 int32_t minDamage = 0;
245 int32_t maxDamage = 0;
246 uint32_t tickInterval = 2000;
247
248 if ((attr = node.attribute("fire"))) {
249 conditionType = CONDITION_FIRE;
250
251 minDamage = pugi::cast<int32_t>(attr.value());
252 maxDamage = minDamage;
253 tickInterval = 9000;
254 } else if ((attr = node.attribute("poison"))) {
255 conditionType = CONDITION_POISON;
256
257 minDamage = pugi::cast<int32_t>(attr.value());
258 maxDamage = minDamage;
259 tickInterval = 4000;
260 } else if ((attr = node.attribute("energy"))) {
261 conditionType = CONDITION_ENERGY;
262
263 minDamage = pugi::cast<int32_t>(attr.value());
264 maxDamage = minDamage;
265 tickInterval = 10000;
266 } else if ((attr = node.attribute("drown"))) {
267 conditionType = CONDITION_DROWN;
268
269 minDamage = pugi::cast<int32_t>(attr.value());
270 maxDamage = minDamage;
271 tickInterval = 5000;
272 } else if ((attr = node.attribute("freeze"))) {
273 conditionType = CONDITION_FREEZING;
274
275 minDamage = pugi::cast<int32_t>(attr.value());
276 maxDamage = minDamage;
277 tickInterval = 8000;
278 } else if ((attr = node.attribute("dazzle"))) {
279 conditionType = CONDITION_DAZZLED;
280
281 minDamage = pugi::cast<int32_t>(attr.value());
282 maxDamage = minDamage;
283 tickInterval = 10000;
284 } else if ((attr = node.attribute("curse"))) {
285 conditionType = CONDITION_CURSED;
286
287 minDamage = pugi::cast<int32_t>(attr.value());
288 maxDamage = minDamage;
289 tickInterval = 4000;
290 } else if ((attr = node.attribute("bleed")) ||
291 (attr = node.attribute("physical"))) {
292 conditionType = CONDITION_BLEEDING;
293 tickInterval = 4000;
294 }
295
296 if ((attr = node.attribute("tick"))) {
297 int32_t value = pugi::cast<int32_t>(attr.value());
298 if (value > 0) {
299 tickInterval = value;
300 }
301 }
302
303 if (conditionType != CONDITION_NONE) {
304 Condition *condition = getDamageCondition(conditionType, maxDamage,
305 minDamage, 0, tickInterval);
306 combat->addCondition(condition);
307 }
308
309 sb.range = 1;
310 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE);
311 combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1);
312 combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1);
313 combat->setOrigin(ORIGIN_MELEE);
314 } else if (tmpName == "physical") {
315 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE);
316 combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1);
317 combat->setOrigin(ORIGIN_RANGED);
318 } else if (tmpName == "bleed") {
319 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE);
320 } else if (tmpName == "poison" || tmpName == "earth") {
321 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE);
322 } else if (tmpName == "fire") {
323 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE);
324 } else if (tmpName == "energy") {
325 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE);
326 } else if (tmpName == "drown") {
327 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_DROWNDAMAGE);
328 } else if (tmpName == "ice") {
329 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ICEDAMAGE);
330 } else if (tmpName == "holy") {
331 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_HOLYDAMAGE);
332 } else if (tmpName == "death") {
333 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_DEATHDAMAGE);
334 } else if (tmpName == "lifedrain") {
335 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_LIFEDRAIN);
336 } else if (tmpName == "manadrain") {
337 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_MANADRAIN);
338 } else if (tmpName == "healing") {
339 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_HEALING);
340 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
341 } else if (tmpName == "speed") {
342 int32_t minSpeedChange = 0;
343 int32_t maxSpeedChange = 0;
344 int32_t duration = 10000;
345
346 if ((attr = node.attribute("duration"))) {
347 duration = pugi::cast<int32_t>(attr.value());
348 }
349
350 if ((attr = node.attribute("speedchange"))) {
351 minSpeedChange = pugi::cast<int32_t>(attr.value());
352 } else if ((attr = node.attribute("minspeedchange"))) {
353 minSpeedChange = pugi::cast<int32_t>(attr.value());
354
355 if ((attr = node.attribute("maxspeedchange"))) {
356 maxSpeedChange = pugi::cast<int32_t>(attr.value());
357 }
358
359 if (minSpeedChange == 0) {
360 std::cout << "[Error - Monsters::deserializeSpell] - " << description
361 << " - missing speedchange/minspeedchange value"
362 << std::endl;
363 delete combat;
364 return false;
365 }
366
367 if (minSpeedChange < -1000) {
368 std::cout
369 << "[Warning - Monsters::deserializeSpell] - " << description
370 << " - you cannot reduce a creatures speed below -1000 (100%)"
371 << std::endl;
372 minSpeedChange = -1000;
373 }
374
375 if (maxSpeedChange == 0) {
376 maxSpeedChange = minSpeedChange; // static speedchange value
377 }
378 }
379
380 ConditionType_t conditionType;
381 if (minSpeedChange >= 0) {
382 conditionType = CONDITION_HASTE;
383 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
384 } else {
385 conditionType = CONDITION_PARALYZE;
386 }
387
388 ConditionSpeed *condition =
389 static_cast<ConditionSpeed *>(Condition::createCondition(
390 CONDITIONID_COMBAT, conditionType, duration, 0));
391 condition->setFormulaVars(minSpeedChange / 1000.0, 0,
392 maxSpeedChange / 1000.0, 0);
393 combat->addCondition(condition);
394 } else if (tmpName == "outfit") {
395 int32_t duration = 10000;
396
397 if ((attr = node.attribute("duration"))) {
398 duration = pugi::cast<int32_t>(attr.value());
399 }
400
401 if ((attr = node.attribute("monster"))) {
402 MonsterType *mType = g_monsters.getMonsterType(attr.as_string());
403 if (mType) {
404 ConditionOutfit *condition =
405 static_cast<ConditionOutfit *>(Condition::createCondition(
406 CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0));
407 condition->setOutfit(mType->info.outfit);
408 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
409 combat->addCondition(condition);
410 }
411 } else if ((attr = node.attribute("item"))) {
412 Outfit_t outfit;
413 outfit.lookTypeEx = pugi::cast<uint16_t>(attr.value());
414
415 ConditionOutfit *condition =
416 static_cast<ConditionOutfit *>(Condition::createCondition(
417 CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0));
418 condition->setOutfit(outfit);
419 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
420 combat->addCondition(condition);
421 }
422 } else if (tmpName == "invisible") {
423 int32_t duration = 10000;
424
425 if ((attr = node.attribute("duration"))) {
426 duration = pugi::cast<int32_t>(attr.value());
427 }
428
429 Condition *condition = Condition::createCondition(
430 CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0);
431 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
432 combat->addCondition(condition);
433 } else if (tmpName == "drunk") {
434 int32_t duration = 10000;
435
436 if ((attr = node.attribute("duration"))) {
437 duration = pugi::cast<int32_t>(attr.value());
438 }
439
440 Condition *condition = Condition::createCondition(
441 CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0);
442 combat->addCondition(condition);
443 } else if (tmpName == "firefield") {
444 combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL);
445 } else if (tmpName == "poisonfield") {
446 combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP);
447 } else if (tmpName == "energyfield") {
448 combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP);
449 } else if (tmpName == "firecondition" || tmpName == "energycondition" ||
450 tmpName == "earthcondition" || tmpName == "poisoncondition" ||
451 tmpName == "icecondition" || tmpName == "freezecondition" ||
452 tmpName == "deathcondition" || tmpName == "cursecondition" ||
453 tmpName == "holycondition" || tmpName == "dazzlecondition" ||
454 tmpName == "drowncondition" || tmpName == "bleedcondition" ||
455 tmpName == "physicalcondition") {
456 ConditionType_t conditionType = CONDITION_NONE;
457 uint32_t tickInterval = 2000;
458
459 if (tmpName == "firecondition") {
460 conditionType = CONDITION_FIRE;
461 tickInterval = 10000;
462 } else if (tmpName == "poisoncondition" || tmpName == "earthcondition") {
463 conditionType = CONDITION_POISON;
464 tickInterval = 4000;
465 } else if (tmpName == "energycondition") {
466 conditionType = CONDITION_ENERGY;
467 tickInterval = 10000;
468 } else if (tmpName == "drowncondition") {
469 conditionType = CONDITION_DROWN;
470 tickInterval = 5000;
471 } else if (tmpName == "freezecondition" || tmpName == "icecondition") {
472 conditionType = CONDITION_FREEZING;
473 tickInterval = 10000;
474 } else if (tmpName == "cursecondition" || tmpName == "deathcondition") {
475 conditionType = CONDITION_CURSED;
476 tickInterval = 4000;
477 } else if (tmpName == "dazzlecondition" || tmpName == "holycondition") {
478 conditionType = CONDITION_DAZZLED;
479 tickInterval = 10000;
480 } else if (tmpName == "physicalcondition" ||
481 tmpName == "bleedcondition") {
482 conditionType = CONDITION_BLEEDING;
483 tickInterval = 4000;
484 }
485
486 if ((attr = node.attribute("tick"))) {
487 int32_t value = pugi::cast<int32_t>(attr.value());
488 if (value > 0) {
489 tickInterval = value;
490 }
491 }
492
493 int32_t minDamage = std::abs(sb.minCombatValue);
494 int32_t maxDamage = std::abs(sb.maxCombatValue);
495 int32_t startDamage = 0;
496
497 if ((attr = node.attribute("start"))) {
498 int32_t value = std::abs(pugi::cast<int32_t>(attr.value()));
499 if (value <= minDamage) {
500 startDamage = value;
501 }
502 }
503
504 Condition *condition = getDamageCondition(
505 conditionType, maxDamage, minDamage, startDamage, tickInterval);
506 combat->addCondition(condition);
507 } else if (tmpName == "strength") {
508 //
509 } else if (tmpName == "effect") {
510 //
511 } else {
512 std::cout << "[Error - Monsters::deserializeSpell] - " << description
513 << " - Unknown spell name: " << name << std::endl;
514 delete combat;
515 return false;
516 }
517
518 combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0,
519 sb.maxCombatValue, 0);
520 combatSpell = new CombatSpell(combat, needTarget, needDirection);
521
522 for (auto attributeNode : node.children()) {
523 if ((attr = attributeNode.attribute("key"))) {
524 const char *value = attr.value();
525 if (strcasecmp(value, "shooteffect") == 0) {
526 if ((attr = attributeNode.attribute("value"))) {
527 ShootType_t shoot =
528 getShootType(asLowerCaseString(attr.as_string()));
529 if (shoot != CONST_ANI_NONE) {
530 combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, shoot);
531 } else {
532 std::cout << "[Warning - Monsters::deserializeSpell] "
533 << description
534 << " - Unknown shootEffect: " << attr.as_string()
535 << std::endl;
536 }
537 }
538 } else if (strcasecmp(value, "areaeffect") == 0) {
539 if ((attr = attributeNode.attribute("value"))) {
540 MagicEffectClasses effect =
541 getMagicEffect(asLowerCaseString(attr.as_string()));
542 if (effect != CONST_ME_NONE) {
543 combat->setParam(COMBAT_PARAM_EFFECT, effect);
544 } else {
545 std::cout << "[Warning - Monsters::deserializeSpell] "
546 << description
547 << " - Unknown areaEffect: " << attr.as_string()
548 << std::endl;
549 }
550 }
551 } else {
552 std::cout << "[Warning - Monsters::deserializeSpells] Effect type \""
553 << attr.as_string() << "\" does not exist." << std::endl;
554 }
555 }
556 }
557 }
558
559 sb.spell = combatSpell;
560 if (combatSpell) {
561 sb.combatSpell = true;
562 }
563 return true;
564}
565
566bool Monsters::deserializeSpell(MonsterSpell *spell, spellBlock_t &sb,
567 const std::string &description) {
568 if (!spell->scriptName.empty()) {
569 spell->isScripted = true;
570 } else if (!spell->name.empty()) {
571 spell->isScripted = false;
572 } else {
573 return false;
574 }
575
576 sb.speed = spell->interval;
577
578 if (spell->chance > 100) {
579 sb.chance = 100;
580 } else {
581 sb.chance = spell->chance;
582 }
583
584 if (spell->range > (Map::maxViewportX * 2)) {
585 spell->range = Map::maxViewportX * 2;
586 }
587 sb.range = spell->range;
588
589 sb.minCombatValue = spell->minCombatValue;
590 sb.maxCombatValue = spell->maxCombatValue;
591 if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) {
592 int32_t value = sb.maxCombatValue;
593 sb.maxCombatValue = sb.minCombatValue;
594 sb.minCombatValue = value;
595 }
596
597 sb.spell = g_spells->getSpellByName(spell->name);
598 if (sb.spell) {
599 return true;
600 }
601
602 CombatSpell *combatSpell = nullptr;
603
604 if (spell->isScripted) {
605 std::unique_ptr<CombatSpell> combatSpellPtr(
606 new CombatSpell(nullptr, spell->needTarget, spell->needDirection));
607 if (!combatSpellPtr->loadScript("data/" + g_spells->getScriptBaseName() +
608 "/scripts/" + spell->scriptName)) {
609 std::cout << "cannot find file" << std::endl;
610 return false;
611 }
612
613 if (!combatSpellPtr->loadScriptCombat()) {
614 return false;
615 }
616
617 combatSpell = combatSpellPtr.release();
618 combatSpell->getCombat()->setPlayerCombatValues(
619 COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0);
620 } else {
621 std::unique_ptr<Combat> combat{new Combat};
622 sb.combatSpell = true;
623
624 if (spell->length > 0) {
625 spell->spread = std::max<int32_t>(0, spell->spread);
626
627 AreaCombat *area = new AreaCombat();
628 area->setupArea(spell->length, spell->spread);
629 combat->setArea(area);
630
631 spell->needDirection = true;
632 }
633
634 if (spell->radius > 0) {
635 AreaCombat *area = new AreaCombat();
636 area->setupArea(spell->radius);
637 combat->setArea(area);
638 }
639
640 std::string tmpName = asLowerCaseString(spell->name);
641
642 if (tmpName == "melee") {
643 sb.isMelee = true;
644
645 if (spell->attack > 0 && spell->skill > 0) {
646 sb.minCombatValue = 0;
647 sb.maxCombatValue =
648 -Weapons::getMaxMeleeDamage(spell->skill, spell->attack);
649 }
650
651 ConditionType_t conditionType = CONDITION_NONE;
652 int32_t minDamage = 0;
653 int32_t maxDamage = 0;
654 uint32_t tickInterval = 2000;
655
656 if (spell->conditionType != CONDITION_NONE) {
657 conditionType = spell->conditionType;
658
659 minDamage = spell->conditionMinDamage;
660 maxDamage = minDamage;
661 if (spell->tickInterval != 0) {
662 tickInterval = spell->tickInterval;
663 }
664
665 Condition *condition =
666 getDamageCondition(conditionType, maxDamage, minDamage,
667 spell->conditionStartDamage, tickInterval);
668 combat->addCondition(condition);
669 }
670
671 sb.range = 1;
672 combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE);
673 combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1);
674 combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1);
675 combat->setOrigin(ORIGIN_MELEE);
676 } else if (tmpName == "combat") {
677 if (spell->combatType == COMBAT_PHYSICALDAMAGE) {
678 combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1);
679 combat->setOrigin(ORIGIN_RANGED);
680 } else if (spell->combatType == COMBAT_HEALING) {
681 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
682 }
683 combat->setParam(COMBAT_PARAM_TYPE, spell->combatType);
684 } else if (tmpName == "speed") {
685 int32_t minSpeedChange = 0;
686 int32_t maxSpeedChange = 0;
687 int32_t duration = 10000;
688
689 if (spell->duration != 0) {
690 duration = spell->duration;
691 }
692
693 if (spell->minSpeedChange != 0) {
694 minSpeedChange = spell->minSpeedChange;
695 } else {
696 std::cout << "[Error - Monsters::deserializeSpell] - " << description
697 << " - missing speedchange/minspeedchange value" << std::endl;
698 delete spell;
699 return false;
700 }
701
702 if (minSpeedChange < -1000) {
703 std::cout << "[Warning - Monsters::deserializeSpell] - " << description
704 << " - you cannot reduce a creatures speed below -1000 (100%)"
705 << std::endl;
706 minSpeedChange = -1000;
707 }
708
709 if (spell->maxSpeedChange != 0) {
710 maxSpeedChange = spell->maxSpeedChange;
711 } else {
712 maxSpeedChange = minSpeedChange; // static speedchange value
713 }
714
715 ConditionType_t conditionType;
716 if (minSpeedChange >= 0) {
717 conditionType = CONDITION_HASTE;
718 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
719 } else {
720 conditionType = CONDITION_PARALYZE;
721 }
722
723 ConditionSpeed *condition =
724 static_cast<ConditionSpeed *>(Condition::createCondition(
725 CONDITIONID_COMBAT, conditionType, duration, 0));
726 condition->setFormulaVars(minSpeedChange / 1000.0, 0,
727 maxSpeedChange / 1000.0, 0);
728 combat->addCondition(condition);
729 } else if (tmpName == "outfit") {
730 int32_t duration = 10000;
731
732 if (spell->duration != 0) {
733 duration = spell->duration;
734 }
735
736 ConditionOutfit *condition =
737 static_cast<ConditionOutfit *>(Condition::createCondition(
738 CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0));
739 condition->setOutfit(spell->outfit);
740 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
741 combat->addCondition(condition);
742 } else if (tmpName == "invisible") {
743 int32_t duration = 10000;
744
745 if (spell->duration != 0) {
746 duration = spell->duration;
747 }
748
749 Condition *condition = Condition::createCondition(
750 CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0);
751 combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0);
752 combat->addCondition(condition);
753 } else if (tmpName == "drunk") {
754 int32_t duration = 10000;
755
756 if (spell->duration != 0) {
757 duration = spell->duration;
758 }
759
760 Condition *condition = Condition::createCondition(
761 CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0);
762 combat->addCondition(condition);
763 } else if (tmpName == "firefield") {
764 combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL);
765 } else if (tmpName == "poisonfield") {
766 combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP);
767 } else if (tmpName == "energyfield") {
768 combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP);
769 } else if (tmpName == "condition") {
770 uint32_t tickInterval = 2000;
771
772 if (spell->conditionType == CONDITION_NONE) {
773 std::cout << "[Error - Monsters::deserializeSpell] - " << description
774 << " - Condition is not set for: " << spell->name
775 << std::endl;
776 }
777
778 if (spell->tickInterval != 0) {
779 int32_t value = spell->tickInterval;
780 if (value > 0) {
781 tickInterval = value;
782 }
783 }
784
785 int32_t minDamage = std::abs(spell->conditionMinDamage);
786 int32_t maxDamage = std::abs(spell->conditionMaxDamage);
787 int32_t startDamage = 0;
788
789 if (spell->conditionStartDamage != 0) {
790 int32_t value = std::abs(spell->conditionStartDamage);
791 if (value <= minDamage) {
792 startDamage = value;
793 }
794 }
795
796 Condition *condition =
797 getDamageCondition(spell->conditionType, maxDamage, minDamage,
798 startDamage, tickInterval);
799 combat->addCondition(condition);
800 } else if (tmpName == "strength") {
801 //
802 } else if (tmpName == "effect") {
803 //
804 } else {
805 std::cout << "[Error - Monsters::deserializeSpell] - " << description
806 << " - Unknown spell name: " << spell->name << std::endl;
807 }
808
809 if (spell->needTarget) {
810 if (spell->shoot != CONST_ANI_NONE) {
811 combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, spell->shoot);
812 }
813 }
814
815 if (spell->effect != CONST_ME_NONE) {
816 combat->setParam(COMBAT_PARAM_EFFECT, spell->effect);
817 }
818
819 combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0,
820 sb.maxCombatValue, 0);
821 combatSpell = new CombatSpell(combat.release(), spell->needTarget,
822 spell->needDirection);
823 }
824
825 sb.spell = combatSpell;
826 if (combatSpell) {
827 sb.combatSpell = true;
828 }
829 return true;
830}
831
832MonsterType *Monsters::loadMonster(const std::string &file,
833 const std::string &monsterName,
834 bool reloading /*= false*/) {
835 MonsterType *mType = nullptr;
836
837 pugi::xml_document doc;
838 pugi::xml_parse_result result = doc.load_file(file.c_str());
839 if (!result) {
840 printXMLError("Error - Monsters::loadMonster", file, result);
841 return nullptr;
842 }
843
844 pugi::xml_node monsterNode = doc.child("monster");
845 if (!monsterNode) {
846 std::cout << "[Error - Monsters::loadMonster] Missing monster node in: "
847 << file << std::endl;
848 return nullptr;
849 }
850
851 pugi::xml_attribute attr;
852 if (!(attr = monsterNode.attribute("name"))) {
853 std::cout << "[Error - Monsters::loadMonster] Missing name in: " << file
854 << std::endl;
855 return nullptr;
856 }
857
858 if (reloading) {
859 auto it = monsters.find(asLowerCaseString(monsterName));
860 if (it != monsters.end()) {
861 mType = &it->second;
862 mType->info = {};
863 }
864 }
865
866 if (!mType) {
867 mType = &monsters[asLowerCaseString(monsterName)];
868 }
869
870 mType->name = attr.as_string();
871
872 if ((attr = monsterNode.attribute("nameDescription"))) {
873 mType->nameDescription = attr.as_string();
874 } else {
875 mType->nameDescription = "a " + asLowerCaseString(mType->name);
876 }
877
878 if ((attr = monsterNode.attribute("race"))) {
879 std::string tmpStrValue = asLowerCaseString(attr.as_string());
880 uint16_t tmpInt = pugi::cast<uint16_t>(attr.value());
881 if (tmpStrValue == "venom" || tmpInt == 1) {
882 mType->info.race = RACE_VENOM;
883 } else if (tmpStrValue == "blood" || tmpInt == 2) {
884 mType->info.race = RACE_BLOOD;
885 } else if (tmpStrValue == "undead" || tmpInt == 3) {
886 mType->info.race = RACE_UNDEAD;
887 } else if (tmpStrValue == "fire" || tmpInt == 4) {
888 mType->info.race = RACE_FIRE;
889 } else if (tmpStrValue == "energy" || tmpInt == 5) {
890 mType->info.race = RACE_ENERGY;
891 } else {
892 std::cout << "[Warning - Monsters::loadMonster] Unknown race type "
893 << attr.as_string() << ". " << file << std::endl;
894 }
895 }
896
897 if ((attr = monsterNode.attribute("experience"))) {
898 mType->info.experience = pugi::cast<uint64_t>(attr.value());
899 }
900
901 if ((attr = monsterNode.attribute("speed"))) {
902 mType->info.baseSpeed = pugi::cast<int32_t>(attr.value());
903 }
904
905 if ((attr = monsterNode.attribute("manacost"))) {
906 mType->info.manaCost = pugi::cast<uint32_t>(attr.value());
907 }
908
909 if ((attr = monsterNode.attribute("skull"))) {
910 mType->info.skull = getSkullType(asLowerCaseString(attr.as_string()));
911 }
912
913 if ((attr = monsterNode.attribute("script"))) {
914 if (!scriptInterface) {
915 scriptInterface.reset(new LuaScriptInterface("Monster Interface"));
916 scriptInterface->initState();
917 }
918
919 std::string script = attr.as_string();
920 if (scriptInterface->loadFile("data/monster/scripts/" + script) == 0) {
921 mType->info.scriptInterface = scriptInterface.get();
922 mType->info.creatureAppearEvent =
923 scriptInterface->getEvent("onCreatureAppear");
924 mType->info.creatureDisappearEvent =
925 scriptInterface->getEvent("onCreatureDisappear");
926 mType->info.creatureMoveEvent =
927 scriptInterface->getEvent("onCreatureMove");
928 mType->info.creatureSayEvent = scriptInterface->getEvent("onCreatureSay");
929 mType->info.thinkEvent = scriptInterface->getEvent("onThink");
930 } else {
931 std::cout << "[Warning - Monsters::loadMonster] Can not load script: "
932 << script << std::endl;
933 std::cout << scriptInterface->getLastLuaError() << std::endl;
934 }
935 }
936
937 pugi::xml_node node;
938 if ((node = monsterNode.child("health"))) {
939 if ((attr = node.attribute("now"))) {
940 mType->info.health = pugi::cast<int32_t>(attr.value());
941 } else {
942 std::cout << "[Error - Monsters::loadMonster] Missing health now. "
943 << file << std::endl;
944 }
945
946 if ((attr = node.attribute("max"))) {
947 mType->info.healthMax = pugi::cast<int32_t>(attr.value());
948 } else {
949 std::cout << "[Error - Monsters::loadMonster] Missing health max. "
950 << file << std::endl;
951 }
952 }
953
954 if ((node = monsterNode.child("flags"))) {
955 for (auto flagNode : node.children()) {
956 attr = flagNode.first_attribute();
957 const char *attrName = attr.name();
958 if (strcasecmp(attrName, "summonable") == 0) {
959 mType->info.isSummonable = attr.as_bool();
960 } else if (strcasecmp(attrName, "attackable") == 0) {
961 mType->info.isAttackable = attr.as_bool();
962 } else if (strcasecmp(attrName, "hostile") == 0) {
963 mType->info.isHostile = attr.as_bool();
964 } else if (strcasecmp(attrName, "illusionable") == 0) {
965 mType->info.isIllusionable = attr.as_bool();
966 } else if (strcasecmp(attrName, "convinceable") == 0) {
967 mType->info.isConvinceable = attr.as_bool();
968 } else if (strcasecmp(attrName, "pushable") == 0) {
969 mType->info.pushable = attr.as_bool();
970 } else if (strcasecmp(attrName, "isboss") == 0) {
971 mType->info.isBoss = attr.as_bool();
972 } else if (strcasecmp(attrName, "canpushitems") == 0) {
973 mType->info.canPushItems = attr.as_bool();
974 } else if (strcasecmp(attrName, "canpushcreatures") == 0) {
975 mType->info.canPushCreatures = attr.as_bool();
976 } else if (strcasecmp(attrName, "staticattack") == 0) {
977 uint32_t staticAttack = pugi::cast<uint32_t>(attr.value());
978 if (staticAttack > 100) {
979 std::cout << "[Warning - Monsters::loadMonster] staticattack greater "
980 "than 100. "
981 << file << std::endl;
982 staticAttack = 100;
983 }
984
985 mType->info.staticAttackChance = staticAttack;
986 } else if (strcasecmp(attrName, "lightlevel") == 0) {
987 mType->info.light.level = pugi::cast<uint16_t>(attr.value());
988 } else if (strcasecmp(attrName, "lightcolor") == 0) {
989 mType->info.light.color = pugi::cast<uint16_t>(attr.value());
990 } else if (strcasecmp(attrName, "targetdistance") == 0) {
991 mType->info.targetDistance =
992 std::max<int32_t>(1, pugi::cast<int32_t>(attr.value()));
993 } else if (strcasecmp(attrName, "runonhealth") == 0) {
994 mType->info.runAwayHealth = pugi::cast<int32_t>(attr.value());
995 } else if (strcasecmp(attrName, "hidehealth") == 0) {
996 mType->info.hiddenHealth = attr.as_bool();
997 } else if (strcasecmp(attrName, "canwalkonenergy") == 0) {
998 mType->info.canWalkOnEnergy = attr.as_bool();
999 } else if (strcasecmp(attrName, "canwalkonfire") == 0) {
1000 mType->info.canWalkOnFire = attr.as_bool();
1001 } else if (strcasecmp(attrName, "canwalkonpoison") == 0) {
1002 mType->info.canWalkOnPoison = attr.as_bool();
1003 } else {
1004 std::cout
1005 << "[Warning - Monsters::loadMonster] Unknown flag attribute: "
1006 << attrName << ". " << file << std::endl;
1007 }
1008 }
1009
1010 // if a monster can push creatures,
1011 // it should not be pushable
1012 if (mType->info.canPushCreatures) {
1013 mType->info.pushable = false;
1014 }
1015 }
1016
1017 if ((node = monsterNode.child("targetchange"))) {
1018 if ((attr = node.attribute("speed")) ||
1019 (attr = node.attribute("interval"))) {
1020 mType->info.changeTargetSpeed = pugi::cast<uint32_t>(attr.value());
1021 } else {
1022 std::cout
1023 << "[Warning - Monsters::loadMonster] Missing targetchange speed. "
1024 << file << std::endl;
1025 }
1026
1027 if ((attr = node.attribute("chance"))) {
1028 mType->info.changeTargetChance = pugi::cast<int32_t>(attr.value());
1029 } else {
1030 std::cout
1031 << "[Warning - Monsters::loadMonster] Missing targetchange chance. "
1032 << file << std::endl;
1033 }
1034 }
1035
1036 if ((node = monsterNode.child("look"))) {
1037 if ((attr = node.attribute("type"))) {
1038 mType->info.outfit.lookType = pugi::cast<uint16_t>(attr.value());
1039
1040 if ((attr = node.attribute("head"))) {
1041 mType->info.outfit.lookHead = pugi::cast<uint16_t>(attr.value());
1042 }
1043
1044 if ((attr = node.attribute("body"))) {
1045 mType->info.outfit.lookBody = pugi::cast<uint16_t>(attr.value());
1046 }
1047
1048 if ((attr = node.attribute("legs"))) {
1049 mType->info.outfit.lookLegs = pugi::cast<uint16_t>(attr.value());
1050 }
1051
1052 if ((attr = node.attribute("feet"))) {
1053 mType->info.outfit.lookFeet = pugi::cast<uint16_t>(attr.value());
1054 }
1055
1056 if ((attr = node.attribute("addons"))) {
1057 mType->info.outfit.lookAddons = pugi::cast<uint16_t>(attr.value());
1058 }
1059 } else if ((attr = node.attribute("typeex"))) {
1060 mType->info.outfit.lookTypeEx = pugi::cast<uint16_t>(attr.value());
1061 } else {
1062 std::cout
1063 << "[Warning - Monsters::loadMonster] Missing look type/typeex. "
1064 << file << std::endl;
1065 }
1066
1067 if ((attr = node.attribute("mount"))) {
1068 mType->info.outfit.lookMount = pugi::cast<uint16_t>(attr.value());
1069 }
1070
1071 if ((attr = node.attribute("aura"))) {
1072 mType->info.outfit.lookAura = pugi::cast<uint16_t>(attr.value());
1073 }
1074
1075 if ((attr = node.attribute("wings"))) {
1076 mType->info.outfit.lookWings = pugi::cast<uint16_t>(attr.value());
1077 }
1078
1079 if ((attr = node.attribute("shader"))) {
1080 Shader *shader = g_game.shaders.getShaderByName(attr.as_string());
1081 mType->info.outfit.lookShader = shader ? shader->id : 0;
1082 }
1083
1084 if ((attr = node.attribute("corpse"))) {
1085 mType->info.lookcorpse = pugi::cast<uint16_t>(attr.value());
1086 }
1087
1088 // Outfit de ataque: opcional. Se especifica con attacktype o attackmeleetype y attackrangetype en el tag <look>
1089 bool hasMeleeOutfit = false;
1090 if ((attr = node.attribute("attackmeleetype"))) {
1091 mType->info.lookAttackMelee = mType->info.outfit;
1092 mType->info.lookAttackMelee.lookType = pugi::cast<uint16_t>(attr.value());
1093 hasMeleeOutfit = true;
1094 } else if ((attr = node.attribute("attacktype"))) {
1095 // Compatibility fallback
1096 mType->info.lookAttack = mType->info.outfit;
1097 mType->info.lookAttack.lookType = pugi::cast<uint16_t>(attr.value());
1098 mType->info.lookAttackMelee = mType->info.lookAttack;
1099 hasMeleeOutfit = true;
1100 }
1101
1102 if ((attr = node.attribute("attackrangetype"))) {
1103 mType->info.lookAttackRange = mType->info.outfit;
1104 mType->info.lookAttackRange.lookType = pugi::cast<uint16_t>(attr.value());
1105 } else if (hasMeleeOutfit) {
1106 mType->info.lookAttackRange = mType->info.lookAttackMelee;
1107 }
1108
1109 if (hasMeleeOutfit || mType->info.lookAttackRange.lookType != 0) {
1110 if ((attr = node.attribute("attackhead"))) {
1111 uint8_t head = pugi::cast<uint8_t>(attr.value());
1112 if (mType->info.lookAttackMelee.lookType != 0) mType->info.lookAttackMelee.lookHead = head;
1113 if (mType->info.lookAttackRange.lookType != 0) mType->info.lookAttackRange.lookHead = head;
1114 mType->info.lookAttack.lookHead = head;
1115 }
1116 if ((attr = node.attribute("attackbody"))) {
1117 uint8_t body = pugi::cast<uint8_t>(attr.value());
1118 if (mType->info.lookAttackMelee.lookType != 0) mType->info.lookAttackMelee.lookBody = body;
1119 if (mType->info.lookAttackRange.lookType != 0) mType->info.lookAttackRange.lookBody = body;
1120 mType->info.lookAttack.lookBody = body;
1121 }
1122 if ((attr = node.attribute("attacklegs"))) {
1123 uint8_t legs = pugi::cast<uint8_t>(attr.value());
1124 if (mType->info.lookAttackMelee.lookType != 0) mType->info.lookAttackMelee.lookLegs = legs;
1125 if (mType->info.lookAttackRange.lookType != 0) mType->info.lookAttackRange.lookLegs = legs;
1126 mType->info.lookAttack.lookLegs = legs;
1127 }
1128 if ((attr = node.attribute("attackfeet"))) {
1129 uint8_t feet = pugi::cast<uint8_t>(attr.value());
1130 if (mType->info.lookAttackMelee.lookType != 0) mType->info.lookAttackMelee.lookFeet = feet;
1131 if (mType->info.lookAttackRange.lookType != 0) mType->info.lookAttackRange.lookFeet = feet;
1132 mType->info.lookAttack.lookFeet = feet;
1133 }
1134 if ((attr = node.attribute("attackrange"))) {
1135 uint8_t range = std::max<uint8_t>(1, static_cast<uint8_t>(pugi::cast<uint16_t>(attr.value())));
1136 if (mType->info.lookAttackMelee.lookType != 0) mType->info.lookAttackMelee.attackRange = range;
1137 if (mType->info.lookAttackRange.lookType != 0) mType->info.lookAttackRange.attackRange = range;
1138 mType->info.lookAttack.attackRange = range;
1139 }
1140 }
1141 }
1142
1143 if ((node = monsterNode.child("attacks"))) {
1144 for (auto attackNode : node.children()) {
1145 spellBlock_t sb;
1146 if (deserializeSpell(attackNode, sb, monsterName)) {
1147 if (sb.isMelee && mType->info.lookAttack.attackRange > 1) {
1148 sb.range = mType->info.lookAttack.attackRange;
1149 }
1150 mType->info.attackSpells.emplace_back(std::move(sb));
1151 } else {
1152 std::cout << "[Warning - Monsters::loadMonster] Cant load spell. "
1153 << file << std::endl;
1154 }
1155 }
1156 }
1157
1158 if ((node = monsterNode.child("defenses"))) {
1159 if ((attr = node.attribute("defense"))) {
1160 mType->info.defense = pugi::cast<int32_t>(attr.value());
1161 }
1162
1163 if ((attr = node.attribute("armor"))) {
1164 mType->info.armor = pugi::cast<int32_t>(attr.value());
1165 }
1166
1167 for (auto defenseNode : node.children()) {
1168 spellBlock_t sb;
1169 if (deserializeSpell(defenseNode, sb, monsterName)) {
1170 mType->info.defenseSpells.emplace_back(std::move(sb));
1171 } else {
1172 std::cout << "[Warning - Monsters::loadMonster] Cant load spell. "
1173 << file << std::endl;
1174 }
1175 }
1176 }
1177
1178 if ((node = monsterNode.child("immunities"))) {
1179 for (auto immunityNode : node.children()) {
1180 if ((attr = immunityNode.attribute("name"))) {
1181 std::string tmpStrValue = asLowerCaseString(attr.as_string());
1182 if (tmpStrValue == "physical") {
1183 mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE;
1184 mType->info.conditionImmunities |= CONDITION_BLEEDING;
1185 } else if (tmpStrValue == "energy") {
1186 mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE;
1187 mType->info.conditionImmunities |= CONDITION_ENERGY;
1188 } else if (tmpStrValue == "fire") {
1189 mType->info.damageImmunities |= COMBAT_FIREDAMAGE;
1190 mType->info.conditionImmunities |= CONDITION_FIRE;
1191 } else if (tmpStrValue == "poison" || tmpStrValue == "earth") {
1192 mType->info.damageImmunities |= COMBAT_EARTHDAMAGE;
1193 mType->info.conditionImmunities |= CONDITION_POISON;
1194 } else if (tmpStrValue == "drown") {
1195 mType->info.damageImmunities |= COMBAT_DROWNDAMAGE;
1196 mType->info.conditionImmunities |= CONDITION_DROWN;
1197 } else if (tmpStrValue == "ice") {
1198 mType->info.damageImmunities |= COMBAT_ICEDAMAGE;
1199 mType->info.conditionImmunities |= CONDITION_FREEZING;
1200 } else if (tmpStrValue == "holy") {
1201 mType->info.damageImmunities |= COMBAT_HOLYDAMAGE;
1202 mType->info.conditionImmunities |= CONDITION_DAZZLED;
1203 } else if (tmpStrValue == "death") {
1204 mType->info.damageImmunities |= COMBAT_DEATHDAMAGE;
1205 mType->info.conditionImmunities |= CONDITION_CURSED;
1206 } else if (tmpStrValue == "lifedrain") {
1207 mType->info.damageImmunities |= COMBAT_LIFEDRAIN;
1208 } else if (tmpStrValue == "manadrain") {
1209 mType->info.damageImmunities |= COMBAT_MANADRAIN;
1210 } else if (tmpStrValue == "paralyze") {
1211 mType->info.conditionImmunities |= CONDITION_PARALYZE;
1212 } else if (tmpStrValue == "outfit") {
1213 mType->info.conditionImmunities |= CONDITION_OUTFIT;
1214 } else if (tmpStrValue == "drunk") {
1215 mType->info.conditionImmunities |= CONDITION_DRUNK;
1216 } else if (tmpStrValue == "invisible" ||
1217 tmpStrValue == "invisibility") {
1218 mType->info.conditionImmunities |= CONDITION_INVISIBLE;
1219 } else if (tmpStrValue == "bleed") {
1220 mType->info.conditionImmunities |= CONDITION_BLEEDING;
1221 } else {
1222 std::cout
1223 << "[Warning - Monsters::loadMonster] Unknown immunity name "
1224 << attr.as_string() << ". " << file << std::endl;
1225 }
1226 } else if ((attr = immunityNode.attribute("physical"))) {
1227 if (attr.as_bool()) {
1228 mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE;
1229 mType->info.conditionImmunities |= CONDITION_BLEEDING;
1230 }
1231 } else if ((attr = immunityNode.attribute("energy"))) {
1232 if (attr.as_bool()) {
1233 mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE;
1234 mType->info.conditionImmunities |= CONDITION_ENERGY;
1235 }
1236 } else if ((attr = immunityNode.attribute("fire"))) {
1237 if (attr.as_bool()) {
1238 mType->info.damageImmunities |= COMBAT_FIREDAMAGE;
1239 mType->info.conditionImmunities |= CONDITION_FIRE;
1240 }
1241 } else if ((attr = immunityNode.attribute("poison")) ||
1242 (attr = immunityNode.attribute("earth"))) {
1243 if (attr.as_bool()) {
1244 mType->info.damageImmunities |= COMBAT_EARTHDAMAGE;
1245 mType->info.conditionImmunities |= CONDITION_POISON;
1246 }
1247 } else if ((attr = immunityNode.attribute("drown"))) {
1248 if (attr.as_bool()) {
1249 mType->info.damageImmunities |= COMBAT_DROWNDAMAGE;
1250 mType->info.conditionImmunities |= CONDITION_DROWN;
1251 }
1252 } else if ((attr = immunityNode.attribute("ice"))) {
1253 if (attr.as_bool()) {
1254 mType->info.damageImmunities |= COMBAT_ICEDAMAGE;
1255 mType->info.conditionImmunities |= CONDITION_FREEZING;
1256 }
1257 } else if ((attr = immunityNode.attribute("holy"))) {
1258 if (attr.as_bool()) {
1259 mType->info.damageImmunities |= COMBAT_HOLYDAMAGE;
1260 mType->info.conditionImmunities |= CONDITION_DAZZLED;
1261 }
1262 } else if ((attr = immunityNode.attribute("death"))) {
1263 if (attr.as_bool()) {
1264 mType->info.damageImmunities |= COMBAT_DEATHDAMAGE;
1265 mType->info.conditionImmunities |= CONDITION_CURSED;
1266 }
1267 } else if ((attr = immunityNode.attribute("lifedrain"))) {
1268 if (attr.as_bool()) {
1269 mType->info.damageImmunities |= COMBAT_LIFEDRAIN;
1270 }
1271 } else if ((attr = immunityNode.attribute("manadrain"))) {
1272 if (attr.as_bool()) {
1273 mType->info.damageImmunities |= COMBAT_MANADRAIN;
1274 }
1275 } else if ((attr = immunityNode.attribute("paralyze"))) {
1276 if (attr.as_bool()) {
1277 mType->info.conditionImmunities |= CONDITION_PARALYZE;
1278 }
1279 } else if ((attr = immunityNode.attribute("outfit"))) {
1280 if (attr.as_bool()) {
1281 mType->info.conditionImmunities |= CONDITION_OUTFIT;
1282 }
1283 } else if ((attr = immunityNode.attribute("bleed"))) {
1284 if (attr.as_bool()) {
1285 mType->info.conditionImmunities |= CONDITION_BLEEDING;
1286 }
1287 } else if ((attr = immunityNode.attribute("drunk"))) {
1288 if (attr.as_bool()) {
1289 mType->info.conditionImmunities |= CONDITION_DRUNK;
1290 }
1291 } else if ((attr = immunityNode.attribute("invisible")) ||
1292 (attr = immunityNode.attribute("invisibility"))) {
1293 if (attr.as_bool()) {
1294 mType->info.conditionImmunities |= CONDITION_INVISIBLE;
1295 }
1296 } else {
1297 std::cout << "[Warning - Monsters::loadMonster] Unknown immunity. "
1298 << file << std::endl;
1299 }
1300 }
1301 }
1302
1303 if ((node = monsterNode.child("voices"))) {
1304 if ((attr = node.attribute("speed")) ||
1305 (attr = node.attribute("interval"))) {
1306 mType->info.yellSpeedTicks = pugi::cast<uint32_t>(attr.value());
1307 } else {
1308 std::cout << "[Warning - Monsters::loadMonster] Missing voices speed. "
1309 << file << std::endl;
1310 }
1311
1312 if ((attr = node.attribute("chance"))) {
1313 mType->info.yellChance = pugi::cast<uint32_t>(attr.value());
1314 } else {
1315 std::cout << "[Warning - Monsters::loadMonster] Missing voices chance. "
1316 << file << std::endl;
1317 }
1318
1319 for (auto voiceNode : node.children()) {
1320 voiceBlock_t vb;
1321 if ((attr = voiceNode.attribute("sentence"))) {
1322 vb.text = attr.as_string();
1323 } else {
1324 std::cout
1325 << "[Warning - Monsters::loadMonster] Missing voice sentence. "
1326 << file << std::endl;
1327 }
1328
1329 if ((attr = voiceNode.attribute("yell"))) {
1330 vb.yellText = attr.as_bool();
1331 } else {
1332 vb.yellText = false;
1333 }
1334 mType->info.voiceVector.emplace_back(vb);
1335 }
1336 }
1337
1338 if ((node = monsterNode.child("loot"))) {
1339 for (auto lootNode : node.children()) {
1340 LootBlock lootBlock;
1341 if (loadLootItem(lootNode, lootBlock)) {
1342 mType->info.lootItems.emplace_back(std::move(lootBlock));
1343 }
1344 }
1345 }
1346
1347 if ((node = monsterNode.child("elements"))) {
1348 for (auto elementNode : node.children()) {
1349 if ((attr = elementNode.attribute("physicalPercent"))) {
1350 mType->info.elementMap[COMBAT_PHYSICALDAMAGE] =
1351 pugi::cast<int32_t>(attr.value());
1352 } else if ((attr = elementNode.attribute("icePercent"))) {
1353 mType->info.elementMap[COMBAT_ICEDAMAGE] =
1354 pugi::cast<int32_t>(attr.value());
1355 } else if ((attr = elementNode.attribute("poisonPercent")) ||
1356 (attr = elementNode.attribute("earthPercent"))) {
1357 mType->info.elementMap[COMBAT_EARTHDAMAGE] =
1358 pugi::cast<int32_t>(attr.value());
1359 } else if ((attr = elementNode.attribute("firePercent"))) {
1360 mType->info.elementMap[COMBAT_FIREDAMAGE] =
1361 pugi::cast<int32_t>(attr.value());
1362 } else if ((attr = elementNode.attribute("energyPercent"))) {
1363 mType->info.elementMap[COMBAT_ENERGYDAMAGE] =
1364 pugi::cast<int32_t>(attr.value());
1365 } else if ((attr = elementNode.attribute("holyPercent"))) {
1366 mType->info.elementMap[COMBAT_HOLYDAMAGE] =
1367 pugi::cast<int32_t>(attr.value());
1368 } else if ((attr = elementNode.attribute("deathPercent"))) {
1369 mType->info.elementMap[COMBAT_DEATHDAMAGE] =
1370 pugi::cast<int32_t>(attr.value());
1371 } else if ((attr = elementNode.attribute("drownPercent"))) {
1372 mType->info.elementMap[COMBAT_DROWNDAMAGE] =
1373 pugi::cast<int32_t>(attr.value());
1374 } else if ((attr = elementNode.attribute("lifedrainPercent"))) {
1375 mType->info.elementMap[COMBAT_LIFEDRAIN] =
1376 pugi::cast<int32_t>(attr.value());
1377 } else if ((attr = elementNode.attribute("manadrainPercent"))) {
1378 mType->info.elementMap[COMBAT_MANADRAIN] =
1379 pugi::cast<int32_t>(attr.value());
1380 } else {
1381 std::cout << "[Warning - Monsters::loadMonster] Unknown element "
1382 "percent. "
1383 << file << std::endl;
1384 }
1385 }
1386 }
1387
1388 if ((node = monsterNode.child("summons"))) {
1389 if ((attr = node.attribute("maxSummons"))) {
1390 mType->info.maxSummons =
1391 std::min<uint32_t>(pugi::cast<uint32_t>(attr.value()), 100);
1392 } else {
1393 std::cout << "[Warning - Monsters::loadMonster] Missing summons "
1394 "maxSummons. "
1395 << file << std::endl;
1396 }
1397
1398 for (auto summonNode : node.children()) {
1399 int32_t chance = 100;
1400 int32_t speed = 1000;
1401 int32_t max = mType->info.maxSummons;
1402 bool force = false;
1403
1404 if ((attr = summonNode.attribute("speed")) ||
1405 (attr = summonNode.attribute("interval"))) {
1406 speed = std::max<int32_t>(1, pugi::cast<int32_t>(attr.value()));
1407 }
1408
1409 if ((attr = summonNode.attribute("chance"))) {
1410 chance = pugi::cast<int32_t>(attr.value());
1411 }
1412
1413 if ((attr = summonNode.attribute("max"))) {
1414 max = pugi::cast<uint32_t>(attr.value());
1415 }
1416
1417 if ((attr = summonNode.attribute("force"))) {
1418 force = attr.as_bool();
1419 }
1420
1421 if ((attr = summonNode.attribute("name"))) {
1422 summonBlock_t sb;
1423 sb.name = attr.as_string();
1424 sb.speed = speed;
1425 sb.chance = chance;
1426 sb.max = max;
1427 sb.force = force;
1428 mType->info.summons.emplace_back(sb);
1429 } else {
1430 std::cout << "[Warning - Monsters::loadMonster] Missing summon name. "
1431 << file << std::endl;
1432 }
1433 }
1434 }
1435
1436 if ((node = monsterNode.child("script"))) {
1437 for (auto eventNode : node.children()) {
1438 if ((attr = eventNode.attribute("name"))) {
1439 mType->info.scripts.emplace_back(attr.as_string());
1440 } else {
1441 std::cout << "[Warning - Monsters::loadMonster] Missing name for "
1442 "script event. "
1443 << file << std::endl;
1444 }
1445 }
1446 }
1447
1448 mType->info.summons.shrink_to_fit();
1449 mType->info.lootItems.shrink_to_fit();
1450 mType->info.attackSpells.shrink_to_fit();
1451 mType->info.defenseSpells.shrink_to_fit();
1452 mType->info.voiceVector.shrink_to_fit();
1453 mType->info.scripts.shrink_to_fit();
1454 return mType;
1455}
1456
1457bool MonsterType::loadCallback(LuaScriptInterface *scriptInterface) {
1458 int32_t id = scriptInterface->getEvent();
1459 if (id == -1) {
1460 std::cout << "[Warning - MonsterType::loadCallback] Event not found. "
1461 << std::endl;
1462 return false;
1463 }
1464
1465 info.scriptInterface = scriptInterface;
1466 if (info.eventType == MONSTERS_EVENT_THINK) {
1467 info.thinkEvent = id;
1468 } else if (info.eventType == MONSTERS_EVENT_APPEAR) {
1469 info.creatureAppearEvent = id;
1470 } else if (info.eventType == MONSTERS_EVENT_DISAPPEAR) {
1471 info.creatureDisappearEvent = id;
1472 } else if (info.eventType == MONSTERS_EVENT_MOVE) {
1473 info.creatureMoveEvent = id;
1474 } else if (info.eventType == MONSTERS_EVENT_SAY) {
1475 info.creatureSayEvent = id;
1476 }
1477 return true;
1478}
1479
1480bool Monsters::loadLootItem(const pugi::xml_node &node, LootBlock &lootBlock) {
1481 pugi::xml_attribute attr;
1482 if ((attr = node.attribute("id"))) {
1483 lootBlock.id = pugi::cast<int32_t>(attr.value());
1484 } else if ((attr = node.attribute("name"))) {
1485 auto name = attr.as_string();
1486 auto ids = Item::items.nameToItems.equal_range(asLowerCaseString(name));
1487
1488 if (ids.first == Item::items.nameToItems.cend()) {
1489 std::cout << "[Warning - Monsters::loadMonster] Unknown loot item \""
1490 << name << "\". " << std::endl;
1491 return false;
1492 }
1493
1494 uint32_t id = ids.first->second;
1495
1496 if (std::next(ids.first) != ids.second) {
1497 std::cout << "[Warning - Monsters::loadMonster] Non-unique "
1498 "loot item \""
1499 << name << "\". " << std::endl;
1500 return false;
1501 }
1502
1503 lootBlock.id = id;
1504 }
1505
1506 if (lootBlock.id == 0) {
1507 return false;
1508 }
1509
1510 if ((attr = node.attribute("countmax"))) {
1511 lootBlock.countmax =
1512 std::max<int32_t>(1, pugi::cast<int32_t>(attr.value()));
1513 } else {
1514 lootBlock.countmax = 1;
1515 }
1516
1517 if ((attr = node.attribute("chance")) || (attr = node.attribute("chance1"))) {
1518 int32_t lootChance = pugi::cast<int32_t>(attr.value());
1519 if (lootChance > static_cast<int32_t>(MAX_LOOTCHANCE)) {
1520 std::cout << "[Warning - Monsters::loadMonster] Invalid \"chance\" "
1521 << lootChance << " used for loot, the max is " << MAX_LOOTCHANCE
1522 << ". " << std::endl;
1523 }
1524 lootBlock.chance = std::min<int32_t>(MAX_LOOTCHANCE, lootChance);
1525 } else {
1526 lootBlock.chance = MAX_LOOTCHANCE;
1527 }
1528
1529 if (Item::items[lootBlock.id].isContainer()) {
1530 loadLootContainer(node, lootBlock);
1531 }
1532
1533 // optional
1534 if ((attr = node.attribute("subtype"))) {
1535 lootBlock.subType = pugi::cast<int32_t>(attr.value());
1536 } else {
1537 uint32_t charges = Item::items[lootBlock.id].charges;
1538 if (charges != 0) {
1539 lootBlock.subType = charges;
1540 }
1541 }
1542
1543 if ((attr = node.attribute("actionId"))) {
1544 lootBlock.actionId = pugi::cast<int32_t>(attr.value());
1545 }
1546
1547 if ((attr = node.attribute("text"))) {
1548 lootBlock.text = attr.as_string();
1549 }
1550 return true;
1551}
1552
1553void Monsters::loadLootContainer(const pugi::xml_node &node,
1554 LootBlock &lBlock) {
1555 for (auto subNode : node.children()) {
1556 LootBlock lootBlock;
1557 if (loadLootItem(subNode, lootBlock)) {
1558 lBlock.childLoot.emplace_back(std::move(lootBlock));
1559 }
1560 }
1561}
1562
1563MonsterType *Monsters::getMonsterType(const std::string &name,
1564 bool loadFromFile /*= true */) {
1565 std::string lowerCaseName = asLowerCaseString(name);
1566
1567 auto it = monsters.find(lowerCaseName);
1568 if (it == monsters.end()) {
1569 auto it2 = unloadedMonsters.find(lowerCaseName);
1570 if (it2 == unloadedMonsters.end()) {
1571 return nullptr;
1572 }
1573
1574 if (!loadFromFile) {
1575 return nullptr;
1576 }
1577
1578 return loadMonster(it2->second, name);
1579 }
1580 return &it->second;
1581}
1582