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