/** * Tibia GIMUD Server - a free and open-source MMORPG server emulator * Copyright (C) 2017 Alejandro Mujica * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "otpch.h" #include "monsters.h" #include "monster.h" #include "spells.h" #include "combat.h" #include "configmanager.h" #include "game.h" #include "pugicast.h" extern Game g_game; extern Spells* g_spells; extern Monsters g_monsters; extern ConfigManager g_config; spellBlock_t::~spellBlock_t() { if (combatSpell) { delete spell; } } uint32_t Monsters::getLootRandom() { return uniform_random(0, MAX_LOOTCHANCE); } void MonsterType::createLoot(Container* corpse) { if (g_config.getNumber(ConfigManager::RATE_LOOT) == 0) { corpse->startDecaying(); return; } Item* bagItem = Item::CreateItem(2853, 1); if (!bagItem) { return; } Container* bagContainer = bagItem->getContainer(); if (!bagContainer) { return; } if (g_game.internalAddItem(corpse, bagItem) != RETURNVALUE_NOERROR) { corpse->internalAddThing(bagItem); } bool includeBagLoot = false; for (auto it = info.lootItems.rbegin(), end = info.lootItems.rend(); it != end; ++it) { std::vector itemList = createLootItem(*it); if (itemList.empty()) { continue; } for (Item* item : itemList) { //check containers if (Container* container = item->getContainer()) { if (!createLootContainer(container, *it)) { delete container; continue; } } const ItemType& itemType = Item::items[item->getID()]; if (itemType.weaponType != WEAPON_NONE || itemType.stopTime || itemType.decayTime) { includeBagLoot = true; if (g_game.internalAddItem(bagContainer, item) != RETURNVALUE_NOERROR) { corpse->internalAddThing(item); } } else { if (g_game.internalAddItem(corpse, item) != RETURNVALUE_NOERROR) { corpse->internalAddThing(item); } } } } if (!includeBagLoot) { g_game.internalRemoveItem(bagItem); } if (g_config.getBoolean(ConfigManager::SHOW_MONSTER_LOOT)) { Player* owner = g_game.getPlayerByID(corpse->getCorpseOwner()); if (owner) { std::ostringstream ss; ss << "Loot of " << nameDescription << ": " << corpse->getContentDescription(); if (owner->getParty()) { owner->getParty()->broadcastPartyLoot(ss.str()); } else { owner->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); } } } corpse->startDecaying(); } std::vector MonsterType::createLootItem(const LootBlock& lootBlock) { int32_t itemCount = 0; uint32_t randvalue = Monsters::getLootRandom(); uint32_t extraMoney = g_config.getNumber(ConfigManager::MONEY_RATE); uint32_t countMax = lootBlock.countmax + 1; if (randvalue < g_config.getNumber(ConfigManager::RATE_LOOT) * lootBlock.chance) { if (Item::items[lootBlock.id].stackable) { if (lootBlock.id == 3031) { countMax *= extraMoney; } itemCount = randvalue % countMax; } else { itemCount = 1; } } std::vector itemList; while (itemCount > 0) { uint16_t n = static_cast(std::min(itemCount, 100)); Item* tmpItem = Item::CreateItem(lootBlock.id, n); if (!tmpItem) { break; } itemCount -= n; if (lootBlock.subType != -1) { tmpItem->setSubType(lootBlock.subType); } if (lootBlock.actionId != -1) { tmpItem->setActionId(lootBlock.actionId); } if (!lootBlock.text.empty()) { tmpItem->setText(lootBlock.text); } itemList.push_back(tmpItem); } return itemList; } bool MonsterType::createLootContainer(Container* parent, const LootBlock& lootblock) { auto it = lootblock.childLoot.begin(), end = lootblock.childLoot.end(); if (it == end) { return true; } for (; it != end && parent->size() < parent->capacity(); ++it) { auto itemList = createLootItem(*it); for (Item* tmpItem : itemList) { if (Container* container = tmpItem->getContainer()) { if (!createLootContainer(container, *it)) { delete container; } else { parent->internalAddThing(container); } } else { parent->internalAddThing(tmpItem); } } } return !parent->empty(); } bool Monsters::loadFromXml(bool reloading /*= false*/) { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file("data/monster/monsters.xml"); if (!result) { printXMLError("Error - Monsters::loadFromXml", "data/monster/monsters.xml", result); return false; } loaded = true; std::list> monsterScriptList; for (auto monsterNode : doc.child("monsters").children()) { loadMonster("data/monster/" + std::string(monsterNode.attribute("file").as_string()), monsterNode.attribute("name").as_string(), monsterScriptList, reloading); } if (!monsterScriptList.empty()) { if (!scriptInterface) { scriptInterface.reset(new LuaScriptInterface("Monster Interface")); scriptInterface->initState(); } for (const auto& scriptEntry : monsterScriptList) { MonsterType* mType = scriptEntry.first; if (scriptInterface->loadFile("data/monster/scripts/" + scriptEntry.second) == 0) { mType->info.scriptInterface = scriptInterface.get(); mType->info.creatureAppearEvent = scriptInterface->getEvent("onCreatureAppear"); mType->info.creatureDisappearEvent = scriptInterface->getEvent("onCreatureDisappear"); mType->info.creatureMoveEvent = scriptInterface->getEvent("onCreatureMove"); mType->info.creatureSayEvent = scriptInterface->getEvent("onCreatureSay"); mType->info.thinkEvent = scriptInterface->getEvent("onThink"); } else { std::cout << "[Warning - Monsters::loadMonster] Can not load script: " << scriptEntry.second << std::endl; std::cout << scriptInterface->getLastLuaError() << std::endl; } } } return true; } bool Monsters::reload() { loaded = false; scriptInterface.reset(); return loadFromXml(true); } ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, int32_t cycle, int32_t count, int32_t max_count) { ConditionDamage* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0, 0)); condition->setParam(CONDITION_PARAM_CYCLE, cycle); condition->setParam(CONDITION_PARAM_COUNT, count); condition->setParam(CONDITION_PARAM_MAX_COUNT, max_count); return condition; } bool Monsters::deserializeSpell(const pugi::xml_node& node, spellBlock_t& sb, const std::string& description) { std::string name; std::string scriptName; bool isScripted; pugi::xml_attribute attr; if ((attr = node.attribute("script"))) { scriptName = attr.as_string(); isScripted = true; } else if ((attr = node.attribute("name"))) { name = attr.as_string(); isScripted = false; } else { return false; } if ((attr = node.attribute("chance"))) { uint32_t chance = pugi::cast(attr.value()); if (chance > 100) { chance = 100; } if (chance == 0) { std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Spell chance is zero: " << name << std::endl; } sb.chance = chance; } if ((attr = node.attribute("range"))) { uint32_t range = pugi::cast(attr.value()); if (range > (Map::maxViewportX * 2)) { range = Map::maxViewportX * 2; } sb.range = range; } else { sb.range = Map::maxClientViewportX; } if ((attr = node.attribute("min"))) { sb.minCombatValue = pugi::cast(attr.value()); } if ((attr = node.attribute("max"))) { sb.maxCombatValue = pugi::cast(attr.value()); //normalize values if (std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) { int32_t value = sb.maxCombatValue; sb.maxCombatValue = sb.minCombatValue; sb.minCombatValue = value; } } if (auto spell = g_spells->getSpellByName(name)) { sb.spell = spell; return true; } CombatSpell* combatSpell = nullptr; bool needTarget = false; bool needDirection = false; if (isScripted) { if ((attr = node.attribute("direction"))) { needDirection = attr.as_bool(); } if ((attr = node.attribute("target"))) { needTarget = attr.as_bool(); } std::unique_ptr combatSpellPtr(new CombatSpell(nullptr, needTarget, needDirection)); if (!combatSpellPtr->loadScript("data/" + g_spells->getScriptBaseName() + "/scripts/" + scriptName)) { return false; } if (!combatSpellPtr->loadScriptCombat()) { return false; } combatSpell = combatSpellPtr.release(); combatSpell->getCombat()->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); } else { Combat* combat = new Combat; if ((attr = node.attribute("length"))) { int32_t length = pugi::cast(attr.value()); if (length > 0) { int32_t spread = 3; //need direction spell if ((attr = node.attribute("spread"))) { spread = std::max(0, pugi::cast(attr.value())); } AreaCombat* area = new AreaCombat(); area->setupArea(length, spread); combat->setArea(area); needDirection = true; } } if ((attr = node.attribute("radius"))) { int32_t radius = pugi::cast(attr.value()); //target spell if ((attr = node.attribute("target"))) { needTarget = attr.as_bool(); } AreaCombat* area = new AreaCombat(); area->setupArea(radius); combat->setArea(area); } std::string tmpName = asLowerCaseString(name); if (tmpName == "physical") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); combat->setParam(COMBAT_PARAM_BLOCKARMOR, 1); combat->setParam(COMBAT_PARAM_BLOCKSHIELD, 1); } else if (tmpName == "bleed") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE); } else if (tmpName == "poison" || tmpName == "earth") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_EARTHDAMAGE); } else if (tmpName == "fire") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_FIREDAMAGE); } else if (tmpName == "energy") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_ENERGYDAMAGE); } else if (tmpName == "lifedrain") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_LIFEDRAIN); } else if (tmpName == "manadrain") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_MANADRAIN); } else if (tmpName == "healing") { combat->setParam(COMBAT_PARAM_TYPE, COMBAT_HEALING); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); } else if (tmpName == "speed") { int32_t speedChange = 0; int32_t variation = 0; int32_t duration = 10000; if ((attr = node.attribute("duration"))) { duration = pugi::cast(attr.value()); } if ((attr = node.attribute("speedchange"))) { speedChange = pugi::cast(attr.value()); } if ((attr = node.attribute("variation"))) { variation = pugi::cast(attr.value()); } ConditionType_t conditionType; if (speedChange > 0) { conditionType = CONDITION_HASTE; combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); } else { conditionType = CONDITION_PARALYZE; } ConditionSpeed* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, duration, 0)); condition->setVariation(variation); condition->setSpeedDelta(speedChange); combat->setCondition(condition); } else if (tmpName == "outfit") { int32_t duration = 10000; if ((attr = node.attribute("duration"))) { duration = pugi::cast(attr.value()); } if ((attr = node.attribute("monster"))) { MonsterType* mType = g_monsters.getMonsterType(attr.as_string()); if (mType) { ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(mType->info.outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); combat->setCondition(condition); } } else if ((attr = node.attribute("item"))) { Outfit_t outfit; outfit.lookTypeEx = pugi::cast(attr.value()); ConditionOutfit* condition = static_cast(Condition::createCondition(CONDITIONID_COMBAT, CONDITION_OUTFIT, duration, 0)); condition->setOutfit(outfit); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); combat->setCondition(condition); } } else if (tmpName == "invisible") { int32_t duration = 10000; if ((attr = node.attribute("duration"))) { duration = pugi::cast(attr.value()); } Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration, 0); combat->setParam(COMBAT_PARAM_AGGRESSIVE, 0); combat->setCondition(condition); } else if (tmpName == "drunk") { int32_t duration = 10000; if ((attr = node.attribute("duration"))) { duration = pugi::cast(attr.value()); } Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration, 0); combat->setCondition(condition); } else if (tmpName == "firefield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_FIREFIELD_PVP_FULL); } else if (tmpName == "poisonfield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_POISONFIELD_PVP); } else if (tmpName == "energyfield") { combat->setParam(COMBAT_PARAM_CREATEITEM, ITEM_ENERGYFIELD_PVP); } else if (tmpName == "firecondition" || tmpName == "energycondition" || tmpName == "earthcondition" || tmpName == "poisoncondition" || tmpName == "icecondition" || tmpName == "freezecondition" || tmpName == "physicalcondition") { ConditionType_t conditionType = CONDITION_NONE; if (tmpName == "firecondition") { conditionType = CONDITION_FIRE; } else if (tmpName == "poisoncondition" || tmpName == "earthcondition") { conditionType = CONDITION_POISON; } else if (tmpName == "energycondition") { conditionType = CONDITION_ENERGY; } int32_t cycle = 0; if ((attr = node.attribute("count"))) { cycle = std::abs(pugi::cast(attr.value())); } else { std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - missing count attribute" << std::endl; delete combat; return false; } int32_t count = 0; if (conditionType == CONDITION_POISON) { count = 3; } else if (conditionType == CONDITION_FIRE) { count = 8; cycle /= 10; } else if (conditionType == CONDITION_ENERGY) { count = 10; cycle /= 20; } Condition* condition = getDamageCondition(conditionType, cycle, count, count); combat->setCondition(condition); } else if (tmpName == "strength") { // } else if (tmpName == "effect") { // } else { std::cout << "[Error - Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << name << std::endl; delete combat; return false; } combat->setPlayerCombatValues(COMBAT_FORMULA_DAMAGE, sb.minCombatValue, 0, sb.maxCombatValue, 0); combatSpell = new CombatSpell(combat, needTarget, needDirection); for (auto attributeNode : node.children()) { if ((attr = attributeNode.attribute("key"))) { const char* value = attr.value(); if (strcasecmp(value, "shooteffect") == 0) { if ((attr = attributeNode.attribute("value"))) { ShootType_t shoot = getShootType(attr.as_string()); if (shoot != CONST_ANI_NONE) { combat->setParam(COMBAT_PARAM_DISTANCEEFFECT, shoot); } else { std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Unknown shootEffect: " << attr.as_string() << std::endl; } } } else if (strcasecmp(value, "areaeffect") == 0) { if ((attr = attributeNode.attribute("value"))) { MagicEffectClasses effect = getMagicEffect(attr.as_string()); if (effect != CONST_ME_NONE) { combat->setParam(COMBAT_PARAM_EFFECT, effect); } else { std::cout << "[Warning - Monsters::deserializeSpell] " << description << " - Unknown areaEffect: " << attr.as_string() << std::endl; } } } else { std::cout << "[Warning - Monsters::deserializeSpells] Effect type \"" << attr.as_string() << "\" does not exist." << std::endl; } } } } sb.spell = combatSpell; if (combatSpell) { sb.combatSpell = true; } return true; } bool Monsters::loadMonster(const std::string& file, const std::string& monsterName, std::list>& monsterScriptList, bool reloading /*= false*/) { MonsterType* mType = nullptr; bool new_mType = true; pugi::xml_document doc; pugi::xml_parse_result result = doc.load_file(file.c_str()); if (!result) { printXMLError("Error - Monsters::loadMonster", file, result); return false; } pugi::xml_node monsterNode = doc.child("monster"); if (!monsterNode) { std::cout << "[Error - Monsters::loadMonster] Missing monster node in: " << file << std::endl; return false; } pugi::xml_attribute attr; if (!(attr = monsterNode.attribute("name"))) { std::cout << "[Error - Monsters::loadMonster] Missing name in: " << file << std::endl; return false; } if (reloading) { mType = getMonsterType(monsterName); if (mType != nullptr) { new_mType = false; mType->info = {}; } } if (new_mType) { mType = &monsters[asLowerCaseString(monsterName)]; } mType->name = attr.as_string(); if ((attr = monsterNode.attribute("nameDescription"))) { mType->nameDescription = attr.as_string(); } else { mType->nameDescription = "a " + asLowerCaseString(mType->name); } if ((attr = monsterNode.attribute("race"))) { std::string tmpStrValue = asLowerCaseString(attr.as_string()); uint16_t tmpInt = pugi::cast(attr.value()); if (tmpStrValue == "venom" || tmpInt == 1) { mType->info.race = RACE_VENOM; } else if (tmpStrValue == "blood" || tmpInt == 2) { mType->info.race = RACE_BLOOD; } else if (tmpStrValue == "undead" || tmpInt == 3) { mType->info.race = RACE_UNDEAD; } else if (tmpStrValue == "fire" || tmpInt == 4) { mType->info.race = RACE_FIRE; } else { std::cout << "[Warning - Monsters::loadMonster] Unknown race type " << attr.as_string() << ". " << file << std::endl; } } if ((attr = monsterNode.attribute("experience"))) { mType->info.experience = pugi::cast(attr.value()); } if ((attr = monsterNode.attribute("speed"))) { mType->info.baseSpeed = pugi::cast(attr.value()); } if ((attr = monsterNode.attribute("manacost"))) { mType->info.manaCost = pugi::cast(attr.value()); } if ((attr = monsterNode.attribute("skull"))) { mType->info.skull = getSkullType(attr.as_string()); } if ((attr = monsterNode.attribute("script"))) { monsterScriptList.emplace_back(mType, attr.as_string()); } pugi::xml_node node; if ((node = monsterNode.child("health"))) { if ((attr = node.attribute("now"))) { mType->info.health = pugi::cast(attr.value()); } else { std::cout << "[Error - Monsters::loadMonster] Missing health now. " << file << std::endl; } if ((attr = node.attribute("max"))) { mType->info.healthMax = pugi::cast(attr.value()); } else { std::cout << "[Error - Monsters::loadMonster] Missing health max. " << file << std::endl; } } if ((node = monsterNode.child("flags"))) { for (auto flagNode : node.children()) { attr = flagNode.first_attribute(); const char* attrName = attr.name(); if (strcasecmp(attrName, "summonable") == 0) { mType->info.isSummonable = attr.as_bool(); } else if (strcasecmp(attrName, "attackable") == 0) { mType->info.isAttackable = attr.as_bool(); } else if (strcasecmp(attrName, "hostile") == 0) { mType->info.isHostile = attr.as_bool(); } else if (strcasecmp(attrName, "illusionable") == 0) { mType->info.isIllusionable = attr.as_bool(); } else if (strcasecmp(attrName, "convinceable") == 0) { mType->info.isConvinceable = attr.as_bool(); } else if (strcasecmp(attrName, "pushable") == 0) { mType->info.pushable = attr.as_bool(); } else if (strcasecmp(attrName, "canpushitems") == 0) { mType->info.canPushItems = attr.as_bool(); } else if (strcasecmp(attrName, "canpushcreatures") == 0) { mType->info.canPushCreatures = attr.as_bool(); } else if (strcasecmp(attrName, "lightlevel") == 0) { mType->info.light.level = pugi::cast(attr.value()); } else if (strcasecmp(attrName, "lightcolor") == 0) { mType->info.light.color = pugi::cast(attr.value()); } else if (strcasecmp(attrName, "targetdistance") == 0) { mType->info.targetDistance = std::max(1, pugi::cast(attr.value())); } else if (strcasecmp(attrName, "runonhealth") == 0) { mType->info.runAwayHealth = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Unknown flag attribute: " << attrName << ". " << file << std::endl; } } //if a monster can push creatures, // it should not be pushable if (mType->info.canPushCreatures) { mType->info.pushable = false; } } if ((node = monsterNode.child("targetchange"))) { if ((attr = node.attribute("speed")) || (attr = node.attribute("interval"))) { mType->info.changeTargetSpeed = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Missing targetchange speed. " << file << std::endl; } if ((attr = node.attribute("chance"))) { mType->info.changeTargetChance = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Missing targetchange chance. " << file << std::endl; } } if ((node = monsterNode.child("targetstrategy"))) { if ((attr = node.attribute("nearest"))) { mType->info.strategyNearestEnemy = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Missing nearest enemy chance. " << file << std::endl; } if ((attr = node.attribute("weakest"))) { mType->info.strategyWeakestEnemy = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Missing weakest enemy chance. " << file << std::endl; } if ((attr = node.attribute("mostdamage"))) { mType->info.strategyMostDamageEnemy = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Missing most damage enemy chance. " << file << std::endl; } if ((attr = node.attribute("random"))) { mType->info.strategyRandomEnemy = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Missing random enemy chance. " << file << std::endl; } } else { std::cout << "[Warning - Monsters::loadMonster] Missing target change strategies. " << file << std::endl; } if ((node = monsterNode.child("look"))) { if ((attr = node.attribute("type"))) { mType->info.outfit.lookType = pugi::cast(attr.value()); if ((attr = node.attribute("head"))) { mType->info.outfit.lookHead = pugi::cast(attr.value()); } if ((attr = node.attribute("body"))) { mType->info.outfit.lookBody = pugi::cast(attr.value()); } if ((attr = node.attribute("legs"))) { mType->info.outfit.lookLegs = pugi::cast(attr.value()); } if ((attr = node.attribute("feet"))) { mType->info.outfit.lookFeet = pugi::cast(attr.value()); } } else if ((attr = node.attribute("typeex"))) { mType->info.outfit.lookTypeEx = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Missing look type/typeex. " << file << std::endl; } if ((attr = node.attribute("corpse"))) { mType->info.lookcorpse = pugi::cast(attr.value()); } } if ((node = monsterNode.child("attacks"))) { if ((attr = node.attribute("attack"))) { mType->info.attack = pugi::cast(attr.value()); } if ((attr = node.attribute("skill"))) { mType->info.skill = pugi::cast(attr.value()); } if ((attr = node.attribute("poison"))) { mType->info.poison = pugi::cast(attr.value()); } for (auto attackNode : node.children()) { spellBlock_t sb; if (deserializeSpell(attackNode, sb, monsterName)) { mType->info.attackSpells.emplace_back(std::move(sb)); } else { std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << file << std::endl; } } } if ((node = monsterNode.child("defenses"))) { if ((attr = node.attribute("defense"))) { mType->info.defense = pugi::cast(attr.value()); } if ((attr = node.attribute("armor"))) { mType->info.armor = pugi::cast(attr.value()); } for (auto defenseNode : node.children()) { spellBlock_t sb; if (deserializeSpell(defenseNode, sb, monsterName)) { mType->info.defenseSpells.emplace_back(std::move(sb)); } else { std::cout << "[Warning - Monsters::loadMonster] Cant load spell. " << file << std::endl; } } } if ((node = monsterNode.child("immunities"))) { for (auto immunityNode : node.children()) { if ((attr = immunityNode.attribute("name"))) { std::string tmpStrValue = asLowerCaseString(attr.as_string()); if (tmpStrValue == "physical") { mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; } else if (tmpStrValue == "energy") { mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; mType->info.conditionImmunities |= CONDITION_ENERGY; } else if (tmpStrValue == "fire") { mType->info.damageImmunities |= COMBAT_FIREDAMAGE; mType->info.conditionImmunities |= CONDITION_FIRE; } else if (tmpStrValue == "poison" || tmpStrValue == "earth") { mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; mType->info.conditionImmunities |= CONDITION_POISON; } else if (tmpStrValue == "lifedrain") { mType->info.damageImmunities |= COMBAT_LIFEDRAIN; } else if (tmpStrValue == "manadrain") { mType->info.damageImmunities |= COMBAT_MANADRAIN; } else if (tmpStrValue == "paralyze") { mType->info.conditionImmunities |= CONDITION_PARALYZE; } else if (tmpStrValue == "outfit") { mType->info.conditionImmunities |= CONDITION_OUTFIT; } else if (tmpStrValue == "drunk") { mType->info.conditionImmunities |= CONDITION_DRUNK; } else if (tmpStrValue == "invisible" || tmpStrValue == "invisibility") { mType->info.conditionImmunities |= CONDITION_INVISIBLE; } else { std::cout << "[Warning - Monsters::loadMonster] Unknown immunity name " << attr.as_string() << ". " << file << std::endl; } } else if ((attr = immunityNode.attribute("physical"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_PHYSICALDAMAGE; } } else if ((attr = immunityNode.attribute("energy"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_ENERGYDAMAGE; mType->info.conditionImmunities |= CONDITION_ENERGY; } } else if ((attr = immunityNode.attribute("fire"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_FIREDAMAGE; mType->info.conditionImmunities |= CONDITION_FIRE; } } else if ((attr = immunityNode.attribute("poison")) || (attr = immunityNode.attribute("earth"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_EARTHDAMAGE; mType->info.conditionImmunities |= CONDITION_POISON; } } else if ((attr = immunityNode.attribute("lifedrain"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_LIFEDRAIN; } } else if ((attr = immunityNode.attribute("manadrain"))) { if (attr.as_bool()) { mType->info.damageImmunities |= COMBAT_MANADRAIN; } } else if ((attr = immunityNode.attribute("paralyze"))) { if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_PARALYZE; } } else if ((attr = immunityNode.attribute("outfit"))) { if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_OUTFIT; } } else if ((attr = immunityNode.attribute("drunk"))) { if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_DRUNK; } } else if ((attr = immunityNode.attribute("invisible")) || (attr = immunityNode.attribute("invisibility"))) { if (attr.as_bool()) { mType->info.conditionImmunities |= CONDITION_INVISIBLE; } } else { std::cout << "[Warning - Monsters::loadMonster] Unknown immunity. " << file << std::endl; } } } if ((node = monsterNode.child("voices"))) { for (auto voiceNode : node.children()) { voiceBlock_t vb; if ((attr = voiceNode.attribute("sentence"))) { vb.text = attr.as_string(); } else { std::cout << "[Warning - Monsters::loadMonster] Missing voice sentence. " << file << std::endl; } if ((attr = voiceNode.attribute("yell"))) { vb.yellText = attr.as_bool(); } else { vb.yellText = false; } mType->info.voiceVector.emplace_back(vb); } } if ((node = monsterNode.child("loot"))) { for (auto lootNode : node.children()) { LootBlock lootBlock; if (loadLootItem(lootNode, lootBlock)) { mType->info.lootItems.emplace_back(std::move(lootBlock)); } else { std::cout << "[Warning - Monsters::loadMonster] Cant load loot. " << file << std::endl; } } } if ((node = monsterNode.child("elements"))) { for (auto elementNode : node.children()) { if ((attr = elementNode.attribute("physicalPercent"))) { mType->info.elementMap[COMBAT_PHYSICALDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("poisonPercent")) || (attr = elementNode.attribute("earthPercent"))) { mType->info.elementMap[COMBAT_EARTHDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("firePercent"))) { mType->info.elementMap[COMBAT_FIREDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("energyPercent"))) { mType->info.elementMap[COMBAT_ENERGYDAMAGE] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("lifedrainPercent"))) { mType->info.elementMap[COMBAT_LIFEDRAIN] = pugi::cast(attr.value()); } else if ((attr = elementNode.attribute("manadrainPercent"))) { mType->info.elementMap[COMBAT_MANADRAIN] = pugi::cast(attr.value()); } else { std::cout << "[Warning - Monsters::loadMonster] Unknown element percent. " << file << std::endl; } } } if ((node = monsterNode.child("summons"))) { if ((attr = node.attribute("maxSummons"))) { mType->info.maxSummons = std::min(pugi::cast(attr.value()), 100); } else { std::cout << "[Warning - Monsters::loadMonster] Missing summons maxSummons. " << file << std::endl; } for (auto summonNode : node.children()) { int32_t chance = 100; int32_t max = mType->info.maxSummons; bool force = false; if ((attr = summonNode.attribute("chance"))) { chance = pugi::cast(attr.value()); } if ((attr = summonNode.attribute("max"))) { max = pugi::cast(attr.value()); } if ((attr = summonNode.attribute("force"))) { force = attr.as_bool(); } if ((attr = summonNode.attribute("name"))) { summonBlock_t sb; sb.name = attr.as_string(); sb.chance = chance; sb.max = max; sb.force = force; mType->info.summons.emplace_back(sb); } else { std::cout << "[Warning - Monsters::loadMonster] Missing summon name. " << file << std::endl; } } } if ((node = monsterNode.child("script"))) { for (auto eventNode : node.children()) { if ((attr = eventNode.attribute("name"))) { mType->info.scripts.emplace_back(attr.as_string()); } else { std::cout << "[Warning - Monsters::loadMonster] Missing name for script event. " << file << std::endl; } } } mType->info.summons.shrink_to_fit(); mType->info.lootItems.shrink_to_fit(); mType->info.attackSpells.shrink_to_fit(); mType->info.defenseSpells.shrink_to_fit(); mType->info.voiceVector.shrink_to_fit(); mType->info.scripts.shrink_to_fit(); return true; } bool Monsters::loadLootItem(const pugi::xml_node& node, LootBlock& lootBlock) { pugi::xml_attribute attr; if ((attr = node.attribute("id"))) { lootBlock.id = pugi::cast(attr.value()); } else if ((attr = node.attribute("name"))) { auto name = attr.as_string(); auto ids = Item::items.nameToItems.equal_range(asLowerCaseString(name)); if (ids.first == Item::items.nameToItems.cend()) { std::cout << "[Warning - Monsters::loadMonster] Unknown loot item \"" << name << "\". " << std::endl; return false; } uint32_t id = ids.first->second; if (std::next(ids.first) != ids.second) { std::cout << "[Warning - Monsters::loadMonster] Non-unique loot item \"" << name << "\". " << std::endl; return false; } lootBlock.id = id; } if (lootBlock.id == 0) { return false; } if ((attr = node.attribute("countmax"))) { lootBlock.countmax = std::max(1, pugi::cast(attr.value())); } else { lootBlock.countmax = 1; } if ((attr = node.attribute("chance")) || (attr = node.attribute("chance1"))) { lootBlock.chance = std::min(MAX_LOOTCHANCE, pugi::cast(attr.value())); } else { lootBlock.chance = MAX_LOOTCHANCE; } if (Item::items[lootBlock.id].isContainer()) { loadLootContainer(node, lootBlock); } //optional if ((attr = node.attribute("subtype"))) { lootBlock.subType = pugi::cast(attr.value()); } else { uint32_t charges = Item::items[lootBlock.id].charges; if (charges != 0) { lootBlock.subType = charges; } } if ((attr = node.attribute("actionId"))) { lootBlock.actionId = pugi::cast(attr.value()); } if ((attr = node.attribute("text"))) { lootBlock.text = attr.as_string(); } return true; } void Monsters::loadLootContainer(const pugi::xml_node& node, LootBlock& lBlock) { for (auto subNode : node.children()) { LootBlock lootBlock; if (loadLootItem(subNode, lootBlock)) { lBlock.childLoot.emplace_back(std::move(lootBlock)); } } } MonsterType* Monsters::getMonsterType(const std::string& name) { auto it = monsters.find(asLowerCaseString(name)); if (it == monsters.end()) { return nullptr; } return &it->second; }