//////////////////////////////////////////////////////////////////////// // OpenTibia - an opensource roleplaying game //////////////////////////////////////////////////////////////////////// // 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 3 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, see . //////////////////////////////////////////////////////////////////////// #include "otpch.h" #include #include #include "monsters.h" #include "tools.h" #include "monster.h" #include "luascript.h" #include "container.h" #include "weapons.h" #include "spells.h" #include "combat.h" #include "configmanager.h" #include "game.h" extern Game g_game; extern Spells* g_spells; extern Monsters g_monsters; extern ConfigManager g_config; void MonsterType::reset() { canPushItems = canPushCreatures = isSummonable = isIllusionable = isConvinceable = isLureable = hideName = hideHealth = false; pushable = isAttackable = isHostile = true; outfit.lookHead = outfit.lookBody = outfit.lookLegs = outfit.lookFeet = outfit.lookType = outfit.lookTypeEx = outfit.lookAddons = 0; experience = defense = armor = lookCorpse = conditionImmunities = damageImmunities = 0; maxSummons = runAwayHealth = manaCost = lightLevel = lightColor = 0; yellSpeedTicks = yellChance = changeTargetSpeed = changeTargetChance = 0; targetDistance = 1; staticAttackChance = 95; health = healthMax = 100; baseSpeed = 200; race = RACE_BLOOD; skull = SKULL_NONE; partyShield = SHIELD_NONE; for(SpellList::iterator it = spellAttackList.begin(); it != spellAttackList.end(); ++it) { if(it->combatSpell) { delete it->spell; it->spell = NULL; } } spellAttackList.clear(); for(SpellList::iterator it = spellDefenseList.begin(); it != spellDefenseList.end(); ++it) { if(it->combatSpell) { delete it->spell; it->spell = NULL; } } spellDefenseList.clear(); voiceVector.clear(); scriptList.clear(); summonList.clear(); lootItems.clear(); elementMap.clear(); } uint32_t Monsters::getLootRandom() { return (uint32_t)std::ceil((double)random_range(0, MAX_LOOTCHANCE) / g_config.getDouble(ConfigManager::RATE_LOOT)); } void MonsterType::createLoot(Container* corpse) { ItemVector itemVector; for(LootItems::const_iterator it = lootItems.begin(); it != lootItems.end() && (corpse->capacity() - corpse->size() > 0); it++) { if(Item* tmpItem = createLootItem(*it)) { if(Container* container = tmpItem->getContainer()) { if(createLootContainer(container, (*it), itemVector)) { corpse->__internalAddThing(tmpItem); itemVector.push_back(tmpItem); } else delete container; } else { corpse->__internalAddThing(tmpItem); itemVector.push_back(tmpItem); } } } corpse->__startDecaying(); uint32_t ownerId = corpse->getCorpseOwner(); if(ownerId) { Player* owner = NULL; if((owner = g_game.getPlayerByID(ownerId)) && owner->getParty()) owner->getParty()->broadcastPartyLoot(nameDescription, itemVector); } } Item* MonsterType::createLootItem(const LootBlock& lootBlock) { Item* tmpItem = NULL; if(Item::items[lootBlock.id].stackable) { uint32_t randvalue = Monsters::getLootRandom(); if(randvalue < lootBlock.chance) { uint32_t n = randvalue % lootBlock.countmax + 1; tmpItem = Item::CreateItem(lootBlock.id, n); } } else { if(Monsters::getLootRandom() < lootBlock.chance) tmpItem = Item::CreateItem(lootBlock.id, 0); } if(tmpItem) { if(lootBlock.subType != -1) tmpItem->setSubType(lootBlock.subType); if(lootBlock.actionId != -1) tmpItem->setActionId(lootBlock.actionId); if(lootBlock.uniqueId != -1) tmpItem->setUniqueId(lootBlock.uniqueId); if(lootBlock.text != "") tmpItem->setText(lootBlock.text); return tmpItem; } return NULL; } bool MonsterType::createLootContainer(Container* parent, const LootBlock& lootblock, ItemVector& itemVector) { LootItems::const_iterator it = lootblock.childLoot.begin(); if(it == lootblock.childLoot.end()) return true; for(; it != lootblock.childLoot.end() && parent->size() < parent->capacity(); ++it) { if(Item* tmpItem = createLootItem((*it))) { if(Container* container = tmpItem->getContainer()) { if(createLootContainer(container, (*it), itemVector)) { parent->__internalAddThing(container); itemVector.push_back(tmpItem); } else delete container; } else { parent->__internalAddThing(tmpItem); itemVector.push_back(tmpItem); } } } return parent->size() != 0; } bool Monsters::loadFromXml(bool reloading /*= false*/) { loaded = false; xmlDocPtr doc = xmlParseFile(getFilePath(FILE_TYPE_OTHER, "monster/monsters.xml").c_str()); if(!doc) { std::cout << "[Warning - Monsters::loadFromXml] Cannot load monsters file." << std::endl; std::cout << getLastXMLError() << std::endl; return false; } xmlNodePtr p, root = xmlDocGetRootElement(doc); if(xmlStrcmp(root->name,(const xmlChar*)"monsters")) { std::cout << "[Error - Monsters::loadFromXml] Malformed monsters file." << std::endl; xmlFreeDoc(doc); return false; } p = root->children; while(p) { if(p->type != XML_ELEMENT_NODE) { p = p->next; continue; } if(xmlStrcmp(p->name, (const xmlChar*)"monster")) { std::cout << "[Warning - Monsters::loadFromXml] Unknown node name (" << p->name << ")." << std::endl; p = p->next; continue; } std::string file, name; if(readXMLString(p, "file", file) && readXMLString(p, "name", name)) { file = getFilePath(FILE_TYPE_OTHER, "monster/" + file); loadMonster(file, name, reloading); } p = p->next; } xmlFreeDoc(doc); loaded = true; return loaded; } ConditionDamage* Monsters::getDamageCondition(ConditionType_t conditionType, int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval) { if(ConditionDamage* condition = dynamic_cast(Condition::createCondition(CONDITIONID_COMBAT, conditionType, 0))) { condition->setParam(CONDITIONPARAM_TICKINTERVAL, tickInterval); condition->setParam(CONDITIONPARAM_MINVALUE, minDamage); condition->setParam(CONDITIONPARAM_MAXVALUE, maxDamage); condition->setParam(CONDITIONPARAM_STARTVALUE, startDamage); condition->setParam(CONDITIONPARAM_DELAYED, 1); return condition; } return NULL; } bool Monsters::deserializeSpell(xmlNodePtr node, spellBlock_t& sb, const std::string& description) { sb.chance = 100; sb.speed = 2000; sb.range = 0; sb.minCombatValue = 0; sb.maxCombatValue = 0; sb.combatSpell = false; sb.isMelee = false; std::string name = "", scriptName = ""; bool isScripted = false; if(readXMLString(node, "script", scriptName)) isScripted = true; else if(!readXMLString(node, "name", name)) return false; int32_t intValue; std::string strValue; if(readXMLInteger(node, "speed", intValue) || readXMLInteger(node, "interval", intValue)) sb.speed = std::max(1, intValue); if(readXMLInteger(node, "chance", intValue)) { if(intValue < 0 || intValue > 100) intValue = 100; sb.chance = intValue; } if(readXMLInteger(node, "range", intValue)) { if(intValue < 0 ) intValue = 0; if(intValue > Map::maxViewportX * 2) intValue = Map::maxViewportX * 2; sb.range = intValue; } if(readXMLInteger(node, "min", intValue)) sb.minCombatValue = intValue; if(readXMLInteger(node, "max", intValue)) { sb.maxCombatValue = intValue; //normalize values if(std::abs(sb.minCombatValue) > std::abs(sb.maxCombatValue)) { int32_t value = sb.maxCombatValue; sb.maxCombatValue = sb.minCombatValue; sb.minCombatValue = value; } } if((sb.spell = g_spells->getSpellByName(name))) return true; CombatSpell* combatSpell = NULL; bool needTarget = false, needDirection = false; if(isScripted) { if(readXMLString(node, "direction", strValue)) needDirection = booleanString(strValue); if(readXMLString(node, "target", strValue)) needTarget = booleanString(strValue); combatSpell = new CombatSpell(NULL, needTarget, needDirection); if(!combatSpell->loadScript(getFilePath(FILE_TYPE_OTHER, g_spells->getScriptBaseName() + "/scripts/" + scriptName), true)) { delete combatSpell; return false; } if(!combatSpell->loadScriptCombat()) { delete combatSpell; return false; } combatSpell->getCombat()->setPlayerCombatValues(FORMULA_VALUE, sb.minCombatValue, 0, sb.maxCombatValue, 0); } else { Combat* combat = new Combat; sb.combatSpell = true; if(readXMLInteger(node, "length", intValue)) { int32_t length = intValue; if(length > 0) { int32_t spread = 3; //need direction spell if(readXMLInteger(node, "spread", intValue)) spread = std::max(0, intValue); AreaCombat* area = new AreaCombat(); area->setupArea(length, spread); combat->setArea(area); needDirection = true; } } if(readXMLInteger(node, "radius", intValue)) { int32_t radius = intValue; //target spell if(readXMLInteger(node, "target", intValue)) needTarget = (intValue != 0); AreaCombat* area = new AreaCombat(); area->setupArea(radius); combat->setArea(area); } std::string tmpName = asLowerCaseString(name); if(tmpName == "melee") { int32_t attack = 0, skill = 0; sb.isMelee = true; if(readXMLInteger(node, "attack", attack) && readXMLInteger(node, "skill", skill)) { sb.minCombatValue = 0; sb.maxCombatValue = -Weapons::getMaxMeleeDamage(skill, attack); } ConditionType_t conditionType = CONDITION_NONE; int32_t startDamage = 0, minDamage = 0, maxDamage = 0; uint32_t tickInterval = 2000; if(readXMLInteger(node, "fire", intValue)) { conditionType = CONDITION_FIRE; minDamage = intValue; maxDamage = intValue; tickInterval = 10000; } else if(readXMLInteger(node, "energy", intValue)) { conditionType = CONDITION_ENERGY; minDamage = intValue; maxDamage = intValue; tickInterval = 10000; } else if(readXMLInteger(node, "poison", intValue) || readXMLInteger(node, "earth", intValue)) { conditionType = CONDITION_POISON; minDamage = intValue; maxDamage = intValue; tickInterval = 5000; } else if(readXMLInteger(node, "freeze", intValue)) { conditionType = CONDITION_FREEZING; minDamage = intValue; maxDamage = intValue; tickInterval = 10000; } else if(readXMLInteger(node, "dazzle", intValue)) { conditionType = CONDITION_DAZZLED; minDamage = intValue; maxDamage = intValue; tickInterval = 10000; } else if(readXMLInteger(node, "curse", intValue)) { conditionType = CONDITION_CURSED; minDamage = intValue; maxDamage = intValue; tickInterval = 10000; } else if(readXMLInteger(node, "drown", intValue)) { conditionType = CONDITION_DROWN; minDamage = intValue; maxDamage = intValue; tickInterval = 10000; } else if(readXMLInteger(node, "physical", intValue)) { conditionType = CONDITION_PHYSICAL; minDamage = intValue; maxDamage = intValue; tickInterval = 10000; } if(readXMLInteger(node, "tick", intValue) && intValue > 0) tickInterval = intValue; if(conditionType != CONDITION_NONE) { Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, startDamage, tickInterval); if(condition) combat->setCondition(condition); } sb.range = 1; combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_PHYSICALDAMAGE); combat->setParam(COMBATPARAM_BLOCKEDBYARMOR, 1); combat->setParam(COMBATPARAM_BLOCKEDBYSHIELD, 1); } else if(tmpName == "physical") { combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_PHYSICALDAMAGE); combat->setParam(COMBATPARAM_BLOCKEDBYARMOR, 1); } else if(tmpName == "drown") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_DROWNDAMAGE); else if(tmpName == "fire") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_FIREDAMAGE); else if(tmpName == "energy") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_ENERGYDAMAGE); else if(tmpName == "poison" || tmpName == "earth") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_EARTHDAMAGE); else if(tmpName == "ice") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_ICEDAMAGE); else if(tmpName == "holy") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_HOLYDAMAGE); else if(tmpName == "death") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_DEATHDAMAGE); else if(tmpName == "lifedrain") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_LIFEDRAIN); else if(tmpName == "manadrain") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_MANADRAIN); else if(tmpName == "healing") { combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_HEALING); combat->setParam(COMBATPARAM_AGGRESSIVE, 0); } else if(tmpName == "undefined") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_UNDEFINEDDAMAGE); else if(tmpName == "speed") { int32_t speedChange = 0, duration = 10000; if(readXMLInteger(node, "duration", intValue)) duration = intValue; if(readXMLInteger(node, "speedchange", intValue)) { speedChange = intValue; if(speedChange < -1000) { //cant be slower than 100% speedChange = -1000; } } ConditionType_t conditionType; if(speedChange > 0) { conditionType = CONDITION_HASTE; combat->setParam(COMBATPARAM_AGGRESSIVE, 0); } else conditionType = CONDITION_PARALYZE; if(ConditionSpeed* condition = dynamic_cast(Condition::createCondition( CONDITIONID_COMBAT, conditionType, duration))) { condition->setFormulaVars(speedChange / 1000.0, 0, speedChange / 1000.0, 0); combat->setCondition(condition); } } else if(tmpName == "outfit") { int32_t duration = 10000; if(readXMLInteger(node, "duration", intValue)) duration = intValue; if(readXMLString(node, "monster", strValue)) { MonsterType* mType = g_monsters.getMonsterType(strValue); if(mType) { if(ConditionOutfit* condition = dynamic_cast(Condition::createCondition( CONDITIONID_COMBAT, CONDITION_OUTFIT, duration))) { condition->addOutfit(mType->outfit); combat->setParam(COMBATPARAM_AGGRESSIVE, 0); combat->setCondition(condition); } } } else if(readXMLInteger(node, "item", intValue)) { Outfit_t outfit; outfit.lookTypeEx = intValue; if(ConditionOutfit* condition = dynamic_cast(Condition::createCondition( CONDITIONID_COMBAT, CONDITION_OUTFIT, duration))) { condition->addOutfit(outfit); combat->setParam(COMBATPARAM_AGGRESSIVE, 0); combat->setCondition(condition); } } } else if(tmpName == "invisible") { int32_t duration = 10000; if(readXMLInteger(node, "duration", intValue)) duration = intValue; if(Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration)) { combat->setParam(COMBATPARAM_AGGRESSIVE, 0); combat->setCondition(condition); } } else if(tmpName == "drunk") { int32_t duration = 10000; if(readXMLInteger(node, "duration", intValue)) duration = intValue; if(Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_DRUNK, duration)) combat->setCondition(condition); } else if(tmpName == "firefield") combat->setParam(COMBATPARAM_CREATEITEM, 1492); else if(tmpName == "poisonfield") combat->setParam(COMBATPARAM_CREATEITEM, 1496); else if(tmpName == "energyfield") combat->setParam(COMBATPARAM_CREATEITEM, 1495); else if(tmpName == "firecondition" || tmpName == "energycondition" || tmpName == "drowncondition" || tmpName == "poisoncondition" || tmpName == "earthcondition" || tmpName == "freezecondition" || tmpName == "cursecondition" || tmpName == "dazzlecondition") { ConditionType_t conditionType = CONDITION_NONE; uint32_t tickInterval = 2000; if(tmpName == "firecondition") { conditionType = CONDITION_FIRE; tickInterval = 10000; } else if(tmpName == "energycondition") { conditionType = CONDITION_ENERGY; tickInterval = 10000; } else if(tmpName == "poisoncondition" || tmpName == "earthcondition") { conditionType = CONDITION_POISON; tickInterval = 5000; } else if(tmpName == "freezecondition") { conditionType = CONDITION_FREEZING; tickInterval = 10000; } else if(tmpName == "cursecondition") { conditionType = CONDITION_CURSED; tickInterval = 10000; } else if(tmpName == "dazzlecondition") { conditionType = CONDITION_CURSED; tickInterval = 10000; } else if(tmpName == "drowncondition") { conditionType = CONDITION_DROWN; tickInterval = 5000; } else if(tmpName == "physicalcondition") { conditionType = CONDITION_PHYSICAL; tickInterval = 5000; } if(readXMLInteger(node, "tick", intValue) && intValue > 0) tickInterval = intValue; int32_t startDamage = 0, minDamage = std::abs(sb.minCombatValue), maxDamage = std::abs(sb.maxCombatValue); if(readXMLInteger(node, "start", intValue)) { intValue = std::abs(intValue); if(intValue <= minDamage) startDamage = intValue; } Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, startDamage, tickInterval); if(condition) combat->setCondition(condition); } else if(tmpName == "strength") { //TODO? } else { std::cout << "Error: [Monsters::deserializeSpell] - " << description << " - Unknown spell name: " << name << std::endl; delete combat; return false; } combat->setPlayerCombatValues(FORMULA_VALUE, sb.minCombatValue, 0, sb.maxCombatValue, 0); combatSpell = new CombatSpell(combat, needTarget, needDirection); xmlNodePtr attributeNode = node->children; while(attributeNode) { if(xmlStrcmp(attributeNode->name, (const xmlChar*)"attribute") == 0) { if(readXMLString(attributeNode, "key", strValue)) { std::string tmpStrValue = asLowerCaseString(strValue); if(tmpStrValue == "shooteffect") { if(readXMLString(attributeNode, "value", strValue)) { ShootType_t shoot = getShootType(strValue); if(shoot != NM_SHOOT_UNK) combat->setParam(COMBATPARAM_DISTANCEEFFECT, shoot); else std::cout << "Warning: [Monsters::deserializeSpell] - " << description << " - Unknown shootEffect: " << strValue << std::endl; } } else if(tmpStrValue == "areaeffect") { if(readXMLString(attributeNode, "value", strValue)) { MagicEffectClasses effect = getMagicEffect(strValue); if(effect != NM_ME_UNK) combat->setParam(COMBATPARAM_EFFECT, effect); else std::cout << "Warning: [Monsters::deserializeSpell] - " << description << " - Unknown areaEffect: " << strValue << std::endl; } } else std::cout << "[Warning - Monsters::deserializeSpells] Effect type \"" << strValue << "\" does not exist." << std::endl; } } attributeNode = attributeNode->next; } } sb.spell = combatSpell; return true; } #define SHOW_XML_WARNING(desc) std::cout << "[Warning - Monsters::loadMonster] " << desc << ". (" << file << ")" << std::endl; #define SHOW_XML_ERROR(desc) std::cout << "[Error - Monsters::loadMonster] " << desc << ". (" << file << ")" << std::endl; bool Monsters::loadMonster(const std::string& file, const std::string& monsterName, bool reloading /*= false*/) { if(getIdByName(monsterName) != 0 && !reloading) { std::cout << "[Warning - Monsters::loadMonster] Duplicate registered monster with name: " << monsterName << std::endl; return true; } bool monsterLoad, new_mType = true; MonsterType* mType = NULL; if(reloading) { uint32_t id = getIdByName(monsterName); if(id != 0) { mType = getMonsterType(id); if(mType != NULL) { new_mType = false; mType->reset(); } } } if(new_mType) mType = new MonsterType(); xmlDocPtr doc = xmlParseFile(file.c_str()); if(!doc) { std::cout << "[Warning - Monsters::loadMonster] Cannot load monster (" << monsterName << ") file (" << file << ")." << std::endl; std::cout << getLastXMLError() << std::endl; return false; } monsterLoad = true; xmlNodePtr p, root = xmlDocGetRootElement(doc); if(xmlStrcmp(root->name,(const xmlChar*)"monster")) { std::cout << "[Error - Monsters::loadMonster] Malformed monster (" << monsterName << ") file (" << file << ")." << std::endl; xmlFreeDoc(doc); return false; } int32_t intValue; std::string strValue; if(readXMLString(root, "name", strValue)) mType->name = strValue; else monsterLoad = false; if(readXMLString(root, "nameDescription", strValue)) mType->nameDescription = strValue; else { mType->nameDescription = "a " + mType->name; toLowerCaseString(mType->nameDescription); } if(readXMLString(root, "race", strValue)) { std::string tmpStrValue = asLowerCaseString(strValue); if(tmpStrValue == "venom" || atoi(strValue.c_str()) == 1) mType->race = RACE_VENOM; else if(tmpStrValue == "blood" || atoi(strValue.c_str()) == 2) mType->race = RACE_BLOOD; else if(tmpStrValue == "undead" || atoi(strValue.c_str()) == 3) mType->race = RACE_UNDEAD; else if(tmpStrValue == "fire" || atoi(strValue.c_str()) == 4) mType->race = RACE_FIRE; else if(tmpStrValue == "energy" || atoi(strValue.c_str()) == 5) mType->race = RACE_ENERGY; else SHOW_XML_WARNING("Unknown race type " << strValue); } if(readXMLInteger(root, "experience", intValue)) mType->experience = intValue; if(readXMLInteger(root, "speed", intValue)) mType->baseSpeed = intValue; if(readXMLInteger(root, "manacost", intValue)) mType->manaCost = intValue; if(readXMLString(root, "skull", strValue)) mType->skull = getSkull(strValue); if(readXMLString(root, "shield", strValue)) mType->partyShield = getPartyShield(strValue); p = root->children; while(p && monsterLoad) { if(p->type != XML_ELEMENT_NODE) { p = p->next; continue; } if(!xmlStrcmp(p->name, (const xmlChar*)"health")) { if(readXMLInteger(p, "now", intValue)) mType->health = intValue; else { SHOW_XML_ERROR("Missing health.now"); monsterLoad = false; } if(readXMLInteger(p, "max", intValue)) mType->healthMax = intValue; else { SHOW_XML_ERROR("Missing health.max"); monsterLoad = false; } } else if(!xmlStrcmp(p->name, (const xmlChar*)"flags")) { xmlNodePtr tmpNode = p->children; while(tmpNode) { if(xmlStrcmp(tmpNode->name, (const xmlChar*)"flag") == 0) { if(readXMLString(tmpNode, "summonable", strValue)) mType->isSummonable = booleanString(strValue); if(readXMLString(tmpNode, "attackable", strValue)) mType->isAttackable = booleanString(strValue); if(readXMLString(tmpNode, "hostile", strValue)) mType->isHostile = booleanString(strValue); if(readXMLString(tmpNode, "illusionable", strValue)) mType->isIllusionable = booleanString(strValue); if(readXMLString(tmpNode, "convinceable", strValue)) mType->isConvinceable = booleanString(strValue); if(readXMLString(tmpNode, "pushable", strValue)) mType->pushable = booleanString(strValue); if(readXMLString(tmpNode, "canpushitems", strValue)) mType->canPushItems = booleanString(strValue); if(readXMLString(tmpNode, "canpushcreatures", strValue)) mType->canPushCreatures = booleanString(strValue); if(readXMLString(tmpNode, "hidename", strValue)) mType->hideName = booleanString(strValue); if(readXMLString(tmpNode, "hidehealth", strValue)) mType->hideHealth = booleanString(strValue); if(readXMLInteger(tmpNode, "staticattack", intValue)) { if(intValue < 0 || intValue > 100) { SHOW_XML_WARNING("staticattack lower than 0 or greater than 100"); intValue = 0; } mType->staticAttackChance = intValue; } if(readXMLInteger(tmpNode, "lightlevel", intValue)) mType->lightLevel = intValue; if(readXMLInteger(tmpNode, "lightcolor", intValue)) mType->lightColor = intValue; if(readXMLInteger(tmpNode, "targetdistance", intValue)) { if(intValue > Map::maxViewportX) SHOW_XML_WARNING("targetdistance greater than maxViewportX"); mType->targetDistance = std::max(1, intValue); } if(readXMLInteger(tmpNode, "runonhealth", intValue)) mType->runAwayHealth = intValue; if(readXMLString(tmpNode, "lureable", strValue)) mType->isLureable = booleanString(strValue); if(readXMLString(tmpNode, "skull", strValue)) mType->skull = getSkull(strValue); if(readXMLString(tmpNode, "shield", strValue)) mType->partyShield = getPartyShield(strValue); } tmpNode = tmpNode->next; } //if a monster can push creatures, it should not be pushable if(mType->canPushCreatures && mType->pushable) mType->pushable = false; } else if(!xmlStrcmp(p->name, (const xmlChar*)"targetchange")) { if(readXMLInteger(p, "speed", intValue) || readXMLInteger(p, "interval", intValue)) mType->changeTargetSpeed = std::max(1, intValue); else SHOW_XML_WARNING("Missing targetchange.speed"); if(readXMLInteger(p, "chance", intValue)) mType->changeTargetChance = intValue; else SHOW_XML_WARNING("Missing targetchange.chance"); } else if(!xmlStrcmp(p->name, (const xmlChar*)"strategy")) { if(readXMLInteger(p, "attack", intValue)) {} //mType->attackStrength = intValue; if(readXMLInteger(p, "defense", intValue)) {} //mType->defenseStrength = intValue; } else if(!xmlStrcmp(p->name, (const xmlChar*)"look")) { if(readXMLInteger(p, "type", intValue)) { mType->outfit.lookType = intValue; if(readXMLInteger(p, "head", intValue)) mType->outfit.lookHead = intValue; if(readXMLInteger(p, "body", intValue)) mType->outfit.lookBody = intValue; if(readXMLInteger(p, "legs", intValue)) mType->outfit.lookLegs = intValue; if(readXMLInteger(p, "feet", intValue)) mType->outfit.lookFeet = intValue; if(readXMLInteger(p, "addons", intValue)) mType->outfit.lookAddons = intValue; } else if(readXMLInteger(p, "typeex", intValue)) mType->outfit.lookTypeEx = intValue; else SHOW_XML_WARNING("Missing look type/typeex"); if(readXMLInteger(p, "corpse", intValue)) mType->lookCorpse = intValue; } else if(!xmlStrcmp(p->name, (const xmlChar*)"attacks")) { xmlNodePtr tmpNode = p->children; while(tmpNode) { if(!xmlStrcmp(tmpNode->name, (const xmlChar*)"attack")) { spellBlock_t sb; if(deserializeSpell(tmpNode, sb, monsterName)) mType->spellAttackList.push_back(sb); else SHOW_XML_WARNING("Cant load spell"); } tmpNode = tmpNode->next; } } else if(!xmlStrcmp(p->name, (const xmlChar*)"defenses")) { if(readXMLInteger(p, "defense", intValue)) mType->defense = intValue; if(readXMLInteger(p, "armor", intValue)) mType->armor = intValue; xmlNodePtr tmpNode = p->children; while(tmpNode) { if(!xmlStrcmp(tmpNode->name, (const xmlChar*)"defense")) { spellBlock_t sb; if(deserializeSpell(tmpNode, sb, monsterName)) mType->spellDefenseList.push_back(sb); else SHOW_XML_WARNING("Cant load spell"); } tmpNode = tmpNode->next; } } else if(!xmlStrcmp(p->name, (const xmlChar*)"immunities")) { xmlNodePtr tmpNode = p->children; while(tmpNode) { if(!xmlStrcmp(tmpNode->name, (const xmlChar*)"immunity")) { if(readXMLString(tmpNode, "name", strValue)) { std::string tmpStrValue = asLowerCaseString(strValue); if(tmpStrValue == "physical") { mType->damageImmunities |= COMBAT_PHYSICALDAMAGE; mType->conditionImmunities |= CONDITION_PHYSICAL; } else if(tmpStrValue == "energy") { mType->damageImmunities |= COMBAT_ENERGYDAMAGE; mType->conditionImmunities |= CONDITION_ENERGY; } else if(tmpStrValue == "fire") { mType->damageImmunities |= COMBAT_FIREDAMAGE; mType->conditionImmunities |= CONDITION_FIRE; } else if(tmpStrValue == "poison" || tmpStrValue == "earth") { mType->damageImmunities |= COMBAT_EARTHDAMAGE; mType->conditionImmunities |= CONDITION_POISON; } else if(tmpStrValue == "ice") { mType->damageImmunities |= COMBAT_ICEDAMAGE; mType->conditionImmunities |= CONDITION_FREEZING; } else if(tmpStrValue == "holy") { mType->damageImmunities |= COMBAT_HOLYDAMAGE; mType->conditionImmunities |= CONDITION_DAZZLED; } else if(tmpStrValue == "death") { mType->damageImmunities |= COMBAT_DEATHDAMAGE; mType->conditionImmunities |= CONDITION_CURSED; } else if(tmpStrValue == "drown") { mType->damageImmunities |= COMBAT_DROWNDAMAGE; mType->conditionImmunities |= CONDITION_DROWN; } else if(tmpStrValue == "lifedrain") mType->damageImmunities |= COMBAT_LIFEDRAIN; else if(tmpStrValue == "manadrain") mType->damageImmunities |= COMBAT_MANADRAIN; else if(tmpStrValue == "paralyze") mType->conditionImmunities |= CONDITION_PARALYZE; else if(tmpStrValue == "outfit") mType->conditionImmunities |= CONDITION_OUTFIT; else if(tmpStrValue == "drunk") mType->conditionImmunities |= CONDITION_DRUNK; else if(tmpStrValue == "invisible") mType->conditionImmunities |= CONDITION_INVISIBLE; else SHOW_XML_WARNING("Unknown immunity name " << strValue); } else if(readXMLString(tmpNode, "physical", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_PHYSICALDAMAGE; //mType->conditionImmunities |= CONDITION_PHYSICAL; } else if(readXMLString(tmpNode, "energy", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_ENERGYDAMAGE; mType->conditionImmunities |= CONDITION_ENERGY; } else if(readXMLString(tmpNode, "fire", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_FIREDAMAGE; mType->conditionImmunities |= CONDITION_FIRE; } else if((readXMLString(tmpNode, "poison", strValue) || readXMLString(tmpNode, "earth", strValue)) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_EARTHDAMAGE; mType->conditionImmunities |= CONDITION_POISON; } else if(readXMLString(tmpNode, "drown", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_DROWNDAMAGE; mType->conditionImmunities |= CONDITION_DROWN; } else if(readXMLString(tmpNode, "ice", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_ICEDAMAGE; mType->conditionImmunities |= CONDITION_FREEZING; } else if(readXMLString(tmpNode, "holy", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_HOLYDAMAGE; mType->conditionImmunities |= CONDITION_DAZZLED; } else if(readXMLString(tmpNode, "death", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_DEATHDAMAGE; mType->conditionImmunities |= CONDITION_CURSED; } else if(readXMLString(tmpNode, "lifedrain", strValue) && booleanString(strValue)) mType->damageImmunities |= COMBAT_LIFEDRAIN; else if(readXMLString(tmpNode, "manadrain", strValue) && booleanString(strValue)) mType->damageImmunities |= COMBAT_LIFEDRAIN; else if(readXMLString(tmpNode, "paralyze", strValue) && booleanString(strValue)) mType->conditionImmunities |= CONDITION_PARALYZE; else if(readXMLString(tmpNode, "outfit", strValue) && booleanString(strValue)) mType->conditionImmunities |= CONDITION_OUTFIT; else if(readXMLString(tmpNode, "drunk", strValue) && booleanString(strValue)) mType->conditionImmunities |= CONDITION_DRUNK; else if(readXMLString(tmpNode, "invisible", strValue) && booleanString(strValue)) mType->conditionImmunities |= CONDITION_INVISIBLE; } tmpNode = tmpNode->next; } } else if(!xmlStrcmp(p->name, (const xmlChar*)"voices")) { if(readXMLInteger(p, "speed", intValue) || readXMLInteger(p, "interval", intValue)) mType->yellSpeedTicks = intValue; else SHOW_XML_WARNING("Missing voices.speed"); if(readXMLInteger(p, "chance", intValue)) mType->yellChance = intValue; else SHOW_XML_WARNING("Missing voices.chance"); xmlNodePtr tmpNode = p->children; while(tmpNode) { if(!xmlStrcmp(tmpNode->name, (const xmlChar*)"voice")) { voiceBlock_t vb; vb.text = ""; vb.yellText = false; if(readXMLString(tmpNode, "sentence", strValue)) vb.text = strValue; else SHOW_XML_WARNING("Missing voice.sentence"); if(readXMLString(tmpNode, "yell", strValue)) vb.yellText = booleanString(strValue); mType->voiceVector.push_back(vb); } tmpNode = tmpNode->next; } } else if(!xmlStrcmp(p->name, (const xmlChar*)"loot")) { xmlNodePtr tmpNode = p->children; while(tmpNode) { if(tmpNode->type != XML_ELEMENT_NODE) { tmpNode = tmpNode->next; continue; } LootBlock lootBlock; if(loadLootItem(tmpNode, lootBlock)) mType->lootItems.push_back(lootBlock); else SHOW_XML_WARNING("Cant load loot"); tmpNode = tmpNode->next; } } else if(!xmlStrcmp(p->name, (const xmlChar*)"elements")) { xmlNodePtr tmpNode = p->children; while(tmpNode) { if(!xmlStrcmp(tmpNode->name, (const xmlChar*)"element")) { if(readXMLInteger(tmpNode, "firePercent", intValue)) mType->elementMap[COMBAT_FIREDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "energyPercent", intValue)) mType->elementMap[COMBAT_ENERGYDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "icePercent", intValue)) mType->elementMap[COMBAT_ICEDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "poisonPercent", intValue) || readXMLInteger(tmpNode, "earthPercent", intValue)) mType->elementMap[COMBAT_EARTHDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "holyPercent", intValue)) mType->elementMap[COMBAT_HOLYDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "deathPercent", intValue)) mType->elementMap[COMBAT_DEATHDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "drownPercent", intValue)) mType->elementMap[COMBAT_DROWNDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "physicalPercent", intValue)) mType->elementMap[COMBAT_PHYSICALDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "lifeDrainPercent", intValue)) mType->elementMap[COMBAT_LIFEDRAIN] = intValue; else if(readXMLInteger(tmpNode, "manaDrainPercent", intValue)) mType->elementMap[COMBAT_MANADRAIN] = intValue; else if(readXMLInteger(tmpNode, "healingPercent", intValue)) mType->elementMap[COMBAT_HEALING] = intValue; else if(readXMLInteger(tmpNode, "undefinedPercent", intValue)) mType->elementMap[COMBAT_UNDEFINEDDAMAGE] = intValue; } tmpNode = tmpNode->next; } } else if(!xmlStrcmp(p->name, (const xmlChar*)"summons")) { if(readXMLInteger(p, "maxSummons", intValue)) mType->maxSummons = std::min(intValue, 100); else SHOW_XML_WARNING("Missing summons.maxSummons"); xmlNodePtr tmpNode = p->children; while(tmpNode) { if(!xmlStrcmp(tmpNode->name, (const xmlChar*)"summon")) { uint32_t chance = 100, interval = 1000, amount = 1; if(readXMLInteger(tmpNode, "speed", intValue) || readXMLInteger(tmpNode, "interval", intValue)) interval = intValue; if(readXMLInteger(tmpNode, "chance", intValue)) chance = intValue; if(readXMLInteger(tmpNode, "amount", intValue) || readXMLInteger(tmpNode, "max", intValue)) amount = intValue; if(readXMLString(tmpNode, "name", strValue)) { summonBlock_t sb; sb.name = strValue; sb.interval = interval; sb.chance = chance; sb.amount = amount; mType->summonList.push_back(sb); } else SHOW_XML_WARNING("Missing summon.name"); } tmpNode = tmpNode->next; } } else if(!xmlStrcmp(p->name, (const xmlChar*)"script")) { xmlNodePtr tmpNode = p->children; while(tmpNode) { if(!xmlStrcmp(tmpNode->name, (const xmlChar*)"event")) { if(readXMLString(tmpNode, "name", strValue)) mType->scriptList.push_back(strValue); else SHOW_XML_WARNING("Missing name for script event"); } tmpNode = tmpNode->next; } } else SHOW_XML_WARNING("Unknown attribute type - " << p->name); p = p->next; } xmlFreeDoc(doc); if(monsterLoad) { static uint32_t id = 0; if(new_mType) { id++; monsterNames[asLowerCaseString(monsterName)] = id; monsters[id] = mType; } return true; } if(new_mType) delete mType; return false; } bool Monsters::loadLootItem(xmlNodePtr node, LootBlock& lootBlock) { int32_t intValue; std::string strValue; if(readXMLInteger(node, "id", intValue)) lootBlock.id = intValue; if(lootBlock.id == 0) return false; if(readXMLInteger(node, "countmax", intValue)) { lootBlock.countmax = intValue; if(lootBlock.countmax > 100) lootBlock.countmax = 100; } else lootBlock.countmax = 1; if(readXMLInteger(node, "chance", intValue) || readXMLInteger(node, "chance1", intValue)) { lootBlock.chance = intValue; if(lootBlock.chance > MAX_LOOTCHANCE) lootBlock.chance = MAX_LOOTCHANCE; } else lootBlock.chance = MAX_LOOTCHANCE; if(Item::items[lootBlock.id].isContainer()) loadLootContainer(node, lootBlock); //optional if(readXMLInteger(node, "subtype", intValue) || readXMLInteger(node, "subType", intValue)) lootBlock.subType = intValue; if(readXMLInteger(node, "actionId", intValue) || readXMLInteger(node, "actionid", intValue) || readXMLInteger(node, "aid", intValue)) lootBlock.actionId = intValue; if(readXMLInteger(node, "uniqueId", intValue) || readXMLInteger(node, "uniqueid", intValue) || readXMLInteger(node, "uid", intValue)) lootBlock.uniqueId = intValue; if(readXMLString(node, "text", strValue)) lootBlock.text = strValue; return true; } bool Monsters::loadLootContainer(xmlNodePtr node, LootBlock& lBlock) { if(node == NULL) return false; xmlNodePtr p, tmpNode = node->children; if(tmpNode == NULL) return false; while(tmpNode) { if(!xmlStrcmp(tmpNode->name, (const xmlChar*)"inside")) { p = tmpNode->children; while(p) { LootBlock lootBlock; if(loadLootItem(p, lootBlock)) lBlock.childLoot.push_back(lootBlock); p = p->next; } return true; } tmpNode = tmpNode->next; } return false; } MonsterType* Monsters::getMonsterType(const std::string& name) { uint32_t mId = getIdByName(name); if(mId != 0) return getMonsterType(mId); return NULL; } MonsterType* Monsters::getMonsterType(uint32_t mid) { MonsterMap::iterator it = monsters.find(mid); if(it != monsters.end()) return it->second; return NULL; } uint32_t Monsters::getIdByName(const std::string& name) { std::string tmp = name; MonsterNameMap::iterator it = monsterNames.find(asLowerCaseString(tmp)); if(it != monsterNames.end()) return it->second; return 0; } Monsters::~Monsters() { loaded = false; for(MonsterMap::iterator it = monsters.begin(); it != monsters.end(); it++) delete it->second; }