Ola a todos! Esse e o meu primeiro poste, e gostaria se alguem podesse me ajudar adaptar Level System no meu poketibia base otpokemon (TFS 0.3.6)
Se alguem pudesse me ajudar colocar ficaria grato!
monsters.h
//////////////////////////////////////////////////////////////////////// // 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 <http://www.gnu.org/licenses/>. //////////////////////////////////////////////////////////////////////// #ifndef __MONSTERS__ #define __MONSTERS__ #include "otsystem.h" #include "creature.h" #define MAX_LOOTCHANCE 100000 #define MAX_STATICWALK 100 struct LootBlock; typedef std::list<LootBlock> LootItems; enum LootMessage_t { LOOTMSG_IGNORE = -1, LOOTMSG_NONE = 0, LOOTMSG_PLAYER = 1, LOOTMSG_PARTY = 2, LOOTMSG_BOTH = 3 }; struct LootBlock { std::vector<uint16_t> ids; uint16_t count; int32_t subType, actionId, uniqueId; uint32_t chance; std::string text; LootItems childLoot; LootBlock() { count = chance = 0; subType = actionId = uniqueId = -1; } }; struct summonBlock_t { std::string name; uint32_t chance, interval, amount; }; class BaseSpell; struct spellBlock_t { bool combatSpell, isMelee; int32_t minCombatValue, maxCombatValue; uint32_t chance, speed, range; BaseSpell* spell; }; struct voiceBlock_t { bool yellText; std::string text; }; typedef std::list<summonBlock_t> SummonList; typedef std::list<spellBlock_t> SpellList; typedef std::vector<voiceBlock_t> VoiceVector; typedef std::map<CombatType_t, int32_t> ElementMap; class MonsterType { public: MonsterType() {reset();} virtual ~MonsterType() {reset();} void reset(); void dropLoot(Container* corpse); Item* createLoot(const LootBlock& lootBlock); bool createChildLoot(Container* parent, const LootBlock& lootBlock); bool isSummonable, isIllusionable, isConvinceable, isAttackable, isHostile, isLureable, isWalkable, canPushItems, canPushCreatures, pushable, hideName, hideHealth; Outfit_t outfit; RaceType_t race; Skulls_t skull; PartyShields_t partyShield; LootMessage_t lootMessage; int32_t defense, armor, health, healthMax, baseSpeed, lookCorpse, corpseUnique, corpseAction, maxSummons, targetDistance, runAwayHealth, conditionImmunities, damageImmunities, lightLevel, lightColor, changeTargetSpeed, changeTargetChance; uint32_t yellChance, yellSpeedTicks, staticAttackChance, manaCost; uint64_t experience; std::string name, nameDescription; SummonList summonList; LootItems lootItems; ElementMap elementMap; SpellList spellAttackList; SpellList spellDefenseList; VoiceVector voiceVector; StringVec scriptList; }; class Monsters { public: Monsters(): loaded(false) {} virtual ~Monsters(); bool reload() {return loadFromXml(true);} bool loadFromXml(bool reloading = false); bool loadMonster(const std::string& file, const std::string& monsterName, bool reloading = false); MonsterType* getMonsterType(const std::string& name); MonsterType* getMonsterType(uint32_t mid); uint32_t getIdByName(const std::string& name); bool isLoaded() const {return loaded;} static uint16_t getLootRandom(); private: bool loaded; bool loadLoot(xmlNodePtr, LootBlock&); bool loadChildLoot(xmlNodePtr, LootBlock&); ConditionDamage* getDamageCondition(ConditionType_t conditionType, int32_t maxDamage, int32_t minDamage, int32_t startDamage, uint32_t tickInterval); bool deserializeSpell(xmlNodePtr node, spellBlock_t& sb, const std::string& description = ""); typedef std::map<std::string, uint32_t> MonsterNameMap; MonsterNameMap monsterNames; typedef std::map<uint32_t, MonsterType*> MonsterMap; MonsterMap monsters; }; #endif
monsters.cpp
//////////////////////////////////////////////////////////////////////// // 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 <http://www.gnu.org/licenses/>. //////////////////////////////////////////////////////////////////////// #include "otpch.h" #include <libxml/xmlmemory.h> #include <libxml/parser.h> #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 = isWalkable = hideName = hideHealth = false; pushable = isAttackable = isHostile = true; outfit.lookHead = outfit.lookBody = outfit.lookLegs = outfit.lookFeet = outfit.lookType = outfit.lookTypeEx = outfit.lookAddons = 0; runAwayHealth = manaCost = lightLevel = lightColor = yellSpeedTicks = yellChance = changeTargetSpeed = changeTargetChance = 0; experience = defense = armor = lookCorpse = corpseUnique = corpseAction = conditionImmunities = damageImmunities = 0; maxSummons = -1; targetDistance = 1; staticAttackChance = 95; health = healthMax = 100; baseSpeed = 200; race = RACE_BLOOD; skull = SKULL_NONE; partyShield = SHIELD_NONE; lootMessage = LOOTMSG_IGNORE; for(SpellList::iterator it = spellAttackList.begin(); it != spellAttackList.end(); ++it) { if(!it->combatSpell) continue; delete it->spell; it->spell = NULL; } spellAttackList.clear(); for(SpellList::iterator it = spellDefenseList.begin(); it != spellDefenseList.end(); ++it) { if(!it->combatSpell) continue; delete it->spell; it->spell = NULL; } spellDefenseList.clear(); summonList.clear(); scriptList.clear(); voiceVector.clear(); lootItems.clear(); elementMap.clear(); } uint16_t Monsters::getLootRandom() { return (uint16_t)std::ceil((double)random_range(0, MAX_LOOTCHANCE) / g_config.getDouble(ConfigManager::RATE_LOOT)); } void MonsterType::dropLoot(Container* corpse) { Item* tmpItem = NULL; for(LootItems::const_iterator it = lootItems.begin(); it != lootItems.end() && !corpse->full(); ++it) { if((tmpItem = createLoot(*it))) { if(Container* container = tmpItem->getContainer()) { if(createChildLoot(container, (*it))) corpse->__internalAddThing(tmpItem); else delete container; } else corpse->__internalAddThing(tmpItem); } } corpse->__startDecaying(); uint32_t ownerId = corpse->getCorpseOwner(); if(!ownerId) return; Player* owner = g_game.getPlayerByID(ownerId); if(!owner) return; LootMessage_t message = lootMessage; if(message == LOOTMSG_IGNORE) message = (LootMessage_t)g_config.getNumber(ConfigManager::LOOT_MESSAGE); if(message < LOOTMSG_PLAYER) return; std::stringstream ss; ss << "Loot of " << nameDescription << ": " << corpse->getContentDescription() << "."; if(owner->getParty() && message > LOOTMSG_PLAYER) owner->getParty()->broadcastMessage((MessageClasses)g_config.getNumber(ConfigManager::LOOT_MESSAGE_TYPE), ss.str()); else if(message == LOOTMSG_PLAYER || message == LOOTMSG_BOTH) owner->sendTextMessage((MessageClasses)g_config.getNumber(ConfigManager::LOOT_MESSAGE_TYPE), ss.str()); } Item* MonsterType::createLoot(const LootBlock& lootBlock) { uint16_t item = lootBlock.ids[0], random = Monsters::getLootRandom(); if(lootBlock.ids.size() > 1) item = lootBlock.ids[random_range((size_t)0, lootBlock.ids.size() - 1)]; Item* tmpItem = NULL; if(Item::items[item].stackable) { if(random < lootBlock.chance) tmpItem = Item::CreateItem(item, (random % lootBlock.count + 1)); } else if(random < lootBlock.chance) tmpItem = Item::CreateItem(item, 0); if(!tmpItem) return NULL; 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.empty()) tmpItem->setText(lootBlock.text); return tmpItem; } bool MonsterType::createChildLoot(Container* parent, const LootBlock& lootBlock) { LootItems::const_iterator it = lootBlock.childLoot.begin(); if(it == lootBlock.childLoot.end()) return true; Item* tmpItem = NULL; for(; it != lootBlock.childLoot.end() && !parent->full(); ++it) { if((tmpItem = createLoot(*it))) { if(Container* container = tmpItem->getContainer()) { if(createChildLoot(container, (*it))) parent->__internalAddThing(container); else delete container; } else parent->__internalAddThing(tmpItem); } } return !parent->empty(); } 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<ConditionDamage*>(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 = sb.minCombatValue = sb.maxCombatValue = 0; sb.combatSpell = 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)) std::swap(sb.minCombatValue, sb.maxCombatValue); } 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, 0, 0, 0, 0, 0, 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); CombatArea* area = new CombatArea(); 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); CombatArea* area = new CombatArea(); area->setupArea(radius); combat->setArea(area); } std::string tmpName = asLowerCaseString(name); if(tmpName == "melee") { int32_t attack = 0, skill = 0; if(readXMLInteger(node, "attack", attack) && readXMLInteger(node, "skill", skill)) { sb.minCombatValue = 0; sb.maxCombatValue = -Weapons::getMaxMeleeDamage(skill, attack); } uint32_t tickInterval = 10000; ConditionType_t conditionType = CONDITION_NONE; if(readXMLInteger(node, "physical", intValue)) conditionType = CONDITION_PHYSICAL; else if(readXMLInteger(node, "fire", intValue)) conditionType = CONDITION_FIRE; else if(readXMLInteger(node, "energy", intValue)) conditionType = CONDITION_ENERGY; else if(readXMLInteger(node, "earth", intValue)) conditionType = CONDITION_POISON; else if(readXMLInteger(node, "freeze", intValue)) conditionType = CONDITION_FREEZING; else if(readXMLInteger(node, "dazzle", intValue)) conditionType = CONDITION_DAZZLED; else if(readXMLInteger(node, "curse", intValue)) conditionType = CONDITION_CURSED; else if(readXMLInteger(node, "drown", intValue)) { conditionType = CONDITION_DROWN; tickInterval = 5000; } else if(readXMLInteger(node, "poison", intValue)) { conditionType = CONDITION_POISON; tickInterval = 5000; } uint32_t damage = std::abs(intValue); if(readXMLInteger(node, "tick", intValue) && intValue > 0) tickInterval = intValue; if(conditionType != CONDITION_NONE) { Condition* condition = getDamageCondition(conditionType, damage, damage, 0, tickInterval); if(condition) combat->setCondition(condition); } sb.isMelee = true; sb.range = 1; combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_PHYSICALDAMAGE); combat->setParam(COMBATPARAM_BLOCKEDBYSHIELD, 1); combat->setParam(COMBATPARAM_BLOCKEDBYARMOR, 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 == "test") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_TESTDAMAGE); else if(tmpName == "fly") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_FLYDAMAGE); else if(tmpName == "bug") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_BUGDAMAGE); else if(tmpName == "venom") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_VENOMDAMAGE); else if(tmpName == "dragon") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_DRAGONDAMAGE); else if(tmpName == "fight") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_FIGHTDAMAGE); else if(tmpName == "electric") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_ELECTRICDAMAGE); else if(tmpName == "rock") combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_ROCKDAMAGE); 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") { bool aggressive = false; if(readXMLInteger(node, "self", intValue)) aggressive = intValue; combat->setParam(COMBATPARAM_COMBATTYPE, COMBAT_HEALING); combat->setParam(COMBATPARAM_AGGRESSIVE, aggressive); } 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; enum Aggressive { NO, YES, AUTO } aggressive = AUTO; if(readXMLInteger(node, "self", intValue)) aggressive = (Aggressive)intValue; if(readXMLInteger(node, "speedchange", intValue)) speedChange = std::max(-1000, intValue); //cant be slower than 100% std::vector<Outfit_t> outfits; for(xmlNodePtr tmpNode = node->children; tmpNode; tmpNode = tmpNode->next) { if(xmlStrcmp(tmpNode->name,(const xmlChar*)"outfit")) continue; if(readXMLInteger(tmpNode, "type", intValue)) { Outfit_t outfit; outfit.lookType = intValue; if(readXMLInteger(tmpNode, "head", intValue)) outfit.lookHead = intValue; if(readXMLInteger(tmpNode, "body", intValue)) outfit.lookBody = intValue; if(readXMLInteger(tmpNode, "legs", intValue)) outfit.lookLegs = intValue; if(readXMLInteger(tmpNode, "feet", intValue)) outfit.lookFeet = intValue; if(readXMLInteger(tmpNode, "addons", intValue)) outfit.lookAddons = intValue; outfits.push_back(outfit); } if(readXMLInteger(tmpNode, "typeex", intValue) || readXMLInteger(tmpNode, "item", intValue)) { Outfit_t outfit; outfit.lookTypeEx = intValue; outfits.push_back(outfit); } if(readXMLString(tmpNode, "monster", strValue)) { if(MonsterType* mType = g_monsters.getMonsterType(strValue)) outfits.push_back(mType->outfit); } } ConditionType_t conditionType = CONDITION_PARALYZE; if(speedChange > 0) { conditionType = CONDITION_HASTE; if(aggressive == AUTO) aggressive = NO; } else if(aggressive == AUTO) aggressive = YES; if(ConditionSpeed* condition = dynamic_cast<ConditionSpeed*>(Condition::createCondition( CONDITIONID_COMBAT, conditionType, duration))) { condition->setFormulaVars((speedChange / 1000.), 0, (speedChange / 1000.), 0); if(!outfits.empty()) condition->setOutfits(outfits); combat->setCondition(condition); combat->setParam(COMBATPARAM_AGGRESSIVE, aggressive); } } else if(tmpName == "outfit") { std::vector<Outfit_t> outfits; for(xmlNodePtr tmpNode = node->children; tmpNode; tmpNode = tmpNode->next) { if(xmlStrcmp(tmpNode->name,(const xmlChar*)"outfit")) continue; if(readXMLInteger(tmpNode, "type", intValue)) { Outfit_t outfit; outfit.lookType = intValue; if(readXMLInteger(tmpNode, "head", intValue)) outfit.lookHead = intValue; if(readXMLInteger(tmpNode, "body", intValue)) outfit.lookBody = intValue; if(readXMLInteger(tmpNode, "legs", intValue)) outfit.lookLegs = intValue; if(readXMLInteger(tmpNode, "feet", intValue)) outfit.lookFeet = intValue; if(readXMLInteger(tmpNode, "addons", intValue)) outfit.lookAddons = intValue; outfits.push_back(outfit); } if(readXMLInteger(tmpNode, "typeex", intValue) || readXMLInteger(tmpNode, "item", intValue)) { Outfit_t outfit; outfit.lookTypeEx = intValue; outfits.push_back(outfit); } if(readXMLString(tmpNode, "monster", strValue)) { if(MonsterType* mType = g_monsters.getMonsterType(strValue)) outfits.push_back(mType->outfit); } } if(outfits.empty()) { if(readXMLInteger(node, "type", intValue)) { Outfit_t outfit; outfit.lookType = intValue; if(readXMLInteger(node, "head", intValue)) outfit.lookHead = intValue; if(readXMLInteger(node, "body", intValue)) outfit.lookBody = intValue; if(readXMLInteger(node, "legs", intValue)) outfit.lookLegs = intValue; if(readXMLInteger(node, "feet", intValue)) outfit.lookFeet = intValue; if(readXMLInteger(node, "addons", intValue)) outfit.lookAddons = intValue; outfits.push_back(outfit); } if(readXMLInteger(node, "typeex", intValue) || readXMLInteger(node, "item", intValue)) { Outfit_t outfit; outfit.lookTypeEx = intValue; outfits.push_back(outfit); } if(readXMLString(node, "monster", strValue)) { if(MonsterType* mType = g_monsters.getMonsterType(strValue)) outfits.push_back(mType->outfit); } } if(!outfits.empty()) { int32_t duration = 10000; if(readXMLInteger(node, "duration", intValue)) duration = intValue; bool aggressive = false; if(readXMLInteger(node, "self", intValue)) aggressive = intValue; if(ConditionOutfit* condition = dynamic_cast<ConditionOutfit*>(Condition::createCondition( CONDITIONID_COMBAT, CONDITION_OUTFIT, duration))) { condition->setOutfits(outfits); combat->setCondition(condition); combat->setParam(COMBATPARAM_AGGRESSIVE, aggressive); } } } else if(tmpName == "invisible") { int32_t duration = 10000; if(readXMLInteger(node, "duration", intValue)) duration = intValue; bool aggressive = false; if(readXMLInteger(node, "self", intValue)) aggressive = intValue; if(Condition* condition = Condition::createCondition(CONDITIONID_COMBAT, CONDITION_INVISIBLE, duration)) { combat->setParam(COMBATPARAM_AGGRESSIVE, aggressive); 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 == "skills" || tmpName == "attributes") { uint32_t duration = 10000, subId = 0; if(readXMLInteger(node, "duration", intValue)) duration = intValue; if(readXMLInteger(node, "subid", intValue)) subId = intValue; intValue = 0; ConditionParam_t param = CONDITIONPARAM_BUFF; //to know was it loaded if(readXMLInteger(node, "melee", intValue)) param = CONDITIONPARAM_SKILL_MELEE; else if(readXMLInteger(node, "fist", intValue)) param = CONDITIONPARAM_SKILL_FIST; else if(readXMLInteger(node, "club", intValue)) param = CONDITIONPARAM_SKILL_CLUB; else if(readXMLInteger(node, "axe", intValue)) param = CONDITIONPARAM_SKILL_AXE; else if(readXMLInteger(node, "sword", intValue)) param = CONDITIONPARAM_SKILL_SWORD; else if(readXMLInteger(node, "distance", intValue) || readXMLInteger(node, "dist", intValue)) param = CONDITIONPARAM_SKILL_DISTANCE; else if(readXMLInteger(node, "shielding", intValue) || readXMLInteger(node, "shield", intValue)) param = CONDITIONPARAM_SKILL_SHIELD; else if(readXMLInteger(node, "fishing", intValue) || readXMLInteger(node, "fish", intValue)) param = CONDITIONPARAM_SKILL_FISHING; else if(readXMLInteger(node, "meleePercent", intValue)) param = CONDITIONPARAM_SKILL_MELEEPERCENT; else if(readXMLInteger(node, "fistPercent", intValue)) param = CONDITIONPARAM_SKILL_FISTPERCENT; else if(readXMLInteger(node, "clubPercent", intValue)) param = CONDITIONPARAM_SKILL_CLUBPERCENT; else if(readXMLInteger(node, "axePercent", intValue)) param = CONDITIONPARAM_SKILL_AXEPERCENT; else if(readXMLInteger(node, "swordPercent", intValue)) param = CONDITIONPARAM_SKILL_SWORDPERCENT; else if(readXMLInteger(node, "distancePercent", intValue) || readXMLInteger(node, "distPercent", intValue)) param = CONDITIONPARAM_SKILL_DISTANCEPERCENT; else if(readXMLInteger(node, "shieldingPercent", intValue) || readXMLInteger(node, "shieldPercent", intValue)) param = CONDITIONPARAM_SKILL_SHIELDPERCENT; else if(readXMLInteger(node, "fishingPercent", intValue) || readXMLInteger(node, "fishPercent", intValue)) param = CONDITIONPARAM_SKILL_FISHINGPERCENT; else if(readXMLInteger(node, "maxhealth", intValue)) param = CONDITIONPARAM_STAT_MAXHEALTHPERCENT; else if(readXMLInteger(node, "maxmana", intValue)) param = CONDITIONPARAM_STAT_MAXMANAPERCENT; else if(readXMLInteger(node, "soul", intValue)) param = CONDITIONPARAM_STAT_SOULPERCENT; else if(readXMLInteger(node, "magiclevel", intValue) || readXMLInteger(node, "maglevel", intValue)) param = CONDITIONPARAM_STAT_MAGICLEVELPERCENT; else if(readXMLInteger(node, "maxhealthPercent", intValue)) param = CONDITIONPARAM_STAT_MAXHEALTHPERCENT; else if(readXMLInteger(node, "maxmanaPercent", intValue)) param = CONDITIONPARAM_STAT_MAXMANAPERCENT; else if(readXMLInteger(node, "soulPercent", intValue)) param = CONDITIONPARAM_STAT_SOULPERCENT; else if(readXMLInteger(node, "magiclevelPercent", intValue) || readXMLInteger(node, "maglevelPercent", intValue)) param = CONDITIONPARAM_STAT_MAGICLEVELPERCENT; if(param != CONDITIONPARAM_BUFF) { if(ConditionAttributes* condition = dynamic_cast<ConditionAttributes*>(Condition::createCondition( CONDITIONID_COMBAT, CONDITION_ATTRIBUTES, duration, false, subId))) { condition->setParam(param, intValue); 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 == "physicalcondition") { conditionType = CONDITION_PHYSICAL; tickInterval = 5000; } else if(tmpName == "firecondition") { conditionType = CONDITION_FIRE; tickInterval = 10000; } else if(tmpName == "energycondition") { conditionType = CONDITION_ENERGY; tickInterval = 10000; } else if(tmpName == "earthcondition") { conditionType = CONDITION_POISON; tickInterval = 10000; } else if(tmpName == "freezecondition") { conditionType = CONDITION_FREEZING; tickInterval = 10000; } else if(tmpName == "cursecondition") { conditionType = CONDITION_CURSED; tickInterval = 10000; } else if(tmpName == "dazzlecondition") { conditionType = CONDITION_DAZZLED; tickInterval = 10000; } else if(tmpName == "drowncondition") { conditionType = CONDITION_DROWN; tickInterval = 5000; } else if(tmpName == "poisoncondition") { conditionType = CONDITION_POISON; 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)) startDamage = std::max(std::abs(intValue), minDamage); if(Condition* condition = getDamageCondition(conditionType, maxDamage, minDamage, startDamage, tickInterval)) combat->setCondition(condition); } else if(tmpName == "strength") { //TODO: monster extra strength } else if(tmpName == "effect") {/*show some effect and bye bye!*/} else { delete combat; std::cout << "[Error - Monsters::deserializeSpell] " << description << " - Unknown spell name: " << name << std::endl; return false; } combat->setPlayerCombatValues(FORMULA_VALUE, sb.minCombatValue, 0, sb.maxCombatValue, 0, 0, 0, 0, 0, 0, 0); combatSpell = new CombatSpell(combat, needTarget, needDirection); xmlNodePtr attributeNode = node->children; while(attributeNode) { if(!xmlStrcmp(attributeNode->name, (const xmlChar*)"attribute")) { if(readXMLString(attributeNode, "key", strValue)) { std::string tmpStrValue = asLowerCaseString(strValue); if(tmpStrValue == "shooteffect") { if(readXMLString(attributeNode, "value", strValue)) { ShootEffect_t shoot = getShootType(strValue); if(shoot != SHOOT_EFFECT_UNKNOWN) 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)) { MagicEffect_t effect = getMagicEffect(strValue); if(effect != MAGIC_EFFECT_UNKNOWN) 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) && !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 if(tmpStrValue == "dark" || atoi(strValue.c_str()) == 6) mType->race = RACE_DARK; else if(tmpStrValue == "steel" || atoi(strValue.c_str()) == 6) mType->race = RACE_STEEL; else if(tmpStrValue == "water" || atoi(strValue.c_str()) == 6) mType->race = RACE_WATER; else if(tmpStrValue == "normal" || atoi(strValue.c_str()) == 7) mType->race = RACE_NORMAL; else if(tmpStrValue == "fire2" || atoi(strValue.c_str()) == 8) mType->race = RACE_FIRE2; else if(tmpStrValue == "fighting" || atoi(strValue.c_str()) == 9) mType->race = RACE_FIGHTING; else if(tmpStrValue == "flying" || atoi(strValue.c_str()) == 10) mType->race = RACE_FLYING; else if(tmpStrValue == "grass" || atoi(strValue.c_str()) == 11) mType->race = RACE_GRASS; else if(tmpStrValue == "poison" || atoi(strValue.c_str()) == 12) mType->race = RACE_POISON; else if(tmpStrValue == "electric" || atoi(strValue.c_str()) == 13) mType->race = RACE_ELECTRIC; else if(tmpStrValue == "ground" || atoi(strValue.c_str()) == 14) mType->race = RACE_GROUND; else if(tmpStrValue == "psychic" || atoi(strValue.c_str()) == 15) mType->race = RACE_PSYCHIC; else if(tmpStrValue == "rock" || atoi(strValue.c_str()) == 16) mType->race = RACE_ROCK; else if(tmpStrValue == "ice" || atoi(strValue.c_str()) == 17) mType->race = RACE_ICE; else if(tmpStrValue == "bug" || atoi(strValue.c_str()) == 18) mType->race = RACE_BUG; else if(tmpStrValue == "dragon" || atoi(strValue.c_str()) == 19) mType->race = RACE_DRAGON; else if(tmpStrValue == "ghost" || atoi(strValue.c_str()) == 20) mType->race = RACE_GHOST; 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, "lootmessage", intValue)) mType->lootMessage = (LootMessage_t)intValue; 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, "walkable", strValue)) mType->isWalkable = 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; if(readXMLInteger(p, "corpseUniqueId", intValue) || readXMLInteger(p, "corpseUid", intValue)) mType->corpseUnique = intValue; if(readXMLInteger(p, "corpseActionId", intValue) || readXMLInteger(p, "corpseAid", intValue)) mType->corpseAction = 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 == "test") { mType->damageImmunities |= COMBAT_TESTDAMAGE; mType->conditionImmunities |= CONDITION_TEST; } else if(tmpStrValue == "rock") { mType->damageImmunities |= COMBAT_ROCKDAMAGE; mType->conditionImmunities |= CONDITION_ROCK; } else if(tmpStrValue == "fly") { mType->damageImmunities |= COMBAT_FLYDAMAGE; mType->conditionImmunities |= CONDITION_FLY; } else if(tmpStrValue == "electric") { mType->damageImmunities |= COMBAT_ELECTRICDAMAGE; mType->conditionImmunities |= CONDITION_ELECTRIC; } else if(tmpStrValue == "bug") { mType->damageImmunities |= COMBAT_BUGDAMAGE; mType->conditionImmunities |= CONDITION_BUG; } else if(tmpStrValue == "dragon") { mType->damageImmunities |= COMBAT_DRAGONDAMAGE; mType->conditionImmunities |= CONDITION_DRAGON; } else if(tmpStrValue == "venom") { mType->damageImmunities |= COMBAT_VENOMDAMAGE; mType->conditionImmunities |= CONDITION_VENOM; } else if(tmpStrValue == "fight") { mType->damageImmunities |= COMBAT_FIGHTDAMAGE; mType->conditionImmunities |= CONDITION_FIGHT; } 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, "test", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_TESTDAMAGE; mType->conditionImmunities |= CONDITION_TEST; } else if(readXMLString(tmpNode, "dragon", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_DRAGONDAMAGE; mType->conditionImmunities |= CONDITION_DRAGON; } else if(readXMLString(tmpNode, "electric", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_ELECTRICDAMAGE; mType->conditionImmunities |= CONDITION_ELECTRIC; } else if(readXMLString(tmpNode, "rock", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_ROCKDAMAGE; mType->conditionImmunities |= CONDITION_ROCK; } else if(readXMLString(tmpNode, "fly", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_FLYDAMAGE; mType->conditionImmunities |= CONDITION_FLY; } else if(readXMLString(tmpNode, "bug", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_BUGDAMAGE; mType->conditionImmunities |= CONDITION_BUG; } else if(readXMLString(tmpNode, "fight", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_FIGHTDAMAGE; mType->conditionImmunities |= CONDITION_FIGHT; } else if(readXMLString(tmpNode, "venom", strValue) && booleanString(strValue)) { mType->damageImmunities |= COMBAT_VENOMDAMAGE; mType->conditionImmunities |= CONDITION_VENOM; } 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 rootBlock; if(loadLoot(tmpNode, rootBlock)) mType->lootItems.push_back(rootBlock); 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, "electricPercent", intValue)) mType->elementMap[COMBAT_ELECTRICDAMAGE] = 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, "testPercent", intValue)) mType->elementMap[COMBAT_TESTDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "rockPercent", intValue)) mType->elementMap[COMBAT_ROCKDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "dragonPercent", intValue)) mType->elementMap[COMBAT_DRAGONDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "fightPercent", intValue)) mType->elementMap[COMBAT_FIGHTDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "venomPercent", intValue)) mType->elementMap[COMBAT_VENOMDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "bugPercent", intValue)) mType->elementMap[COMBAT_BUGDAMAGE] = intValue; else if(readXMLInteger(tmpNode, "flyPercent", intValue)) mType->elementMap[COMBAT_FLYDAMAGE] = 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) || readXMLInteger(p, "max", intValue)) mType->maxSummons = intValue; 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::loadLoot(xmlNodePtr node, LootBlock& lootBlock) { std::string strValue; if(readXMLString(node, "id", strValue) || readXMLString(node, "ids", strValue)) { IntegerVec idsVec; parseIntegerVec(strValue, idsVec); for(IntegerVec::iterator it = idsVec.begin(); it != idsVec.end(); ++it) { lootBlock.ids.push_back(*it); if(Item::items[(*it)].isContainer()) loadChildLoot(node, lootBlock); } } else if(readXMLString(node, "name", strValue) || readXMLString(node, "names", strValue)) { StringVec names = explodeString(strValue, ";"); for(StringVec::iterator it = names.begin(); it != names.end(); ++it) { uint16_t tmp = Item::items.getItemIdByName(strValue); if(!tmp) continue; lootBlock.ids.push_back(tmp); if(Item::items[tmp].isContainer()) loadChildLoot(node, lootBlock); } } if(lootBlock.ids.empty()) return false; int32_t intValue; if(readXMLInteger(node, "count", intValue) || readXMLInteger(node, "countmax", intValue)) lootBlock.count = std::max(1, std::min(100, intValue)); else lootBlock.count = 1; if(readXMLInteger(node, "chance", intValue) || readXMLInteger(node, "chance1", intValue)) lootBlock.chance = std::min(MAX_LOOTCHANCE, intValue); else lootBlock.chance = MAX_LOOTCHANCE; 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::loadChildLoot(xmlNodePtr node, LootBlock& parentBlock) { if(!node) return false; xmlNodePtr p = node->children, insideNode; while(p) { if(!xmlStrcmp(p->name, (const xmlChar*)"inside")) { insideNode = p->children; while(insideNode) { LootBlock childBlock; if(loadLoot(insideNode, childBlock)) parentBlock.childLoot.push_back(childBlock); insideNode = insideNode->next; } p = p->next; continue; } LootBlock childBlock; if(loadLoot(p, childBlock)) parentBlock.childLoot.push_back(childBlock); p = p->next; } return true; } 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; }
monster.cpp
//////////////////////////////////////////////////////////////////////// // 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 <http://www.gnu.org/licenses/>. //////////////////////////////////////////////////////////////////////// #include "otpch.h" #include "monster.h" #include "spawn.h" #include "monsters.h" #include "spells.h" #include "combat.h" #include "configmanager.h" #include "game.h" extern Game g_game; extern ConfigManager g_config; extern Monsters g_monsters; AutoList<Monster>Monster::autoList; #ifdef __ENABLE_SERVER_DIAGNOSTIC__ uint32_t Monster::monsterCount = 0; #endif Monster* Monster::createMonster(MonsterType* mType) { return new Monster(mType); } Monster* Monster::createMonster(const std::string& name) { MonsterType* mType = g_monsters.getMonsterType(name); if(!mType) return NULL; return createMonster(mType); } Monster::Monster(MonsterType* _mType): Creature() { isIdle = true; isMasterInRange = false; teleportToMaster = false; mType = _mType; spawn = NULL; raid = NULL; defaultOutfit = mType->outfit; currentOutfit = mType->outfit; double multiplier = g_config.getDouble(ConfigManager::RATE_MONSTER_HEALTH); health = (int32_t)(mType->health * multiplier); healthMax = (int32_t)(mType->healthMax * multiplier); baseSpeed = mType->baseSpeed; internalLight.level = mType->lightLevel; internalLight.color = mType->lightColor; setSkull(mType->skull); setShield(mType->partyShield); hideName = mType->hideName, hideHealth = mType->hideHealth; minCombatValue = 0; maxCombatValue = 0; targetTicks = 0; targetChangeTicks = 0; targetChangeCooldown = 0; attackTicks = 0; defenseTicks = 0; yellTicks = 0; extraMeleeAttack = false; // register creature events for(StringVec::iterator it = mType->scriptList.begin(); it != mType->scriptList.end(); ++it) { if(!registerCreatureEvent(*it)) std::cout << "[Warning - Monster::Monster] Unknown event name - " << *it << std::endl; } #ifdef __ENABLE_SERVER_DIAGNOSTIC__ monsterCount++; #endif } Monster::~Monster() { clearTargetList(); clearFriendList(); #ifdef __ENABLE_SERVER_DIAGNOSTIC__ monsterCount--; #endif if(raid) { raid->unRef(); raid = NULL; } } void Monster::onAttackedCreature(Creature* target) { Creature::onAttackedCreature(target); if(isSummon()) getMaster()->onSummonAttackedCreature(this, target); } void Monster::onAttackedCreatureDisappear(bool isLogout) { #ifdef __DEBUG__ std::cout << "Attacked creature disappeared." << std::endl; #endif attackTicks = 0; extraMeleeAttack = true; } void Monster::onAttackedCreatureDrain(Creature* target, int32_t points) { Creature::onAttackedCreatureDrain(target, points); if(isSummon()) getMaster()->onSummonAttackedCreatureDrain(this, target, points); } void Monster::onCreatureAppear(const Creature* creature) { Creature::onCreatureAppear(creature); if(creature == this) { //We just spawned lets look around to see who is there. if(isSummon()) isMasterInRange = canSee(getMaster()->getPosition()); CreatureEventList spawn = getCreatureEvents(CREATURE_EVENT_SPAWN); for(CreatureEventList::iterator it = spawn.begin(); it != spawn.end(); ++it) (*it)->executeOnSpawn(this); updateTargetList(); updateIdleStatus(); } else onCreatureEnter(const_cast<Creature*>(creature)); } void Monster::onCreatureDisappear(const Creature* creature, bool isLogout) { Creature::onCreatureDisappear(creature, isLogout); if(creature == this) { if(spawn) spawn->startEvent(); setIdle(true); } else onCreatureLeave(const_cast<Creature*>(creature)); } void Monster::onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); if(creature == this) { if(isSummon()) isMasterInRange = canSee(getMaster()->getPosition()); updateTargetList(); updateIdleStatus(); } else { bool canSeeNewPos = canSee(newPos), canSeeOldPos = canSee(oldPos); if(canSeeNewPos && !canSeeOldPos) onCreatureEnter(const_cast<Creature*>(creature)); else if(!canSeeNewPos && canSeeOldPos) onCreatureLeave(const_cast<Creature*>(creature)); if(isSummon() && getMaster() == creature && canSeeNewPos) //Turn the summon on again isMasterInRange = true; updateIdleStatus(); if(!followCreature && !isSummon() && isOpponent(creature)) //we have no target lets try pick this one selectTarget(const_cast<Creature*>(creature)); } } void Monster::updateTargetList() { CreatureList::iterator it; for(it = friendList.begin(); it != friendList.end();) { if((*it)->getHealth() <= 0 || !canSee((*it)->getPosition())) { (*it)->unRef(); it = friendList.erase(it); } else ++it; } for(it = targetList.begin(); it != targetList.end();) { if((*it)->getHealth() <= 0 || !canSee((*it)->getPosition())) { (*it)->unRef(); it = targetList.erase(it); } else ++it; } const SpectatorVec& list = g_game.getSpectators(getPosition()); for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it) { if((*it) != this && canSee((*it)->getPosition())) onCreatureFound(*it); } } void Monster::clearTargetList() { for(CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it) (*it)->unRef(); targetList.clear(); } void Monster::clearFriendList() { for(CreatureList::iterator it = friendList.begin(); it != friendList.end(); ++it) (*it)->unRef(); friendList.clear(); } void Monster::onCreatureFound(Creature* creature, bool pushFront /*= false*/) { if(isFriend(creature)) { assert(creature != this); if(std::find(friendList.begin(), friendList.end(), creature) == friendList.end()) { creature->addRef(); friendList.push_back(creature); } } if(isOpponent(creature)) { assert(creature != this); if(std::find(targetList.begin(), targetList.end(), creature) == targetList.end()) { creature->addRef(); if(pushFront) targetList.push_front(creature); else targetList.push_back(creature); } } updateIdleStatus(); } void Monster::onCreatureEnter(Creature* creature) { if(getMaster() == creature) //Turn the summon on again { isMasterInRange = true; updateIdleStatus(); } onCreatureFound(creature, true); } bool Monster::isFriend(const Creature* creature) { if(!isSummon() || !getMaster()->getPlayer()) return creature->getMonster() && !creature->isSummon(); const Player* tmpPlayer = NULL; if(creature->getPlayer()) tmpPlayer = creature->getPlayer(); else if(creature->getMaster() && creature->getMaster()->getPlayer()) tmpPlayer = creature->getMaster()->getPlayer(); const Player* masterPlayer = getMaster()->getPlayer(); return tmpPlayer && (tmpPlayer == getMaster() || masterPlayer->isPartner(tmpPlayer)); } bool Monster::isOpponent(const Creature* creature) { return (isSummon() && getMaster()->getPlayer() && creature != getMaster()) || ((creature->getPlayer() && !creature->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) || (creature->getMaster() && creature->getMaster()->getPlayer())); } bool Monster::doTeleportToMaster() { const Position& tmp = getPosition(); if(g_game.internalTeleport(this, g_game.getClosestFreeTile(this, getMaster()->getPosition(), true), false) != RET_NOERROR) return false; g_game.addMagicEffect(tmp, MAGIC_EFFECT_POFF); g_game.addMagicEffect(getPosition(), MAGIC_EFFECT_TELEPORT); return true; } void Monster::onCreatureLeave(Creature* creature) { #ifdef __DEBUG__ std::cout << "onCreatureLeave - " << creature->getName() << std::endl; #endif if(isSummon() && getMaster() == creature) { if(!g_config.getBool(ConfigManager::TELEPORT_SUMMONS) && (!getMaster()->getPlayer() || !g_config.getBool(ConfigManager::TELEPORT_PLAYER_SUMMONS))) { //Turn the monster off until its master comes back isMasterInRange = false; updateIdleStatus(); } else if(!doTeleportToMaster()) teleportToMaster = true; } //update friendList if(isFriend(creature)) { CreatureList::iterator it = std::find(friendList.begin(), friendList.end(), creature); if(it != friendList.end()) { (*it)->unRef(); friendList.erase(it); } #ifdef __DEBUG__ else std::cout << "Monster: " << creature->getName() << " not found in the friendList." << std::endl; #endif } //update targetList if(isOpponent(creature)) { CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature); if(it != targetList.end()) { (*it)->unRef(); targetList.erase(it); if(targetList.empty()) updateIdleStatus(); } #ifdef __DEBUG__ else std::cout << "Player: " << creature->getName() << " not found in the targetList." << std::endl; #endif } } bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAULT*/) { #ifdef __DEBUG__ std::cout << "Searching target... " << std::endl; #endif std::list<Creature*> resultList; const Position& myPos = getPosition(); for(CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it) { if(followCreature != (*it) && isTarget(*it) && (searchType == TARGETSEARCH_RANDOM || canUseAttack(myPos, *it))) resultList.push_back(*it); } switch(searchType) { case TARGETSEARCH_NEAREST: { Creature* target = NULL; int32_t range = -1; for(CreatureList::iterator it = resultList.begin(); it != resultList.end(); ++it) { int32_t tmp = std::max(std::abs(myPos.x - (*it)->getPosition().x), std::abs(myPos.y - (*it)->getPosition().y)); if(range >= 0 && tmp >= range) continue; target = *it; range = tmp; } if(target && selectTarget(target)) return target; break; } default: { if(!resultList.empty()) { CreatureList::iterator it = resultList.begin(); std::advance(it, random_range(0, resultList.size() - 1)); #ifdef __DEBUG__ std::cout << "Selecting target " << (*it)->getName() << std::endl; #endif return selectTarget(*it); } if(searchType == TARGETSEARCH_ATTACKRANGE) return false; break; } } //lets just pick the first target in the list for(CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it) { if(followCreature == (*it) || !selectTarget(*it)) continue; #ifdef __DEBUG__ std::cout << "Selecting target " << (*it)->getName() << std::endl; #endif return true; } return false; } void Monster::onFollowCreatureComplete(const Creature* creature) { if(!creature) return; CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature); if(it != targetList.end()) { Creature* target = (*it); targetList.erase(it); if(hasFollowPath) //push target we have found a path to the front targetList.push_front(target); else if(!isSummon()) //push target we have not found a path to the back targetList.push_back(target); else //Since we removed the creature from the targetList (and not put it back) we have to release it too target->unRef(); } } BlockType_t Monster::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, bool checkDefense/* = false*/, bool checkArmor/* = false*/) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor); if(!damage) return blockType; int32_t elementMod = 0; ElementMap::iterator it = mType->elementMap.find(combatType); if(it != mType->elementMap.end()) elementMod = it->second; if(!elementMod) return blockType; damage = (int32_t)std::ceil(damage * ((float)(100 - elementMod) / 100)); if(damage > 0) return blockType; damage = 0; blockType = BLOCK_DEFENSE; return blockType; } bool Monster::isTarget(Creature* creature) { return (!creature->isRemoved() && creature->isAttackable() && creature->getZone() != ZONE_PROTECTION && canSeeCreature(creature) && creature->getPosition().z == getPosition().z); } bool Monster::selectTarget(Creature* creature) { #ifdef __DEBUG__ std::cout << "Selecting target... " << std::endl; #endif if(!isTarget(creature)) return false; CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature); if(it == targetList.end()) { //Target not found in our target list. #ifdef __DEBUG__ std::cout << "Target not found in targetList." << std::endl; #endif return false; } if(!isHostile() && getHealth() == getMaxHealth() && !isSummon()){ return false; } if((isHostile() || isSummon() || !isHostile() && getHealth() != getMaxHealth()) && setAttackedCreature(creature) && !isSummon()) Dispatcher::getInstance().addTask(createTask( boost::bind(&Game::checkCreatureAttack, &g_game, getID()))); return setFollowCreature(creature, true); } void Monster::setIdle(bool _idle) { if(isRemoved() || getHealth() <= 0) return; isIdle = _idle; if(isIdle) { onIdleStatus(); clearTargetList(); clearFriendList(); g_game.removeCreatureCheck(this); } else g_game.addCreatureCheck(this); } void Monster::updateIdleStatus() { bool idle = false; if(conditions.empty()) { if(isSummon()) { if((!isMasterInRange && !teleportToMaster) || (getMaster()->getMonster() && getMaster()->getMonster()->getIdleStatus())) idle = true; } else if(targetList.empty()) idle = true; } setIdle(idle); } void Monster::onAddCondition(ConditionType_t type, bool hadCondition) { Creature::onAddCondition(type, hadCondition); //the walkCache need to be updated if the monster becomes "resistent" to the damage, see Tile::__queryAdd() if(type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) updateMapCache(); updateIdleStatus(); } void Monster::onEndCondition(ConditionType_t type) { Creature::onEndCondition(type); //the walkCache need to be updated if the monster loose the "resistent" to the damage, see Tile::__queryAdd() if(type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) updateMapCache(); updateIdleStatus(); } void Monster::onThink(uint32_t interval) { Creature::onThink(interval); if(despawn()) { g_game.removeCreature(this, true); setIdle(true); return; } updateIdleStatus(); if(isIdle) return; if(teleportToMaster && doTeleportToMaster()) teleportToMaster = false; if(getMaster()){ if(!Position::areInRange<6,6,0>(getPosition(), getMaster()->getPosition())) doTeleportToMaster(); } addEventWalk(); if(isSummon()) { if(!attackedCreature) { if(getMaster() && getMaster()->getAttackedCreature()) //This happens if the monster is summoned during combat selectTarget(getMaster()->getAttackedCreature()); else if(getMaster() != followCreature) //Our master has not ordered us to attack anything, lets follow him around instead. setFollowCreature(getMaster()); } else if(attackedCreature == this) setFollowCreature(NULL); else if(followCreature != attackedCreature) //This happens just after a master orders an attack, so lets follow it aswell. setFollowCreature(attackedCreature); } else if(!targetList.empty()) { if(!followCreature || !hasFollowPath) searchTarget(); else if(isFleeing() && attackedCreature && !canUseAttack(getPosition(), attackedCreature)) searchTarget(TARGETSEARCH_ATTACKRANGE); } onThinkTarget(interval); onThinkYell(interval); onThinkDefense(interval); } void Monster::doAttacking(uint32_t interval) { if(!attackedCreature || (isSummon() && attackedCreature == this)) return; const std::list<Creature*>& summons = attackedCreature->getSummons(); CreatureList::const_iterator itt = summons.begin(); for(uint32_t i = 1; itt != summons.end(); ++itt, ++i) { if (i >= 1 && *itt){ searchTarget(); } } bool updateLook = true, outOfRange = true; resetTicks = interval; attackTicks += interval; const Position& myPos = getPosition(); const Position& targetPos = attackedCreature->getPosition(); for(SpellList::iterator it = mType->spellAttackList.begin(); it != mType->spellAttackList.end(); ++it) { if(it->isMelee && isFleeing()) continue; bool inRange = false; if(canUseSpell(myPos, targetPos, *it, interval, inRange)) { if(it->chance >= (uint32_t)random_range(1, 100)) { if(updateLook) { updateLookDirection(); updateLook = false; } double multiplier; if(maxCombatValue > 0) //defense multiplier = g_config.getDouble(ConfigManager::RATE_MONSTER_DEFENSE); else //attack multiplier = g_config.getDouble(ConfigManager::RATE_MONSTER_ATTACK); minCombatValue = (int32_t)(it->minCombatValue * multiplier); maxCombatValue = (int32_t)(it->maxCombatValue * multiplier); it->spell->castSpell(this, attackedCreature); if(it->isMelee) extraMeleeAttack = false; #ifdef __DEBUG__ static uint64_t prevTicks = OTSYS_TIME(); std::cout << "doAttacking ticks: " << OTSYS_TIME() - prevTicks << std::endl; prevTicks = OTSYS_TIME(); #endif } } if(inRange) outOfRange = false; else if(it->isMelee) //melee swing out of reach extraMeleeAttack = true; } if(updateLook) updateLookDirection(); if(resetTicks) attackTicks = 0; } bool Monster::canUseAttack(const Position& pos, const Creature* target) const { const Position& targetPos = target->getPosition(); for(SpellList::iterator it = mType->spellAttackList.begin(); it != mType->spellAttackList.end(); ++it) { if((*it).range != 0 && std::max(std::abs(pos.x - targetPos.x), std::abs(pos.y - targetPos.y)) <= (int32_t)(*it).range) return g_game.isSightClear(pos, targetPos, true); } return false; } bool Monster::canUseSpell(const Position& pos, const Position& targetPos, const spellBlock_t& sb, uint32_t interval, bool& inRange) { inRange = true; if(!sb.isMelee || !extraMeleeAttack) { if(sb.speed > attackTicks) { resetTicks = false; return false; } if(attackTicks % sb.speed >= interval) //already used this spell for this round return false; } if(!sb.range || std::max(std::abs(pos.x - targetPos.x), std::abs(pos.y - targetPos.y)) <= (int32_t)sb.range) return true; inRange = false; return false; } void Monster::onThinkTarget(uint32_t interval) { if(isSummon() || mType->changeTargetSpeed <= 0) return; bool canChangeTarget = true; if(targetChangeCooldown > 0) { targetChangeCooldown -= interval; if(targetChangeCooldown <= 0) { targetChangeCooldown = 0; targetChangeTicks = (uint32_t)mType->changeTargetSpeed; } else canChangeTarget = false; } if(!canChangeTarget) return; targetChangeTicks += interval; if(targetChangeTicks < (uint32_t)mType->changeTargetSpeed) return; targetChangeTicks = 0; targetChangeCooldown = (uint32_t)mType->changeTargetSpeed; if(mType->changeTargetChance < random_range(1, 100)) return; if(mType->targetDistance <= 1) searchTarget(TARGETSEARCH_RANDOM); else searchTarget(TARGETSEARCH_NEAREST); } void Monster::onThinkDefense(uint32_t interval) { resetTicks = true; defenseTicks += interval; for(SpellList::iterator it = mType->spellDefenseList.begin(); it != mType->spellDefenseList.end(); ++it) { if(it->speed > defenseTicks) { if(resetTicks) resetTicks = false; continue; } if(defenseTicks % it->speed >= interval) //already used this spell for this round continue; if((it->chance >= (uint32_t)random_range(1, 100))) { minCombatValue = it->minCombatValue; maxCombatValue = it->maxCombatValue; it->spell->castSpell(this, this); } } if(!isSummon()) { if(mType->maxSummons < 0 || (int32_t)summons.size() < mType->maxSummons) { for(SummonList::iterator it = mType->summonList.begin(); it != mType->summonList.end(); ++it) { if((int32_t)summons.size() >= mType->maxSummons) break; if(it->interval > defenseTicks) { if(resetTicks) resetTicks = false; continue; } if(defenseTicks % it->interval >= interval) continue; uint32_t typeCount = 0; for(CreatureList::iterator cit = summons.begin(); cit != summons.end(); ++cit) { if(!(*cit)->isRemoved() && (*cit)->getMonster() && (*cit)->getMonster()->getName() == it->name) typeCount++; } if(typeCount >= it->amount) continue; if((it->chance >= (uint32_t)random_range(1, 100))) { if(Monster* summon = Monster::createMonster(it->name)) { addSummon(summon); if(g_game.placeCreature(summon, getPosition())) g_game.addMagicEffect(getPosition(), MAGIC_EFFECT_WRAPS_BLUE); else removeSummon(summon); } } } } } if(resetTicks) defenseTicks = 0; } void Monster::onThinkYell(uint32_t interval) { if(mType->yellSpeedTicks <= 0) return; yellTicks += interval; if(yellTicks < mType->yellSpeedTicks) return; yellTicks = 0; if(mType->voiceVector.empty() || (mType->yellChance < (uint32_t)random_range(1, 100))) return; const voiceBlock_t& vb = mType->voiceVector[random_range(0, mType->voiceVector.size() - 1)]; if(vb.yellText) g_game.internalCreatureSay(this, SPEAK_MONSTER_YELL, vb.text, false); else g_game.internalCreatureSay(this, SPEAK_MONSTER_SAY, vb.text, false); } bool Monster::pushItem(Item* item, int32_t radius) { const Position& centerPos = item->getPosition(); PairVector pairVector; pairVector.push_back(PositionPair(-1, -1)); pairVector.push_back(PositionPair(-1, 0)); pairVector.push_back(PositionPair(-1, 1)); pairVector.push_back(PositionPair(0, -1)); pairVector.push_back(PositionPair(0, 1)); pairVector.push_back(PositionPair(1, -1)); pairVector.push_back(PositionPair(1, 0)); pairVector.push_back(PositionPair(1, 1)); std::random_shuffle(pairVector.begin(), pairVector.end()); Position tryPos; for(int32_t n = 1; n <= radius; ++n) { for(PairVector::iterator it = pairVector.begin(); it != pairVector.end(); ++it) { int32_t dx = it->first * n, dy = it->second * n; tryPos = centerPos; tryPos.x = tryPos.x + dx; tryPos.y = tryPos.y + dy; Tile* tile = g_game.getTile(tryPos); if(tile && g_game.canThrowObjectTo(centerPos, tryPos) && g_game.internalMoveItem(this, item->getParent(), tile, INDEX_WHEREEVER, item, item->getItemCount(), NULL) == RET_NOERROR) return true; } } return false; } void Monster::pushItems(Tile* tile) { TileItemVector* items = tile->getItemList(); if(!items) return; //We cannot use iterators here since we can push the item to another tile //which will invalidate the iterator. //start from the end to minimize the amount of traffic int32_t moveCount = 0, removeCount = 0, downItemsSize = tile->getDownItemCount(); Item* item = NULL; for(int32_t i = downItemsSize - 1; i >= 0; --i) { assert(i >= 0 && i < downItemsSize); if((item = items->at(i)) && item->hasProperty(MOVEABLE) && (item->hasProperty(BLOCKPATH) || item->hasProperty(BLOCKSOLID))) { if(moveCount < 20 && pushItem(item, 1)) moveCount++; else if(g_game.internalRemoveItem(this, item) == RET_NOERROR) ++removeCount; } } if(removeCount > 0) g_game.addMagicEffect(tile->getPosition(), MAGIC_EFFECT_POFF); } bool Monster::pushCreature(Creature* creature) { DirVector dirVector; dirVector.push_back(NORTH); dirVector.push_back(SOUTH); dirVector.push_back(WEST); dirVector.push_back(EAST); std::random_shuffle(dirVector.begin(), dirVector.end()); Position monsterPos = creature->getPosition(); Tile* tile = NULL; for(DirVector::iterator it = dirVector.begin(); it != dirVector.end(); ++it) { if((tile = g_game.getTile(Spells::getCasterPosition(creature, (*it)))) && !tile->hasProperty( BLOCKPATH) && g_game.internalMoveCreature(creature, (*it)) == RET_NOERROR) return true; } return false; } void Monster::pushCreatures(Tile* tile) { CreatureVector* creatures = tile->getCreatures(); if(!creatures) return; bool effect = false; Monster* monster = NULL; for(uint32_t i = 0; i < creatures->size();) { if((monster = creatures->at(i)->getMonster()) && monster->isPushable()) { if(pushCreature(monster)) continue; monster->setDropLoot(LOOT_DROP_NONE); monster->changeHealth(-monster->getHealth()); if(!effect) effect = true; } ++i; } if(effect) g_game.addMagicEffect(tile->getPosition(), MAGIC_EFFECT_BLOCKHIT); } bool Monster::getNextStep(Direction& dir, uint32_t& flags) { if(isIdle || getHealth() <= 0) { //we dont have anyone watching might aswell stop walking eventWalk = 0; return false; } bool result = false; if((!followCreature || !hasFollowPath) && !isSummon()) { if(followCreature || getTimeSinceLastMove() > 1000) //choose a random direction result = getRandomStep(getPosition(), dir); } else if(isSummon() || followCreature) { result = Creature::getNextStep(dir, flags); if(!result) { //target dancing if(attackedCreature && attackedCreature == followCreature) { if(isFleeing()) result = getDanceStep(getPosition(), dir, false, false); else if(mType->staticAttackChance < (uint32_t)random_range(1, 100)) result = getDanceStep(getPosition(), dir); } } else flags |= FLAG_PATHFINDING; } if(result && (canPushItems() || canPushCreatures())) { if(Tile* tile = g_game.getTile(Spells::getCasterPosition(this, dir))) { if(canPushItems()) pushItems(tile); if(canPushCreatures()) pushCreatures(tile); } #ifdef __DEBUG__ else std::cout << "[Warning - Monster::getNextStep] no tile found." << std::endl; #endif } return result; } bool Monster::getRandomStep(const Position& creaturePos, Direction& dir) { DirVector dirVector; dirVector.push_back(NORTH); dirVector.push_back(SOUTH); dirVector.push_back(WEST); dirVector.push_back(EAST); std::random_shuffle(dirVector.begin(), dirVector.end()); for(DirVector::iterator it = dirVector.begin(); it != dirVector.end(); ++it) { if(!canWalkTo(creaturePos, *it)) continue; dir = *it; return true; } return false; } bool Monster::getDanceStep(const Position& creaturePos, Direction& dir, bool keepAttack /*= true*/, bool keepDistance /*= true*/) { assert(attackedCreature); bool canDoAttackNow = canUseAttack(creaturePos, attackedCreature); const Position& centerPos = attackedCreature->getPosition(); uint32_t tmpDist, centerToDist = std::max(std::abs(creaturePos.x - centerPos.x), std::abs(creaturePos.y - centerPos.y)); DirVector dirVector; if(!keepDistance || creaturePos.y - centerPos.y >= 0) { tmpDist = std::max(std::abs((creaturePos.x) - centerPos.x), std::abs((creaturePos.y - 1) - centerPos.y)); if(tmpDist == centerToDist && canWalkTo(creaturePos, NORTH)) { bool result = true; if(keepAttack) result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature)); if(result) dirVector.push_back(NORTH); } } if(!keepDistance || creaturePos.y - centerPos.y <= 0) { tmpDist = std::max(std::abs((creaturePos.x) - centerPos.x), std::abs((creaturePos.y + 1) - centerPos.y)); if(tmpDist == centerToDist && canWalkTo(creaturePos, SOUTH)) { bool result = true; if(keepAttack) result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature)); if(result) dirVector.push_back(SOUTH); } } if(!keepDistance || creaturePos.x - centerPos.x >= 0) { tmpDist = std::max(std::abs((creaturePos.x + 1) - centerPos.x), std::abs((creaturePos.y) - centerPos.y)); if(tmpDist == centerToDist && canWalkTo(creaturePos, EAST)) { bool result = true; if(keepAttack) result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature)); if(result) dirVector.push_back(EAST); } } if(!keepDistance || creaturePos.x - centerPos.x <= 0) { tmpDist = std::max(std::abs((creaturePos.x - 1) - centerPos.x), std::abs((creaturePos.y) - centerPos.y)); if(tmpDist == centerToDist && canWalkTo(creaturePos, WEST)) { bool result = true; if(keepAttack) result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature)); if(result) dirVector.push_back(WEST); } } if(dirVector.empty()) return false; std::random_shuffle(dirVector.begin(), dirVector.end()); dir = dirVector[random_range(0, dirVector.size() - 1)]; return true; } bool Monster::isInSpawnRange(const Position& toPos) { return masterRadius == -1 || !inDespawnRange(toPos); } bool Monster::canWalkTo(Position pos, Direction dir) { if(getNoMove()) return false; switch(dir) { case NORTH: pos.y += -1; break; case WEST: pos.x += -1; break; case EAST: pos.x += 1; break; case SOUTH: pos.y += 1; break; default: break; } if(!isInSpawnRange(pos) || !getWalkCache(pos)) return false; Tile* tile = g_game.getTile(pos); if(!tile || getTile()->isSwimmingPool(false) != tile->isSwimmingPool(false)) // prevent monsters entering/exiting to swimming pool return false; return !tile->getTopVisibleCreature(this) && tile->__queryAdd( 0, this, 1, FLAG_PATHFINDING) == RET_NOERROR; } bool Monster::onDeath() { if(!Creature::onDeath()) return false; destroySummons(); clearTargetList(); clearFriendList(); setAttackedCreature(NULL); onIdleStatus(); if(raid) { raid->unRef(); raid = NULL; } g_game.removeCreature(this, false); return true; } Item* Monster::createCorpse(DeathList deathList) { Item* corpse = Creature::createCorpse(deathList); if(!corpse) return NULL; if(mType->corpseUnique) corpse->setUniqueId(mType->corpseUnique); if(mType->corpseAction) corpse->setActionId(mType->corpseAction); DeathEntry ownerEntry = deathList[0]; if(ownerEntry.isNameKill()) return corpse; Creature* owner = ownerEntry.getKillerCreature(); if(!owner) return corpse; uint32_t ownerId = 0; if(owner->getPlayer()) ownerId = owner->getID(); else if(owner->getMaster() && owner->getPlayerMaster()) ownerId = owner->getMaster()->getID(); if(ownerId) corpse->setCorpseOwner(ownerId); return corpse; } bool Monster::inDespawnRange(const Position& pos) { if(!spawn || mType->isLureable) return false; int32_t radius = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRADIUS); if(!radius) return false; if(!Spawns::getInstance()->isInZone(masterPosition, radius, pos)) return true; int32_t range = g_config.getNumber(ConfigManager::DEFAULT_DESPAWNRANGE); if(!range) return false; return std::abs(pos.z - masterPosition.z) > range; } bool Monster::despawn() { return inDespawnRange(getPosition()); } bool Monster::getCombatValues(int32_t& min, int32_t& max) { if(!minCombatValue && !maxCombatValue) return false; double multiplier; if(maxCombatValue > 0) //defense multiplier = g_config.getDouble(ConfigManager::RATE_MONSTER_DEFENSE); else //attack multiplier = g_config.getDouble(ConfigManager::RATE_MONSTER_ATTACK); min = (int32_t)(minCombatValue * multiplier); max = (int32_t)(maxCombatValue * multiplier); return true; } void Monster::updateLookDirection() { Direction newDir = getDirection(); if(attackedCreature) { const Position& pos = getPosition(); const Position& attackedCreaturePos = attackedCreature->getPosition(); int32_t dx = attackedCreaturePos.x - pos.x, dy = attackedCreaturePos.y - pos.y; if(std::abs(dx) > std::abs(dy)) { //look EAST/WEST if(dx < 0) newDir = WEST; else newDir = EAST; } else if(std::abs(dx) < std::abs(dy)) { //look NORTH/SOUTH if(dy < 0) newDir = NORTH; else newDir = SOUTH; } else if(dx < 0 && dy < 0) { if(getDirection() == SOUTH) newDir = WEST; else if(getDirection() == EAST) newDir = NORTH; } else if(dx < 0 && dy > 0) { if(getDirection() == NORTH) newDir = WEST; else if(getDirection() == EAST) newDir = SOUTH; } else if(dx > 0 && dy < 0) { if(getDirection() == SOUTH) newDir = EAST; else if(getDirection() == WEST) newDir = NORTH; } else if(getDirection() == NORTH) newDir = EAST; else if(getDirection() == WEST) newDir = SOUTH; } g_game.internalCreatureTurn(this, newDir); } void Monster::dropLoot(Container* corpse) { if(corpse && lootDrop == LOOT_DROP_FULL) mType->dropLoot(corpse); } bool Monster::isImmune(CombatType_t type) const { ElementMap::const_iterator it = mType->elementMap.find(type); if(it == mType->elementMap.end()) return Creature::isImmune(type); return it->second >= 100; } void Monster::setNormalCreatureLight() { internalLight.level = mType->lightLevel; internalLight.color = mType->lightColor; } void Monster::drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage) { Creature::drainHealth(attacker, combatType, damage); if(isInvisible()) removeCondition(CONDITION_INVISIBLE); } void Monster::changeHealth(int32_t healthChange) { //In case a player with ignore flag set attacks the monster setIdle(false); Creature::changeHealth(healthChange); } bool Monster::challengeCreature(Creature* creature) { if(isSummon() || !selectTarget(creature)) return false; targetChangeCooldown = 8000; targetChangeTicks = 0; return true; } bool Monster::convinceCreature(Creature* creature) { Player* player = creature->getPlayer(); if(player && !player->hasFlag(PlayerFlag_CanConvinceAll) && !mType->isConvinceable) return false; Creature* oldMaster = NULL; if(isSummon()) oldMaster = getMaster(); if(oldMaster) { if(oldMaster->getPlayer() || oldMaster == creature) return false; oldMaster->removeSummon(this); } setFollowCreature(NULL); setAttackedCreature(NULL); destroySummons(); creature->addSummon(this); updateTargetList(); updateIdleStatus(); //Notify surrounding about the change SpectatorVec list; g_game.getSpectators(list, getPosition(), false, true); g_game.getSpectators(list, creature->getPosition(), true, true); isMasterInRange = true; for(SpectatorVec::iterator it = list.begin(); it != list.end(); ++it) (*it)->onCreatureConvinced(creature, this); if(spawn) { spawn->removeMonster(this); spawn = NULL; masterRadius = -1; } if(raid) { raid->unRef(); raid = NULL; } return true; } void Monster::onCreatureConvinced(const Creature* convincer, const Creature* creature) { if(convincer == this || (!isFriend(creature) && !isOpponent(creature))) return; updateTargetList(); updateIdleStatus(); } void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const { Creature::getPathSearchParams(creature, fpp); fpp.minTargetDist = 1; fpp.maxTargetDist = mType->targetDistance; if(isSummon()) { if(getMaster() == creature) { fpp.maxTargetDist = 2; fpp.fullPathSearch = false; } else if(mType->targetDistance <= 1) fpp.fullPathSearch = true; else fpp.fullPathSearch = !canUseAttack(getPosition(), creature); } else if(isFleeing()) { //Distance should be higher than the client view range (Map::maxClientViewportX/Map::maxClientViewportY) fpp.maxTargetDist = Map::maxViewportX; fpp.clearSight = fpp.fullPathSearch = false; fpp.keepDistance = true; } else if(mType->targetDistance <= 1) fpp.fullPathSearch = true; else fpp.fullPathSearch = !canUseAttack(getPosition(), creature); }
monster.h
//////////////////////////////////////////////////////////////////////// // 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 <http://www.gnu.org/licenses/>. //////////////////////////////////////////////////////////////////////// #ifndef __MONSTER__ #define __MONSTER__ #include "monsters.h" #include "raids.h" #include "tile.h" class Creature; class Game; class Spawn; enum TargetSearchType_t { TARGETSEARCH_DEFAULT, TARGETSEARCH_RANDOM, TARGETSEARCH_ATTACKRANGE, TARGETSEARCH_NEAREST }; typedef std::list<Creature*> CreatureList; class Monster : public Creature { private: Monster(MonsterType* _mType); public: #ifdef __ENABLE_SERVER_DIAGNOSTIC__ static uint32_t monsterCount; #endif virtual ~Monster(); static Monster* createMonster(MonsterType* mType); static Monster* createMonster(const std::string& name); virtual Monster* getMonster() {return this;} virtual const Monster* getMonster() const {return this;} virtual uint32_t rangeId() {return 0x40000000;} static AutoList<Monster> autoList; void addList() {autoList[id] = this;} void removeList() {autoList.erase(id);} virtual const std::string& getName() const {return mType->name;} virtual const std::string& getNameDescription() const {return mType->nameDescription;} virtual std::string getDescription(int32_t lookDistance) const {return mType->nameDescription + ".";} virtual RaceType_t getRace() const {return mType->race;} virtual int32_t getArmor() const {return mType->armor;} virtual int32_t getDefense() const {return mType->defense;} virtual MonsterType* getMonsterType() const {return mType;} virtual bool isPushable() const {return mType->pushable && (baseSpeed > 0);} virtual bool isAttackable() const {return mType->isAttackable;} virtual bool isImmune(CombatType_t type) const; bool canPushItems() const {return mType->canPushItems;} bool canPushCreatures() const {return mType->canPushCreatures;} bool isHostile() const {return mType->isHostile;} virtual bool isWalkable() const {return mType->isWalkable;} virtual bool canSeeInvisibility() const {return Creature::isImmune(CONDITION_INVISIBLE);} uint32_t getManaCost() const {return mType->manaCost;} void setSpawn(Spawn* _spawn) {spawn = _spawn;} void setRaid(Raid* _raid) {raid = _raid;} virtual void onAttackedCreature(Creature* target); virtual void onAttackedCreatureDisappear(bool isLogout); virtual void onAttackedCreatureDrain(Creature* target, int32_t points); virtual void onCreatureAppear(const Creature* creature); virtual void onCreatureDisappear(const Creature* creature, bool isLogout); virtual void onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport); virtual void drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage); virtual void changeHealth(int32_t healthChange); virtual bool getNextStep(Direction& dir, uint32_t& flags); virtual void onFollowCreatureComplete(const Creature* creature); virtual void onThink(uint32_t interval); virtual bool challengeCreature(Creature* creature); virtual bool convinceCreature(Creature* creature); virtual void setNormalCreatureLight(); virtual bool getCombatValues(int32_t& min, int32_t& max); virtual void doAttacking(uint32_t interval); virtual bool hasExtraSwing() {return extraMeleeAttack;} bool searchTarget(TargetSearchType_t searchType = TARGETSEARCH_DEFAULT); bool selectTarget(Creature* creature); const CreatureList& getTargetList() {return targetList;} const CreatureList& getFriendList() {return friendList;} bool isTarget(Creature* creature); bool isFleeing() const {return getHealth() <= mType->runAwayHealth;} virtual BlockType_t blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, bool checkDefense = false, bool checkArmor = false); private: CreatureList targetList; CreatureList friendList; MonsterType* mType; int32_t minCombatValue; int32_t maxCombatValue; uint32_t attackTicks; uint32_t targetTicks; uint32_t targetChangeTicks; uint32_t defenseTicks; uint32_t yellTicks; int32_t targetChangeCooldown; bool resetTicks; bool isIdle; bool extraMeleeAttack; Spawn* spawn; Raid* raid; bool isMasterInRange; bool teleportToMaster; virtual void onCreatureEnter(Creature* creature); virtual void onCreatureLeave(Creature* creature); void onCreatureFound(Creature* creature, bool pushFront = false); bool doTeleportToMaster(); void updateLookDirection(); void updateTargetList(); void clearTargetList(); void clearFriendList(); virtual bool onDeath(); virtual Item* createCorpse(DeathList deathList); bool despawn(); bool inDespawnRange(const Position& pos); void setIdle(bool _idle); void updateIdleStatus(); bool getIdleStatus() const {return isIdle;} virtual void onAddCondition(ConditionType_t type, bool hadCondition); virtual void onEndCondition(ConditionType_t type); virtual void onCreatureConvinced(const Creature* convincer, const Creature* creature); bool canUseAttack(const Position& pos, const Creature* target) const; bool canUseSpell(const Position& pos, const Position& targetPos, const spellBlock_t& sb, uint32_t interval, bool& inRange); bool getRandomStep(const Position& creaturePos, Direction& dir); bool getDanceStep(const Position& creaturePos, Direction& dir, bool keepAttack = true, bool keepDistance = true); bool isInSpawnRange(const Position& toPos); bool canWalkTo(Position pos, Direction dir); bool pushItem(Item* item, int32_t radius); void pushItems(Tile* tile); bool pushCreature(Creature* creature); void pushCreatures(Tile* tile); void onThinkTarget(uint32_t interval); void onThinkYell(uint32_t interval); void onThinkDefense(uint32_t interval); bool isFriend(const Creature* creature); bool isOpponent(const Creature* creature); virtual uint64_t getLostExperience() const {return ((skillLoss ? mType->experience : 0));} virtual void dropLoot(Container* corpse); virtual uint32_t getDamageImmunities() const {return mType->damageImmunities;} virtual uint32_t getConditionImmunities() const {return mType->conditionImmunities;} virtual uint16_t getLookCorpse() {return mType->lookCorpse;} virtual uint16_t getLookCorpse() const {return mType->lookCorpse;} virtual void getPathSearchParams(const Creature* creature, FindPathParams& fpp) const; virtual bool useCacheMap() const {return true;} }; #endif
map.cpp
//////////////////////////////////////////////////////////////////////// // 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 <http://www.gnu.org/licenses/>. //////////////////////////////////////////////////////////////////////// #include "otpch.h" #include <iomanip> #include <boost/config.hpp> #include <boost/bind.hpp> #include "iomap.h" #include "map.h" #include "tile.h" #include "creature.h" #include "player.h" #include "combat.h" #include "iomapserialize.h" #include "items.h" #include "game.h" extern Game g_game; Map::Map() { mapWidth = 0; mapHeight = 0; } bool Map::loadMap(const std::string& identifier) { int64_t start = OTSYS_TIME(); IOMap* loader = new IOMap(); if(!loader->loadMap(this, identifier)) { std::cout << "> FATAL: OTBM Loader - " << loader->getLastErrorString() << std::endl; return false; } std::cout << "> Map loading time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; start = OTSYS_TIME(); if(!loader->loadSpawns(this)) std::cout << "> WARNING: Could not load spawn data." << std::endl; if(!loader->loadHouses(this)) std::cout << "> WARNING: Could not load house data." << std::endl; delete loader; std::cout << "> Data parsing time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; start = OTSYS_TIME(); IOMapSerialize::getInstance()->updateHouses(); IOMapSerialize::getInstance()->updateAuctions(); std::cout << "> Houses synchronization time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; start = OTSYS_TIME(); IOMapSerialize::getInstance()->loadHouses(); IOMapSerialize::getInstance()->loadMap(this); std::cout << "> Content unserialization time: " << (OTSYS_TIME() - start) / (1000.) << " seconds." << std::endl; return true; } bool Map::saveMap() { IOMapSerialize* IOLoader = IOMapSerialize::getInstance(); bool saved = false; for(uint32_t tries = 0; tries < 3; ++tries) { if(!IOLoader->saveHouses()) continue; saved = true; break; } if(!saved) return false; saved = false; for(uint32_t tries = 0; tries < 3; ++tries) { if(!IOLoader->saveMap(this)) continue; saved = true; break; } return saved; } Tile* Map::getTile(int32_t x, int32_t y, int32_t z) { if(x < 0 || x > 0xFFFF || y < 0 || y > 0xFFFF || z < 0 || z >= MAP_MAX_LAYERS) return NULL; QTreeLeafNode* leaf = QTreeNode::getLeafStatic(&root, x, y); if(!leaf) return NULL; Floor* floor = leaf->getFloor(z); if(!floor) return NULL; return floor->tiles[x & FLOOR_MASK][y & FLOOR_MASK]; } void Map::setTile(uint16_t x, uint16_t y, uint16_t z, Tile* newTile) { if(z >= MAP_MAX_LAYERS) { std::cout << "[Error - Map::setTile]: Attempt to set tile on invalid Z coordinate - " << z << "!" << std::endl; return; } QTreeLeafNode::newLeaf = false; QTreeLeafNode* leaf = root.createLeaf(x, y, 15); if(QTreeLeafNode::newLeaf) { //update north QTreeLeafNode* northLeaf = root.getLeaf(x, y - FLOOR_SIZE); if(northLeaf) northLeaf->m_leafS = leaf; //update west leaf QTreeLeafNode* westLeaf = root.getLeaf(x - FLOOR_SIZE, y); if(westLeaf) westLeaf->m_leafE = leaf; //update south QTreeLeafNode* southLeaf = root.getLeaf(x, y + FLOOR_SIZE); if(southLeaf) leaf->m_leafS = southLeaf; //update east QTreeLeafNode* eastLeaf = root.getLeaf(x + FLOOR_SIZE, y); if(eastLeaf) leaf->m_leafE = eastLeaf; } uint32_t offsetX = x & FLOOR_MASK, offsetY = y & FLOOR_MASK; Floor* floor = leaf->createFloor(z); if(!floor->tiles[offsetX][offsetY]) { floor->tiles[offsetX][offsetY] = newTile; newTile->qt_node = leaf; } else std::cout << "[Error - Map::setTile] Tile already exists - pos " << offsetX << "/" << offsetY << "/" << z << std::endl; if(newTile->hasFlag(TILESTATE_REFRESH)) { RefreshBlock_t rb; if(TileItemVector* tileItems = newTile->getItemList()) { for(ItemVector::iterator it = tileItems->getBeginDownItem(); it != tileItems->getEndDownItem(); ++it) rb.list.push_back((*it)->clone()); } rb.lastRefresh = OTSYS_TIME(); g_game.addRefreshTile(newTile, rb); } } bool Map::placeCreature(const Position& centerPos, Creature* creature, bool extendedPos /*= false*/, bool forced /*= false*/) { bool foundTile = false, placeInPz = false; Tile* tile = getTile(centerPos); if(tile) { placeInPz = tile->hasFlag(TILESTATE_PROTECTIONZONE); uint32_t flags = FLAG_IGNOREBLOCKITEM; if(creature->isAccountManager()) flags |= FLAG_IGNOREBLOCKCREATURE; ReturnValue ret = tile->__queryAdd(0, creature, 1, flags); if(forced || ret == RET_NOERROR || ret == RET_PLAYERISNOTINVITED) foundTile = true; } size_t shufflePos = 0; PairVector relList; if(extendedPos) { shufflePos = 8; relList.push_back(PositionPair(-2, 0)); relList.push_back(PositionPair(0, -2)); relList.push_back(PositionPair(0, 2)); relList.push_back(PositionPair(2, 0)); std::random_shuffle(relList.begin(), relList.end()); } relList.push_back(PositionPair(-1, -1)); relList.push_back(PositionPair(-1, 0)); relList.push_back(PositionPair(-1, 1)); relList.push_back(PositionPair(0, -1)); relList.push_back(PositionPair(0, 1)); relList.push_back(PositionPair(1, -1)); relList.push_back(PositionPair(1, 0)); relList.push_back(PositionPair(1, 1)); std::random_shuffle(relList.begin() + shufflePos, relList.end()); uint32_t radius = 1; Position tryPos; for(uint32_t n = 1; n <= radius && !foundTile; ++n) { for(PairVector::iterator it = relList.begin(); it != relList.end() && !foundTile; ++it) { int32_t dx = it->first * n, dy = it->second * n; tryPos = centerPos; tryPos.x = tryPos.x + dx; tryPos.y = tryPos.y + dy; if(!(tile = getTile(tryPos)) || (placeInPz && !tile->hasFlag(TILESTATE_PROTECTIONZONE))) continue; if(tile->__queryAdd(0, creature, 1, 0) == RET_NOERROR) { if(!extendedPos) { foundTile = true; break; } if(isSightClear(centerPos, tryPos, false)) { foundTile = true; break; } } } } if(!foundTile) return false; int32_t index = 0; uint32_t flags = 0; Item* toItem = NULL; if(Cylinder* toCylinder = tile->__queryDestination(index, creature, &toItem, flags)) { toCylinder->__internalAddThing(creature); if(Tile* toTile = toCylinder->getTile()) toTile->qt_node->addCreature(creature); } return true; } bool Map::removeCreature(Creature* creature) { Tile* tile = creature->getTile(); if(!tile) return false; tile->qt_node->removeCreature(creature); tile->__removeThing(creature, 0); return true; } void Map::getSpectatorsInternal(SpectatorVec& list, const Position& centerPos, bool checkForDuplicate, int32_t minRangeX, int32_t maxRangeX, int32_t minRangeY, int32_t maxRangeY, int32_t minRangeZ, int32_t maxRangeZ) { int32_t minoffset = centerPos.z - maxRangeZ, maxoffset = centerPos.z - minRangeZ, x1 = std::min((int32_t)0xFFFF, std::max((int32_t)0, (centerPos.x + minRangeX + minoffset))), y1 = std::min((int32_t)0xFFFF, std::max((int32_t)0, (centerPos.y + minRangeY + minoffset))), x2 = std::min((int32_t)0xFFFF, std::max((int32_t)0, (centerPos.x + maxRangeX + maxoffset))), y2 = std::min((int32_t)0xFFFF, std::max((int32_t)0, (centerPos.y + maxRangeY + maxoffset))), startx1 = x1 - (x1 % FLOOR_SIZE), starty1 = y1 - (y1 % FLOOR_SIZE), endx2 = x2 - (x2 % FLOOR_SIZE), endy2 = y2 - (y2 % FLOOR_SIZE); QTreeLeafNode* startLeaf = getLeaf(startx1, starty1); QTreeLeafNode* leafS = startLeaf; QTreeLeafNode* leafE; for(int32_t ny = starty1; ny <= endy2; ny += FLOOR_SIZE) { leafE = leafS; for(int32_t nx = startx1; nx <= endx2; nx += FLOOR_SIZE) { if(leafE) { CreatureVector& nodeList = leafE->creatureList; CreatureVector::const_iterator it = nodeList.begin(); if(it != nodeList.end()) { do { Creature* creature = (*it); const Position& pos = creature->getPosition(); int32_t offsetZ = centerPos.z - pos.z; if(pos.z < minRangeZ || pos.z > maxRangeZ) continue; if(pos.y < (centerPos.y + minRangeY + offsetZ) || pos.y > (centerPos.y + maxRangeY + offsetZ)) continue; if(pos.x < (centerPos.x + minRangeX + offsetZ) || pos.x > (centerPos.x + maxRangeX + offsetZ)) continue; if(!checkForDuplicate || std::find(list.begin(), list.end(), creature) == list.end()) list.push_back(creature); } while(++it != nodeList.end()); } leafE = leafE->stepEast(); } else leafE = getLeaf(nx + FLOOR_SIZE, ny); } if(leafS) leafS = leafS->stepSouth(); else leafS = getLeaf(startx1, ny + FLOOR_SIZE); } } void Map::getSpectators(SpectatorVec& list, const Position& centerPos, bool checkforduplicate /*= false*/, bool multifloor /*= false*/, int32_t minRangeX /*= 0*/, int32_t maxRangeX /*= 0*/, int32_t minRangeY /*= 0*/, int32_t maxRangeY /*= 0*/) { if(centerPos.z >= MAP_MAX_LAYERS) return; bool foundCache = false, cacheResult = false; if(!minRangeX && !maxRangeX && !minRangeY && !maxRangeY && multifloor && !checkforduplicate) { SpectatorCache::iterator it = spectatorCache.find(centerPos); if(it != spectatorCache.end()) { list = *it->second; foundCache = true; } else cacheResult = true; } if(!foundCache) { minRangeX = (!minRangeX ? -maxViewportX : -minRangeX); maxRangeX = (!maxRangeX ? maxViewportX : maxRangeX); minRangeY = (!minRangeY ? -maxViewportY : -minRangeY); maxRangeY = (!maxRangeY ? maxViewportY : maxRangeY); int32_t minRangeZ, maxRangeZ; if(multifloor) { if(centerPos.z > 7) { //underground, 8->15 minRangeZ = std::max(centerPos.z - 2, 0); maxRangeZ = std::min(centerPos.z + 2, MAP_MAX_LAYERS - 1); } //above ground else if(centerPos.z == 6) { minRangeZ = 0; maxRangeZ = 8; } else if(centerPos.z == 7) { minRangeZ = 0; maxRangeZ = 9; } else { minRangeZ = 0; maxRangeZ = 7; } } else { minRangeZ = centerPos.z; maxRangeZ = centerPos.z; } getSpectatorsInternal(list, centerPos, true, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ); if(cacheResult) spectatorCache[centerPos].reset(new SpectatorVec(list)); } } const SpectatorVec& Map::getSpectators(const Position& centerPos) { if(centerPos.z >= MAP_MAX_LAYERS) { boost::shared_ptr<SpectatorVec> p(new SpectatorVec()); SpectatorVec& list = *p; return list; } SpectatorCache::iterator it = spectatorCache.find(centerPos); if(it != spectatorCache.end()) return *it->second; boost::shared_ptr<SpectatorVec> p(new SpectatorVec()); spectatorCache[centerPos] = p; SpectatorVec& list = *p; int32_t minRangeX = -maxViewportX, maxRangeX = maxViewportX, minRangeY = -maxViewportY, maxRangeY = maxViewportY, minRangeZ, maxRangeZ; if(centerPos.z > 7) { //underground, 8->15 minRangeZ = std::max(centerPos.z - 2, 0); maxRangeZ = std::min(centerPos.z + 2, MAP_MAX_LAYERS - 1); } //above ground else if(centerPos.z == 6) { minRangeZ = 0; maxRangeZ = 8; } else if(centerPos.z == 7) { minRangeZ = 0; maxRangeZ = 9; } else { minRangeZ = 0; maxRangeZ = 7; } getSpectatorsInternal(list, centerPos, false, minRangeX, maxRangeX, minRangeY, maxRangeY, minRangeZ, maxRangeZ); return list; } bool Map::canThrowObjectTo(const Position& fromPos, const Position& toPos, bool checkLineOfSight /*= true*/, int32_t rangex /*= Map::maxClientViewportX*/, int32_t rangey /*= Map::maxClientViewportY*/) { //z checks //underground 8->15 //ground level and above 7->0 if((fromPos.z >= 8 && toPos.z < 8) || (toPos.z >= 8 && fromPos.z < 8) || fromPos.z - fromPos.z > 2) return false; int32_t deltax = std::abs(fromPos.x - toPos.x), deltay = std::abs( fromPos.y - toPos.y), deltaz = std::abs(fromPos.z - toPos.z); if(deltax - deltaz > rangex || deltay - deltaz > rangey) return false; if(!checkLineOfSight) return true; return isSightClear(fromPos, toPos, false); } bool Map::checkSightLine(const Position& fromPos, const Position& toPos) const { Position start = fromPos; Position end = toPos; int32_t x, y, z, dx = std::abs(start.x - end.x), dy = std::abs(start.y - end.y), dz = std::abs(start.z - end.z), sx, sy, sz, ey, ez, max = dx, dir = 0; if(dy > max) { max = dy; dir = 1; } if(dz > max) { max = dz; dir = 2; } switch(dir) { case 1: //x -> y //y -> x //z -> z std::swap(start.x, start.y); std::swap(end.x, end.y); std::swap(dx, dy); break; case 2: //x -> z //y -> y //z -> x std::swap(start.x, start.z); std::swap(end.x, end.z); std::swap(dx, dz); break; default: //x -> x //y -> y //z -> z break; } sx = ((start.x < end.x) ? 1 : -1); sy = ((start.y < end.y) ? 1 : -1); sz = ((start.z < end.z) ? 1 : -1); ey = ez = 0; x = start.x; y = start.y; z = start.z; int32_t lastrx = 0, lastry = 0, lastrz = 0; for(; x != end.x + sx; x += sx) { int32_t rx, ry, rz; switch(dir) { case 1: rx = y; ry = x; rz = z; break; case 2: rx = z; ry = y; rz = x; break; default: rx = x; ry = y; rz = z; break; } if(!lastrx && !lastry && !lastrz) { lastrx = rx; lastry = ry; lastrz = rz; } if(lastrz != rz || ((toPos.x != rx || toPos.y != ry || toPos.z != rz) && (fromPos.x != rx || fromPos.y != ry || fromPos.z != rz))) { if(lastrz != rz && const_cast<Map*>(this)->getTile(lastrx, lastry, std::min(lastrz, rz))) return false; lastrx = rx; lastry = ry; lastrz = rz; const Tile* tile = const_cast<Map*>(this)->getTile(rx, ry, rz); if(tile && tile->hasProperty(BLOCKPROJECTILE)) return false; } ey += dy; ez += dz; if(2 * ey >= dx) { y += sy; ey -= dx; } if(2 * ez >= dx) { z += sz; ez -= dx; } } return true; } bool Map::isSightClear(const Position& fromPos, const Position& toPos, bool floorCheck) const { if(floorCheck && fromPos.z != toPos.z) return false; // Cast two converging rays and see if either yields a result. return checkSightLine(fromPos, toPos) || checkSightLine(toPos, fromPos); } const Tile* Map::canWalkTo(const Creature* creature, const Position& pos) { switch(creature->getWalkCache(pos)) { case 0: return NULL; case 1: return getTile(pos); default: break; } //used for none-cached tiles Tile* tile = getTile(pos); if(creature->getTile() != tile && (!tile || tile->__queryAdd(0, creature, 1, FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) != RET_NOERROR)) return NULL; return tile; } bool Map::getPathTo(const Creature* creature, const Position& destPos, std::list<Direction>& listDir, int32_t maxSearchDist /*= -1*/) { if(!canWalkTo(creature, destPos)) return false; Position startPos = destPos; Position endPos = creature->getPosition(); listDir.clear(); if(startPos.z != endPos.z) return false; AStarNodes nodes; AStarNode* startNode = nodes.createOpenNode(); startNode->x = startPos.x; startNode->y = startPos.y; startNode->g = 0; startNode->h = nodes.getEstimatedDistance(startPos.x, startPos.y, endPos.x, endPos.y); startNode->f = startNode->g + startNode->h; startNode->parent = NULL; Position pos; pos.z = startPos.z; static int32_t neighbourOrderList[8][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1}, //diagonal {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, }; AStarNode* found = NULL; AStarNode* n = NULL; const Tile* tile = NULL; while(maxSearchDist != -1 || nodes.countClosedNodes() < 100) { if(!(n = nodes.getBestNode())) { listDir.clear(); return false; //no path found } if(n->x == endPos.x && n->y == endPos.y) { found = n; break; } for(uint8_t i = 0; i < 8; ++i) { pos.x = n->x + neighbourOrderList[i][0]; pos.y = n->y + neighbourOrderList[i][1]; bool outOfRange = false; if(maxSearchDist != -1 && (std::abs(endPos.x - pos.x) > maxSearchDist || std::abs(endPos.y - pos.y) > maxSearchDist)) outOfRange = true; if(!outOfRange && (tile = canWalkTo(creature, pos))) { //The cost (g) for this neighbour int32_t cost = nodes.getMapWalkCost(creature, n, tile, pos), extraCost = nodes.getTileWalkCost(creature, tile), newg = n->g + cost + extraCost; //Check if the node is already in the closed/open list //If it exists and the nodes already on them has a lower cost (g) then we can ignore this neighbour node AStarNode* neighbourNode = nodes.getNodeInList(pos.x, pos.y); if(neighbourNode) { if(neighbourNode->g <= newg) //The node on the closed/open list is cheaper than this one continue; nodes.openNode(neighbourNode); } else if(!(neighbourNode = nodes.createOpenNode())) //Does not exist in the open/closed list, create a new node { //seems we ran out of nodes listDir.clear(); return false; } //This node is the best node so far with this state neighbourNode->x = pos.x; neighbourNode->y = pos.y; neighbourNode->g = newg; neighbourNode->h = nodes.getEstimatedDistance(neighbourNode->x, neighbourNode->y, endPos.x, endPos.y); neighbourNode->f = neighbourNode->g + neighbourNode->h; neighbourNode->parent = n; } } nodes.closeNode(n); } int32_t prevx = endPos.x, prevy = endPos.y, dx, dy; while(found) { pos.x = found->x; pos.y = found->y; dx = pos.x - prevx; dy = pos.y - prevy; prevx = pos.x; prevy = pos.y; found = found->parent; if(dx == -1 && dy == -1) listDir.push_back(NORTHWEST); else if(dx == 1 && dy == -1) listDir.push_back(NORTHEAST); else if(dx == -1 && dy == 1) listDir.push_back(SOUTHWEST); else if(dx == 1 && dy == 1) listDir.push_back(SOUTHEAST); else if(dx == -1) listDir.push_back(WEST); else if(dx == 1) listDir.push_back(EAST); else if(dy == -1) listDir.push_back(NORTH); else if(dy == 1) listDir.push_back(SOUTH); } return !listDir.empty(); } bool Map::getPathMatching(const Creature* creature, std::list<Direction>& dirList, const FrozenPathingConditionCall& pathCondition, const FindPathParams& fpp) { Position startPos = creature->getPosition(); Position endPos; AStarNodes nodes; AStarNode* startNode = nodes.createOpenNode(); startNode->x = startPos.x; startNode->y = startPos.y; startNode->f = 0; startNode->parent = NULL; dirList.clear(); int32_t bestMatch = 0; Position pos; pos.z = startPos.z; static int32_t neighbourOrderList[8][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1}, //diagonal {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, }; AStarNode* found = NULL; AStarNode* n = NULL; const Tile* tile = NULL; while(fpp.maxSearchDist != -1 || nodes.countClosedNodes() < 100) { if(!(n = nodes.getBestNode())) { if(found) //not quite what we want, but we found something break; dirList.clear(); return false; //no path found } if(pathCondition(startPos, Position(n->x, n->y, startPos.z), fpp, bestMatch)) { found = n; endPos = Position(n->x, n->y, startPos.z); if(!bestMatch) break; } int32_t dirCount = (fpp.allowDiagonal ? 8 : 4); for(int32_t i = 0; i < dirCount; ++i) { pos.x = n->x + neighbourOrderList[i][0]; pos.y = n->y + neighbourOrderList[i][1]; bool inRange = true; if(fpp.maxSearchDist != -1 && (std::abs(startPos.x - pos.x) > fpp.maxSearchDist || std::abs(startPos.y - pos.y) > fpp.maxSearchDist)) inRange = false; if(fpp.keepDistance) { if(!pathCondition.isInRange(startPos, pos, fpp)) inRange = false; } if(inRange && (tile = canWalkTo(creature, pos))) { //The cost (g) for this neighbour int32_t cost = nodes.getMapWalkCost(creature, n, tile, pos), extraCost = nodes.getTileWalkCost(creature, tile), newf = n->f + cost + extraCost; //Check if the node is already in the closed/open list //If it exists and the nodes already on them has a lower cost (g) then we can ignore this neighbour node AStarNode* neighbourNode = nodes.getNodeInList(pos.x, pos.y); if(neighbourNode) { if(neighbourNode->f <= newf) //The node on the closed/open list is cheaper than this one continue; nodes.openNode(neighbourNode); } else if(!(neighbourNode = nodes.createOpenNode())) //Does not exist in the open/closed list, create a new node { if(found) //not quite what we want, but we found something break; //seems we ran out of nodes dirList.clear(); return false; } //This node is the best node so far with this state neighbourNode->x = pos.x; neighbourNode->y = pos.y; neighbourNode->parent = n; neighbourNode->f = newf; } } nodes.closeNode(n); } if(!found) return false; int32_t prevx = endPos.x, prevy = endPos.y, dx, dy; found = found->parent; while(found) { pos.x = found->x; pos.y = found->y; dx = pos.x - prevx; dy = pos.y - prevy; prevx = pos.x; prevy = pos.y; found = found->parent; if(dx == 1 && dy == 1) dirList.push_front(NORTHWEST); else if(dx == -1 && dy == 1) dirList.push_front(NORTHEAST); else if(dx == 1 && dy == -1) dirList.push_front(SOUTHWEST); else if(dx == -1 && dy == -1) dirList.push_front(SOUTHEAST); else if(dx == 1) dirList.push_front(WEST); else if(dx == -1) dirList.push_front(EAST); else if(dy == 1) dirList.push_front(NORTH); else if(dy == -1) dirList.push_front(SOUTH); } return true; } //*********** AStarNodes ************* AStarNodes::AStarNodes() { curNode = 0; openNodes.reset(); } AStarNode* AStarNodes::createOpenNode() { if(curNode >= MAX_NODES) return NULL; uint32_t retNode = curNode; curNode++; openNodes[retNode] = 1; return &nodes[retNode]; } AStarNode* AStarNodes::getBestNode() { if(!curNode) return NULL; int32_t bestNodeF = 100000; uint32_t bestNode = 0; bool found = false; for(uint32_t i = 0; i < curNode; i++) { if(nodes[i].f < bestNodeF && openNodes[i] == 1) { found = true; bestNodeF = nodes[i].f; bestNode = i; } } if(found) return &nodes[bestNode]; return NULL; } void AStarNodes::closeNode(AStarNode* node) { uint32_t pos = GET_NODE_INDEX(node); if(pos < MAX_NODES) { openNodes[pos] = 0; return; } assert(pos >= MAX_NODES); std::cout << "AStarNodes. trying to close node out of range" << std::endl; return; } void AStarNodes::openNode(AStarNode* node) { uint32_t pos = GET_NODE_INDEX(node); if(pos < MAX_NODES) { openNodes[pos] = 1; return; } assert(pos >= MAX_NODES); std::cout << "AStarNodes. trying to open node out of range" << std::endl; return; } uint32_t AStarNodes::countClosedNodes() { uint32_t counter = 0; for(uint32_t i = 0; i < curNode; i++) { if(!openNodes[i]) counter++; } return counter; } uint32_t AStarNodes::countOpenNodes() { uint32_t counter = 0; for(uint32_t i = 0; i < curNode; i++) { if(openNodes[i] == 1) counter++; } return counter; } bool AStarNodes::isInList(uint16_t x, uint16_t y) { for(uint32_t i = 0; i < curNode; i++) { if(nodes[i].x == x && nodes[i].y == y) return true; } return false; } AStarNode* AStarNodes::getNodeInList(uint16_t x, uint16_t y) { for(uint32_t i = 0; i < curNode; i++) { if(nodes[i].x == x && nodes[i].y == y) return &nodes[i]; } return NULL; } int32_t AStarNodes::getMapWalkCost(const Creature* creature, AStarNode* node, const Tile* neighbourTile, const Position& neighbourPos) { if(std::abs(node->x - neighbourPos.x) == std::abs(node->y - neighbourPos.y)) //diagonal movement extra cost return MAP_DIAGONALWALKCOST; return MAP_NORMALWALKCOST; } int32_t AStarNodes::getTileWalkCost(const Creature* creature, const Tile* tile) { int32_t cost = 0; if(tile->getTopVisibleCreature(creature)) //destroy creature cost cost += MAP_NORMALWALKCOST * 3; if(const MagicField* field = tile->getFieldItem()) { if(!creature->isImmune(field->getCombatType())) cost += MAP_NORMALWALKCOST * 3; } return cost; } int32_t AStarNodes::getEstimatedDistance(uint16_t x, uint16_t y, uint16_t xGoal, uint16_t yGoal) { int32_t diagonal = std::min(std::abs(x - xGoal), std::abs(y - yGoal)); return (MAP_DIAGONALWALKCOST * diagonal) + (MAP_NORMALWALKCOST * ((std::abs( x - xGoal) + std::abs(y - yGoal)) - (2 * diagonal))); } //*********** Floor constructor ************** Floor::Floor() { for(int32_t i = 0; i < FLOOR_SIZE; ++i) { for(int32_t j = 0; j < FLOOR_SIZE; ++j) tiles[i][j] = 0; } } //**************** QTreeNode ********************** QTreeNode::QTreeNode() { m_isLeaf = false; for(int32_t i = 0; i < 4; ++i) m_child[i] = NULL; } QTreeNode::~QTreeNode() { for(int32_t i = 0; i < 4; ++i) delete m_child[i]; } QTreeLeafNode* QTreeNode::getLeaf(uint16_t x, uint16_t y) { if(isLeaf()) return static_cast<QTreeLeafNode*>(this); uint32_t index = ((x & 0x8000) >> 15) | ((y & 0x8000) >> 14); if(m_child[index]) return m_child[index]->getLeaf(x * 2, y * 2); return NULL; } QTreeLeafNode* QTreeNode::getLeafStatic(QTreeNode* root, uint16_t x, uint16_t y) { QTreeNode* currentNode = root; uint32_t currentX = x, currentY = y; while(currentNode) { if(currentNode->isLeaf()) return static_cast<QTreeLeafNode*>(currentNode); uint32_t index = ((currentX & 0x8000) >> 15) | ((currentY & 0x8000) >> 14); if(!currentNode->m_child[index]) return NULL; currentNode = currentNode->m_child[index]; currentX = currentX * 2; currentY = currentY * 2; } return NULL; } QTreeLeafNode* QTreeNode::createLeaf(uint16_t x, uint16_t y, uint16_t level) { if(!isLeaf()) { uint32_t index = ((x & 0x8000) >> 15) | ((y & 0x8000) >> 14); if(!m_child[index]) { if(level != FLOOR_BITS) m_child[index] = new QTreeNode(); else { m_child[index] = new QTreeLeafNode(); QTreeLeafNode::newLeaf = true; } } return m_child[index]->createLeaf(x * 2, y * 2, level - 1); } return static_cast<QTreeLeafNode*>(this); } //************ LeafNode ************************ bool QTreeLeafNode::newLeaf = false; QTreeLeafNode::QTreeLeafNode() { for(int32_t i = 0; i < MAP_MAX_LAYERS; ++i) m_array[i] = NULL; m_isLeaf = true; m_leafS = NULL; m_leafE = NULL; } QTreeLeafNode::~QTreeLeafNode() { for(int32_t i = 0; i < MAP_MAX_LAYERS; ++i) delete m_array[i]; } Floor* QTreeLeafNode::createFloor(uint16_t z) { if(!m_array[z]) m_array[z] = new Floor(); return m_array[z]; }
configmanager.h
//////////////////////////////////////////////////////////////////////// // 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 <http://www.gnu.org/licenses/>. //////////////////////////////////////////////////////////////////////// #ifndef __CONFIG_MANAGER__ #define __CONFIG_MANAGER__ #include "luascript.h" class ConfigManager { public: ConfigManager(); virtual ~ConfigManager() {} enum string_config_t { DUMMY_STR = 0, CONFIG_FILE, MAP_NAME, HOUSE_RENT_PERIOD, LOGIN_MSG, FIRST_MSG, SERVER_NAME, OWNER_NAME, OWNER_EMAIL, URL, LOCATION, IP, MOTD, WORLD_TYPE, SQL_HOST, SQL_USER, SQL_PASS, SQL_DB, DEFAULT_PRIORITY, #ifdef MULTI_SQL_DRIVERS SQL_TYPE, #endif SQL_FILE, ENCRYPTION_TYPE, MAP_AUTHOR, RUNFILE, OUT_LOG, ERROR_LOG, DATA_DIRECTORY, PREFIX_CHANNEL_LOGS, CORES_USED, MAILBOX_DISABLED_TOWNS, LAST_STRING_CONFIG /* this must be the last one */ }; enum number_config_t { LOGIN_TRIES = 0, RETRY_TIMEOUT, LOGIN_TIMEOUT, LOGIN_PORT, GAME_PORT, ADMIN_PORT, STATUS_PORT, SQL_PORT, SQL_KEEPALIVE, MAX_PLAYERS, PZ_LOCKED, HUNTING_DURATION, DEFAULT_DESPAWNRANGE, DEFAULT_DESPAWNRADIUS, RATE_SPAWN, SPAWNPOS_X, SPAWNPOS_Y, SPAWNPOS_Z, SPAWNTOWN_ID, ALLOW_CLONES, GLOBALSAVE_H, START_LEVEL, START_MAGICLEVEL, HOUSE_PRICE, HIGHSCORES_TOP, MAX_MESSAGEBUFFER, HIGHSCORES_UPDATETIME, ACTIONS_DELAY_INTERVAL, EX_ACTIONS_DELAY_INTERVAL, CRITICAL_HIT_CHANCE, PROTECTION_LEVEL, ENCRYPTION, STATUSQUERY_TIMEOUT, LEVEL_TO_FORM_GUILD, MIN_GUILDNAME, MAX_GUILDNAME, LEVEL_TO_BUY_HOUSE, HOUSES_PER_ACCOUNT, WHITE_SKULL_TIME, RED_SKULL_LENGTH, BLACK_SKULL_LENGTH, MAX_VIOLATIONCOMMENT_SIZE, NOTATIONS_TO_BAN, WARNINGS_TO_FINALBAN, WARNINGS_TO_DELETION, BAN_LENGTH, KILLS_BAN_LENGTH, FINALBAN_LENGTH, IPBANISHMENT_LENGTH, MAX_PLAYER_SUMMONS, FIELD_OWNERSHIP, WORLD_ID, EXTRA_PARTY_PERCENT, EXTRA_PARTY_LIMIT, MYSQL_READ_TIMEOUT, MYSQL_WRITE_TIMEOUT, PARTY_RADIUS_X, PARTY_RADIUS_Y, PARTY_RADIUS_Z, LOGIN_PROTECTION, PLAYER_DEEPNESS, STAIRHOP_DELAY, RATE_STAMINA_LOSS, STAMINA_LIMIT_TOP, STAMINA_LIMIT_BOTTOM, BLESS_REDUCTION_BASE, BLESS_REDUCTION_DECREAMENT, BLESS_REDUCTION, NICE_LEVEL, EXPERIENCE_COLOR, GUILD_PREMIUM_DAYS, PUSH_CREATURE_DELAY, DEATH_CONTAINER, MAXIMUM_DOOR_LEVEL, DEATH_ASSISTS, RED_DAILY_LIMIT, RED_WEEKLY_LIMIT, RED_MONTHLY_LIMIT, BLACK_DAILY_LIMIT, BLACK_WEEKLY_LIMIT, BLACK_MONTHLY_LIMIT, BAN_DAILY_LIMIT, BAN_WEEKLY_LIMIT, BAN_MONTHLY_LIMIT, BLACK_SKULL_DEATH_HEALTH, BLACK_SKULL_DEATH_MANA, DEATHLIST_REQUIRED_TIME, EXPERIENCE_SHARE_ACTIVITY, ITEMLIMIT_PROTECTIONZONE, ITEMLIMIT_HOUSETILE, SQUARE_COLOR, LOOT_MESSAGE, LOOT_MESSAGE_TYPE, NAME_REPORT_TYPE, HOUSE_CLEAN_OLD, LAST_NUMBER_CONFIG /* this must be the last one */ }; enum double_config_t { RATE_EXPERIENCE, RATE_SKILL, RATE_MAGIC, RATE_LOOT, PARTY_DIFFERENCE, CRITICAL_HIT_MUL, RATE_STAMINA_GAIN, RATE_STAMINA_THRESHOLD, RATE_STAMINA_ABOVE, RATE_STAMINA_UNDER, EFP_MIN_THRESHOLD, EFP_MAX_THRESHOLD, RATE_PVP_EXPERIENCE, RATE_MONSTER_HEALTH, RATE_MONSTER_MANA, RATE_MONSTER_ATTACK, RATE_MONSTER_DEFENSE, FORMULA_LEVEL, FORMULA_MAGIC, LAST_DOUBLE_CONFIG /* this must be the last one */ }; enum bool_config_t { GLOBALSAVE_ENABLED = 0, START_CHOOSEVOC, ON_OR_OFF_CHARLIST, ONE_PLAYER_ON_ACCOUNT, REMOVE_WEAPON_AMMO, REMOVE_WEAPON_CHARGES, REMOVE_RUNE_CHARGES, RANDOMIZE_TILES, SHUTDOWN_AT_GLOBALSAVE, CLEAN_MAP_AT_GLOBALSAVE, FREE_PREMIUM, ADMIN_LOGS_ENABLED, GENERATE_ACCOUNT_NUMBER, BANK_SYSTEM, REMOVE_PREMIUM_ON_INIT, TELEPORT_SUMMONS, TELEPORT_PLAYER_SUMMONS, PVP_TILE_IGNORE_PROTECTION, DISPLAY_CRITICAL_HIT, ADVANCING_SKILL_LEVEL, CLEAN_PROTECTED_ZONES, SPELL_NAME_INSTEAD_WORDS, EMOTE_SPELLS, REPLACE_KICK_ON_LOGIN, PREMIUM_FOR_PROMOTION, SHOW_HEALING_DAMAGE, BROADCAST_BANISHMENTS, SAVE_GLOBAL_STORAGE, INGAME_GUILD_MANAGEMENT, HOUSE_BUY_AND_SELL, HOUSE_NEED_PREMIUM, HOUSE_RENTASPRICE, HOUSE_PRICEASRENT, ACCOUNT_MANAGER, NAMELOCK_MANAGER, ALLOW_CHANGEOUTFIT, CANNOT_ATTACK_SAME_LOOKFEET, AIMBOT_HOTKEY_ENABLED, FORCE_CLOSE_SLOW_CONNECTION, EXPERIENCE_STAGES, BLESSING_ONLY_PREMIUM, BED_REQUIRE_PREMIUM, ALLOW_CHANGECOLORS, LOGIN_ONLY_LOGINSERVER, STOP_ATTACK_AT_EXIT, DISABLE_OUTFITS_PRIVILEGED, OPTIMIZE_DB_AT_STARTUP, OLD_CONDITION_ACCURACY, STORE_TRASH, HOUSE_STORAGE, TRUNCATE_LOGS, TRACER_BOX, STORE_DIRECTION, DISPLAY_LOGGING, STAMINA_BONUS_PREMIUM, BAN_UNKNOWN_BYTES, ALLOW_CHANGEADDONS, GHOST_INVISIBLE_EFFECT, SHOW_HEALING_DAMAGE_MONSTER, CHECK_CORPSE_OWNER, BUFFER_SPELL_FAILURE, CONFIM_OUTDATED_VERSION, PREMIUM_SKIP_WAIT, GUILD_HALLS, DEATH_LIST, BIND_IP_ONLY, GHOST_SPELL_EFFECTS, PVPZONE_ADDMANASPENT, USE_BLACK_SKULL, ALLOW_FIGHTBACK, VIPLIST_PER_PLAYER, USE_FRAG_HANDLER, ADDONS_PREMIUM, LAST_BOOL_CONFIG /* this must be the last one */ }; bool load(); bool reload(); void startup() {m_startup = false;} const std::string& getString(uint32_t _what) const; bool getBool(uint32_t _what) const; int32_t getNumber(uint32_t _what) const; double getDouble(uint32_t _what) const; bool setString(uint32_t _what, const std::string& _value); bool setNumber(uint32_t _what, int32_t _value); void getValue(const std::string& key, lua_State* _L) {LuaScriptInterface::getValue(key, L, _L);} private: std::string getGlobalString(const std::string& _identifier, const std::string& _default = "") { return LuaScriptInterface::getGlobalString(L, _identifier, _default); } bool getGlobalBool(const std::string& _identifier, bool _default = false) { return LuaScriptInterface::getGlobalBool(L, _identifier, _default); } int32_t getGlobalNumber(const std::string& _identifier, const int32_t _default = 0) { return LuaScriptInterface::getGlobalNumber(L, _identifier, _default); } double getGlobalDouble(const std::string& _identifier, const double _default = 0) { return LuaScriptInterface::getGlobalDouble(L, _identifier, _default); } bool m_loaded, m_startup; lua_State* L; static void moveValue(lua_State* fromL, lua_State* toL); std::string m_confString[LAST_STRING_CONFIG]; bool m_confBool[LAST_BOOL_CONFIG]; int32_t m_confNumber[LAST_NUMBER_CONFIG]; double m_confDouble[LAST_DOUBLE_CONFIG]; }; #endif
configmanager.cpp
//////////////////////////////////////////////////////////////////////// // 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 <http://www.gnu.org/licenses/>. //////////////////////////////////////////////////////////////////////// #include "otpch.h" #include <iostream> #include "configmanager.h" #include "tools.h" ConfigManager::ConfigManager() { L = NULL; m_loaded = false; m_startup = true; m_confString[CONFIG_FILE] = getFilePath(FILE_TYPE_CONFIG, "config.lua"); m_confBool[LOGIN_ONLY_LOGINSERVER] = false; m_confNumber[LOGIN_PORT] = m_confNumber[GAME_PORT] = m_confNumber[ADMIN_PORT] = m_confNumber[STATUS_PORT] = 0; m_confString[DATA_DIRECTORY] = m_confString[IP] = m_confString[RUNFILE] = m_confString[ERROR_LOG] = m_confString[OUT_LOG] = ""; } bool ConfigManager::load() { if(L) lua_close(L); L = lua_open(); if(!L) return false; if(luaL_dofile(L, m_confString[CONFIG_FILE].c_str())) { lua_close(L); L = NULL; return false; } //parse config if(!m_loaded) //info that must be loaded one time (unless we reset the modules involved) { if(m_confString[DATA_DIRECTORY] == "") m_confString[DATA_DIRECTORY] = getGlobalString("dataDirectory", "data/"); if(m_confString[IP] == "") m_confString[IP] = getGlobalString("ip", "127.0.0.1"); if(m_confNumber[LOGIN_PORT] == 0) m_confNumber[LOGIN_PORT] = getGlobalNumber("loginPort", 7171); if(m_confNumber[GAME_PORT] == 0) m_confNumber[GAME_PORT] = getGlobalNumber("gamePort", 7172); if(m_confNumber[ADMIN_PORT] == 0) m_confNumber[ADMIN_PORT] = getGlobalNumber("adminPort", 7171); if(m_confNumber[STATUS_PORT] == 0) m_confNumber[STATUS_PORT] = getGlobalNumber("statusPort", 7171); if(m_confString[RUNFILE] == "") m_confString[RUNFILE] = getGlobalString("runFile", ""); if(m_confString[OUT_LOG] == "") m_confString[OUT_LOG] = getGlobalString("outLogName", ""); if(m_confString[ERROR_LOG] == "") m_confString[ERROR_LOG] = getGlobalString("errorLogName", ""); m_confBool[BIND_IP_ONLY] = getGlobalBool("bindOnlyConfiguredIpAddress", false); m_confBool[TRUNCATE_LOGS] = getGlobalBool("truncateLogsOnStartup", true); #ifdef MULTI_SQL_DRIVERS m_confString[SQL_TYPE] = getGlobalString("sqlType", "sqlite"); #endif m_confString[SQL_HOST] = getGlobalString("sqlHost", "localhost"); m_confNumber[SQL_PORT] = getGlobalNumber("sqlPort", 3306); m_confString[SQL_DB] = getGlobalString("sqlDatabase", "theforgottenserver"); m_confString[SQL_USER] = getGlobalString("sqlUser", "root"); m_confString[SQL_PASS] = getGlobalString("sqlPass", ""); m_confString[SQL_FILE] = getGlobalString("sqlFile", "forgottenserver.s3db"); m_confNumber[SQL_KEEPALIVE] = getGlobalNumber("sqlKeepAlive", 0); m_confNumber[MYSQL_READ_TIMEOUT] = getGlobalNumber("mysqlReadTimeout", 10); m_confNumber[MYSQL_WRITE_TIMEOUT] = getGlobalNumber("mysqlWriteTimeout", 10); m_confBool[OPTIMIZE_DB_AT_STARTUP] = getGlobalBool("optimizeDatabaseAtStartup", true); m_confString[MAP_NAME] = getGlobalString("mapName", "forgotten"); m_confBool[GLOBALSAVE_ENABLED] = getGlobalBool("globalSaveEnabled", true); m_confNumber[GLOBALSAVE_H] = getGlobalNumber("globalSaveHour", 8); m_confString[HOUSE_RENT_PERIOD] = getGlobalString("houseRentPeriod", "monthly"); m_confNumber[WORLD_ID] = getGlobalNumber("worldId", 0); m_confBool[RANDOMIZE_TILES] = getGlobalBool("randomizeTiles", true); m_confBool[STORE_TRASH] = getGlobalBool("storeTrash", true); m_confBool[EXPERIENCE_STAGES] = getGlobalBool("experienceStages", false); m_confString[DEFAULT_PRIORITY] = getGlobalString("defaultPriority", "high"); m_confBool[GUILD_HALLS] = getGlobalBool("guildHalls", false); #ifndef __LOGIN_SERVER__ m_confBool[LOGIN_ONLY_LOGINSERVER] = getGlobalBool("loginOnlyWithLoginServer", false); #endif m_confString[ENCRYPTION_TYPE] = getGlobalString("encryptionType", "plain"); m_confNumber[ENCRYPTION] = ENCRYPTION_PLAIN; } m_confString[MAP_AUTHOR] = getGlobalString("mapAuthor", "Unknown"); m_confNumber[LOGIN_TRIES] = getGlobalNumber("loginTries", 3); m_confNumber[RETRY_TIMEOUT] = getGlobalNumber("retryTimeout", 30 * 1000); m_confNumber[LOGIN_TIMEOUT] = getGlobalNumber("loginTimeout", 5 * 1000); m_confNumber[MAX_MESSAGEBUFFER] = getGlobalNumber("maxMessageBuffer", 4); m_confNumber[MAX_PLAYERS] = getGlobalNumber("maxPlayers"); m_confNumber[DEFAULT_DESPAWNRANGE] = getGlobalNumber("deSpawnRange", 2); m_confNumber[DEFAULT_DESPAWNRADIUS] = getGlobalNumber("deSpawnRadius", 50); m_confNumber[PZ_LOCKED] = getGlobalNumber("pzLocked", 60 * 1000); m_confNumber[HUNTING_DURATION] = getGlobalNumber("huntingDuration", 60 * 1000); m_confString[SERVER_NAME] = getGlobalString("serverName"); m_confString[OWNER_NAME] = getGlobalString("ownerName"); m_confString[OWNER_EMAIL] = getGlobalString("ownerEmail"); m_confString[URL] = getGlobalString("url"); m_confString[LOCATION] = getGlobalString("location"); m_confString[MOTD] = getGlobalString("motd"); m_confNumber[ALLOW_CLONES] = getGlobalNumber("allowClones", 0); m_confDouble[RATE_EXPERIENCE] = getGlobalDouble("rateExperience", 1); m_confDouble[RATE_SKILL] = getGlobalDouble("rateSkill", 1); m_confDouble[RATE_MAGIC] = getGlobalDouble("rateMagic", 1); m_confDouble[RATE_LOOT] = getGlobalDouble("rateLoot", 1); m_confNumber[RATE_SPAWN] = getGlobalNumber("rateSpawn", 1); m_confNumber[PARTY_RADIUS_X] = getGlobalNumber("experienceShareRadiusX", 30); m_confNumber[PARTY_RADIUS_Y] = getGlobalNumber("experienceShareRadiusY", 30); m_confNumber[PARTY_RADIUS_Z] = getGlobalNumber("experienceShareRadiusZ", 1); m_confDouble[PARTY_DIFFERENCE] = getGlobalDouble("experienceShareLevelDifference", (double)2 / 3); m_confNumber[SPAWNPOS_X] = getGlobalNumber("newPlayerSpawnPosX", 100); m_confNumber[SPAWNPOS_Y] = getGlobalNumber("newPlayerSpawnPosY", 100); m_confNumber[SPAWNPOS_Z] = getGlobalNumber("newPlayerSpawnPosZ", 7); m_confNumber[SPAWNTOWN_ID] = getGlobalNumber("newPlayerTownId", 1); m_confString[WORLD_TYPE] = getGlobalString("worldType", "pvp"); m_confBool[ACCOUNT_MANAGER] = getGlobalBool("accountManager", true); m_confBool[NAMELOCK_MANAGER] = getGlobalBool("namelockManager", false); m_confNumber[START_LEVEL] = getGlobalNumber("newPlayerLevel", 1); m_confNumber[START_MAGICLEVEL] = getGlobalNumber("newPlayerMagicLevel", 0); m_confBool[START_CHOOSEVOC] = getGlobalBool("newPlayerChooseVoc", false); m_confNumber[HOUSE_PRICE] = getGlobalNumber("housePriceEachSquare", 1000); m_confNumber[WHITE_SKULL_TIME] = getGlobalNumber("whiteSkullTime", 15 * 60 * 1000); m_confNumber[HIGHSCORES_TOP] = getGlobalNumber("highscoreDisplayPlayers", 10); m_confNumber[HIGHSCORES_UPDATETIME] = getGlobalNumber("updateHighscoresAfterMinutes", 60); m_confBool[ON_OR_OFF_CHARLIST] = getGlobalBool("displayOnOrOffAtCharlist", false); m_confBool[ALLOW_CHANGEOUTFIT] = getGlobalBool("allowChangeOutfit", true); m_confBool[ONE_PLAYER_ON_ACCOUNT] = getGlobalBool("onePlayerOnlinePerAccount", true); m_confBool[CANNOT_ATTACK_SAME_LOOKFEET] = getGlobalBool("noDamageToSameLookfeet", false); m_confBool[AIMBOT_HOTKEY_ENABLED] = getGlobalBool("hotkeyAimbotEnabled", true); m_confNumber[ACTIONS_DELAY_INTERVAL] = getGlobalNumber("timeBetweenActions", 200); m_confNumber[EX_ACTIONS_DELAY_INTERVAL] = getGlobalNumber("timeBetweenExActions", 1000); m_confNumber[CRITICAL_HIT_CHANCE] = getGlobalNumber("criticalHitChance", 5); m_confBool[REMOVE_WEAPON_AMMO] = getGlobalBool("removeWeaponAmmunition", true); m_confBool[REMOVE_WEAPON_CHARGES] = getGlobalBool("removeWeaponCharges", true); m_confBool[REMOVE_RUNE_CHARGES] = getGlobalBool("removeRuneCharges", true); m_confDouble[RATE_PVP_EXPERIENCE] = getGlobalDouble("rateExperienceFromPlayers", 0); m_confDouble[EFP_MIN_THRESHOLD] = getGlobalDouble("minLevelThresholdForKilledPlayer", 0.9f); m_confDouble[EFP_MAX_THRESHOLD] = getGlobalDouble("maxLevelThresholdForKilledPlayer", 1.1f); m_confBool[SHUTDOWN_AT_GLOBALSAVE] = getGlobalBool("shutdownAtGlobalSave", false); m_confBool[CLEAN_MAP_AT_GLOBALSAVE] = getGlobalBool("cleanMapAtGlobalSave", true); m_confBool[FREE_PREMIUM] = getGlobalBool("freePremium", false); m_confNumber[PROTECTION_LEVEL] = getGlobalNumber("protectionLevel", 1); m_confBool[ADMIN_LOGS_ENABLED] = getGlobalBool("adminLogsEnabled", false); m_confNumber[STATUSQUERY_TIMEOUT] = getGlobalNumber("statusTimeout", 5 * 60 * 1000); m_confBool[BROADCAST_BANISHMENTS] = getGlobalBool("broadcastBanishments", true); m_confBool[GENERATE_ACCOUNT_NUMBER] = getGlobalBool("generateAccountNumber", true); m_confBool[INGAME_GUILD_MANAGEMENT] = getGlobalBool("ingameGuildManagement", true); m_confNumber[LEVEL_TO_FORM_GUILD] = getGlobalNumber("levelToFormGuild", 8); m_confNumber[MIN_GUILDNAME] = getGlobalNumber("guildNameMinLength", 4); m_confNumber[MAX_GUILDNAME] = getGlobalNumber("guildNameMaxLength", 20); m_confNumber[LEVEL_TO_BUY_HOUSE] = getGlobalNumber("levelToBuyHouse", 1); m_confNumber[HOUSES_PER_ACCOUNT] = getGlobalNumber("housesPerAccount", 0); m_confBool[HOUSE_BUY_AND_SELL] = getGlobalBool("buyableAndSellableHouses", true); m_confBool[REPLACE_KICK_ON_LOGIN] = getGlobalBool("replaceKickOnLogin", true); m_confBool[HOUSE_NEED_PREMIUM] = getGlobalBool("houseNeedPremium", true); m_confBool[HOUSE_RENTASPRICE] = getGlobalBool("houseRentAsPrice", false); m_confBool[HOUSE_PRICEASRENT] = getGlobalBool("housePriceAsRent", false); m_confNumber[RED_SKULL_LENGTH] = getGlobalNumber("redSkullLength", 30 * 24 * 60 * 60); m_confNumber[BLACK_SKULL_LENGTH] = getGlobalNumber("blackSkullLength", 45 * 24 * 60 * 60); m_confNumber[MAX_VIOLATIONCOMMENT_SIZE] = getGlobalNumber("maxViolationCommentSize", 60); m_confNumber[NOTATIONS_TO_BAN] = getGlobalNumber("notationsToBan", 3); m_confNumber[WARNINGS_TO_FINALBAN] = getGlobalNumber("warningsToFinalBan", 4); m_confNumber[WARNINGS_TO_DELETION] = getGlobalNumber("warningsToDeletion", 5); m_confNumber[BAN_LENGTH] = getGlobalNumber("banLength", 7 * 24 * 60 * 60); m_confNumber[KILLS_BAN_LENGTH] = getGlobalNumber("killsBanLength", 7 * 24 * 60 * 60); m_confNumber[FINALBAN_LENGTH] = getGlobalNumber("finalBanLength", 30 * 24 * 60 * 60); m_confNumber[IPBANISHMENT_LENGTH] = getGlobalNumber("ipBanishmentLength", 1 * 24 * 60 * 60); m_confBool[BANK_SYSTEM] = getGlobalBool("bankSystem", true); m_confBool[PREMIUM_FOR_PROMOTION] = getGlobalBool("premiumForPromotion", true); m_confBool[REMOVE_PREMIUM_ON_INIT] = getGlobalBool("removePremiumOnInit", true); m_confBool[SHOW_HEALING_DAMAGE] = getGlobalBool("showHealingDamage", false); m_confBool[TELEPORT_SUMMONS] = getGlobalBool("teleportAllSummons", false); m_confBool[TELEPORT_PLAYER_SUMMONS] = getGlobalBool("teleportPlayerSummons", false); m_confBool[PVP_TILE_IGNORE_PROTECTION] = getGlobalBool("pvpTileIgnoreLevelAndVocationProtection", true); m_confBool[DISPLAY_CRITICAL_HIT] = getGlobalBool("displayCriticalHitNotify", false); m_confBool[ADVANCING_SKILL_LEVEL] = getGlobalBool("displaySkillLevelOnAdvance", false); m_confBool[CLEAN_PROTECTED_ZONES] = getGlobalBool("cleanProtectedZones", true); m_confBool[SPELL_NAME_INSTEAD_WORDS] = getGlobalBool("spellNameInsteadOfWords", false); m_confBool[EMOTE_SPELLS] = getGlobalBool("emoteSpells", false); m_confNumber[MAX_PLAYER_SUMMONS] = getGlobalNumber("maxPlayerSummons", 2); m_confBool[SAVE_GLOBAL_STORAGE] = getGlobalBool("saveGlobalStorage", true); m_confBool[FORCE_CLOSE_SLOW_CONNECTION] = getGlobalBool("forceSlowConnectionsToDisconnect", false); m_confBool[BLESSING_ONLY_PREMIUM] = getGlobalBool("blessingOnlyPremium", true); m_confBool[BED_REQUIRE_PREMIUM] = getGlobalBool("bedsRequirePremium", true); m_confNumber[FIELD_OWNERSHIP] = getGlobalNumber("fieldOwnershipDuration", 5 * 1000); m_confBool[ALLOW_CHANGECOLORS] = getGlobalBool("allowChangeColors", true); m_confBool[STOP_ATTACK_AT_EXIT] = getGlobalBool("stopAttackingAtExit", false); m_confNumber[EXTRA_PARTY_PERCENT] = getGlobalNumber("extraPartyExperiencePercent", 5); m_confNumber[EXTRA_PARTY_LIMIT] = getGlobalNumber("extraPartyExperienceLimit", 20); m_confBool[DISABLE_OUTFITS_PRIVILEGED] = getGlobalBool("disableOutfitsForPrivilegedPlayers", false); m_confBool[OLD_CONDITION_ACCURACY] = getGlobalBool("oldConditionAccuracy", false); m_confBool[HOUSE_STORAGE] = getGlobalBool("useHouseDataStorage", false); m_confBool[TRACER_BOX] = getGlobalBool("promptExceptionTracerErrorBox", true); m_confNumber[LOGIN_PROTECTION] = getGlobalNumber("loginProtectionPeriod", 10 * 1000); m_confBool[STORE_DIRECTION] = getGlobalBool("storePlayerDirection", false); m_confNumber[PLAYER_DEEPNESS] = getGlobalNumber("playerQueryDeepness", 1); m_confDouble[CRITICAL_HIT_MUL] = getGlobalDouble("criticalHitMultiplier", 1); m_confNumber[STAIRHOP_DELAY] = getGlobalNumber("stairhopDelay", 2 * 1000); m_confNumber[RATE_STAMINA_LOSS] = getGlobalNumber("rateStaminaLoss", 1); m_confDouble[RATE_STAMINA_GAIN] = getGlobalDouble("rateStaminaGain", 3); m_confDouble[RATE_STAMINA_THRESHOLD] = getGlobalDouble("rateStaminaThresholdGain", 12); m_confDouble[RATE_STAMINA_ABOVE] = getGlobalDouble("rateStaminaAboveNormal", 1.5f); m_confDouble[RATE_STAMINA_UNDER] = getGlobalDouble("rateStaminaUnderNormal", 0.5f); m_confNumber[STAMINA_LIMIT_TOP] = getGlobalNumber("staminaRatingLimitTop", 41 * 60); m_confNumber[STAMINA_LIMIT_BOTTOM] = getGlobalNumber("staminaRatingLimitBottom", 14 * 60); m_confBool[DISPLAY_LOGGING] = getGlobalBool("displayPlayersLogging", true); m_confBool[STAMINA_BONUS_PREMIUM] = getGlobalBool("staminaThresholdOnlyPremium", true); m_confBool[BAN_UNKNOWN_BYTES] = getGlobalBool("autoBanishUnknownBytes", false); m_confNumber[BLESS_REDUCTION_BASE] = getGlobalNumber("blessingReductionBase", 30); m_confNumber[BLESS_REDUCTION_DECREAMENT] = getGlobalNumber("blessingReductionDecreament", 5); m_confBool[ALLOW_CHANGEADDONS] = getGlobalBool("allowChangeAddons", true); m_confNumber[BLESS_REDUCTION] = getGlobalNumber("eachBlessReduction", 8); m_confDouble[FORMULA_LEVEL] = getGlobalDouble("formulaLevel", 5.0); m_confDouble[FORMULA_MAGIC] = getGlobalDouble("formulaMagic", 1.0); m_confString[PREFIX_CHANNEL_LOGS] = getGlobalString("prefixChannelLogs", ""); m_confBool[GHOST_INVISIBLE_EFFECT] = getGlobalBool("ghostModeInvisibleEffect", false); m_confString[CORES_USED] = getGlobalString("coresUsed", "-1"); m_confNumber[NICE_LEVEL] = getGlobalNumber("niceLevel", 5); m_confNumber[EXPERIENCE_COLOR] = getGlobalNumber("gainExperienceColor", TEXTCOLOR_WHITE); m_confBool[SHOW_HEALING_DAMAGE_MONSTER] = getGlobalBool("showHealingDamageForMonsters", false); m_confBool[CHECK_CORPSE_OWNER] = getGlobalBool("checkCorpseOwner ", true); m_confBool[BUFFER_SPELL_FAILURE] = getGlobalBool("bufferMutedOnSpellFailure", false); m_confBool[CONFIM_OUTDATED_VERSION] = getGlobalBool("confirmOutdatedVersion", true); m_confNumber[GUILD_PREMIUM_DAYS] = getGlobalNumber("premiumDaysToFormGuild", 0); m_confNumber[PUSH_CREATURE_DELAY] = getGlobalNumber("pushCreatureDelay", 2 * 1000); m_confNumber[DEATH_CONTAINER] = getGlobalNumber("deathContainerId", 1987); m_confBool[PREMIUM_SKIP_WAIT] = getGlobalBool("premiumPlayerSkipWaitList", false); m_confNumber[MAXIMUM_DOOR_LEVEL] = getGlobalNumber("maximumDoorLevel", 500); m_confBool[DEATH_LIST] = getGlobalBool("deathListEnabled", true); m_confNumber[DEATH_ASSISTS] = getGlobalNumber("deathAssistCount", 1); m_confNumber[RED_DAILY_LIMIT] = getGlobalNumber("dailyFragsToRedSkull", 3); m_confNumber[RED_WEEKLY_LIMIT] = getGlobalNumber("weeklyFragsToRedSkull", 5); m_confNumber[RED_MONTHLY_LIMIT] = getGlobalNumber("monthlyFragsToRedSkull", 10); m_confNumber[BLACK_DAILY_LIMIT] = getGlobalNumber("dailyFragsToBlackSkull", m_confNumber[RED_DAILY_LIMIT]); m_confNumber[BLACK_WEEKLY_LIMIT] = getGlobalNumber("weeklyFragsToBlackSkull", m_confNumber[RED_WEEKLY_LIMIT]); m_confNumber[BLACK_MONTHLY_LIMIT] = getGlobalNumber("monthlyFragsToBlackSkull", m_confNumber[RED_MONTHLY_LIMIT]); m_confNumber[BAN_DAILY_LIMIT] = getGlobalNumber("dailyFragsToBanishment", m_confNumber[RED_DAILY_LIMIT]); m_confNumber[BAN_WEEKLY_LIMIT] = getGlobalNumber("weeklyFragsToBanishment", m_confNumber[RED_WEEKLY_LIMIT]); m_confNumber[BAN_MONTHLY_LIMIT] = getGlobalNumber("monthlyFragsToBanishment", m_confNumber[RED_MONTHLY_LIMIT]); m_confNumber[BLACK_SKULL_DEATH_HEALTH] = getGlobalNumber("blackSkulledDeathHealth", 40); m_confNumber[BLACK_SKULL_DEATH_MANA] = getGlobalNumber("blackSkulledDeathMana", 0); m_confNumber[DEATHLIST_REQUIRED_TIME] = getGlobalNumber("deathListRequiredTime", 1 * 60 * 1000); m_confNumber[EXPERIENCE_SHARE_ACTIVITY] = getGlobalNumber("experienceShareActivity", 2 * 60 * 1000); m_confBool[GHOST_SPELL_EFFECTS] = getGlobalBool("ghostModeSpellEffects", true); m_confBool[PVPZONE_ADDMANASPENT] = getGlobalBool("addManaSpentInPvPZone", true); m_confNumber[ITEMLIMIT_PROTECTIONZONE] = getGlobalNumber("maxItemsPerPZTile", 0); m_confNumber[ITEMLIMIT_HOUSETILE] = getGlobalNumber("maxItemsPerHouseTile", 0); m_confString[MAILBOX_DISABLED_TOWNS] = getGlobalString("mailboxDisabledTowns", "-1"); m_confNumber[SQUARE_COLOR] = getGlobalNumber("squareColor", 0); m_confBool[USE_BLACK_SKULL] = getGlobalBool("useBlackSkull", false); m_confBool[USE_FRAG_HANDLER] = getGlobalBool("useFragHandler", true); m_confNumber[LOOT_MESSAGE] = getGlobalNumber("monsterLootMessage", 3); m_confNumber[LOOT_MESSAGE_TYPE] = getGlobalNumber("monsterLootMessageType", 25); m_confNumber[NAME_REPORT_TYPE] = getGlobalNumber("violationNameReportActionType", 2); m_confBool[ALLOW_FIGHTBACK] = getGlobalBool("allowFightback", true); m_confNumber[HOUSE_CLEAN_OLD] = getGlobalNumber("houseCleanOld", 0); m_confBool[VIPLIST_PER_PLAYER] = getGlobalBool("separateViplistPerCharacter", false); m_confDouble[RATE_MONSTER_HEALTH] = getGlobalDouble("rateMonsterHealth", 1); m_confDouble[RATE_MONSTER_MANA] = getGlobalDouble("rateMonsterMana", 1); m_confDouble[RATE_MONSTER_ATTACK] = getGlobalDouble("rateMonsterAttack", 1); m_confDouble[RATE_MONSTER_DEFENSE] = getGlobalDouble("rateMonsterDefense", 1); m_confBool[ADDONS_PREMIUM] = getGlobalBool("addonsOnlyPremium", true); m_loaded = true; return true; } bool ConfigManager::reload() { if(!m_loaded) return false; return load(); } const std::string& ConfigManager::getString(uint32_t _what) const { if((m_loaded && _what < LAST_STRING_CONFIG) || _what <= CONFIG_FILE) return m_confString[_what]; if(!m_startup) std::cout << "[Warning - ConfigManager::getString] " << _what << std::endl; return m_confString[DUMMY_STR]; } bool ConfigManager::getBool(uint32_t _what) const { if(m_loaded && _what < LAST_BOOL_CONFIG) return m_confBool[_what]; if(!m_startup) std::cout << "[Warning - ConfigManager::getBool] " << _what << std::endl; return false; } int32_t ConfigManager::getNumber(uint32_t _what) const { if(m_loaded && _what < LAST_NUMBER_CONFIG) return m_confNumber[_what]; if(!m_startup) std::cout << "[Warning - ConfigManager::getNumber] " << _what << std::endl; return 0; } double ConfigManager::getDouble(uint32_t _what) const { if(m_loaded && _what < LAST_DOUBLE_CONFIG) return m_confDouble[_what]; if(!m_startup) std::cout << "[Warning - ConfigManager::getDouble] " << _what << std::endl; return 0; } bool ConfigManager::setString(uint32_t _what, const std::string& _value) { if(_what < LAST_STRING_CONFIG) { m_confString[_what] = _value; return true; } std::cout << "[Warning - ConfigManager::setString] " << _what << std::endl; return false; } bool ConfigManager::setNumber(uint32_t _what, int32_t _value) { if(_what < LAST_NUMBER_CONFIG) { m_confNumber[_what] = _value; return true; } std::cout << "[Warning - ConfigManager::setNumber] " << _what << std::endl; return false; }
Estava seguindo esse tutorial mais toda vez que Eu ia compiliar a source dava erro.