From 86cda7a6429391d32c9d9499dbd6a728329892ef Mon Sep 17 00:00:00 2001 From: codinablack Date: Tue, 13 Aug 2024 06:12:47 -0500 Subject: [PATCH] Augments (WIP) --- data/augments/druid.toml | 11 + data/augments/knight.toml | 21 ++ data/augments/paladin.toml | 11 + data/augments/sorceror.toml | 11 + data/scripts/#discord.lua | 19 -- premake5.lua | 13 +- src/augment.cpp | 37 +++ src/augment.h | 92 ++++++ src/augments.cpp | 348 ++++++++++++++++++++ src/augments.h | 48 +++ src/combat.cpp | 527 +++++++++++++++++++++++------- src/combat.h | 5 +- src/creature.cpp | 5 - src/creature.h | 4 + src/damagemodifier.cpp | 137 ++++++++ src/damagemodifier.h | 297 +++++++++++++++++ src/enums.h | 30 +- src/game.cpp | 42 ++- src/item.cpp | 325 ++++--------------- src/item.h | 43 +-- src/items.cpp | 421 ------------------------ src/items.h | 68 ---- src/luascript.cpp | 623 ++++++++++++++++++++++++++++++------ src/luascript.h | 39 ++- src/monster.h | 1 + src/otserv.cpp | 4 + src/player.cpp | 391 +++++++++++++++++++--- src/player.h | 29 ++ src/protocolgame.cpp | 23 -- src/spectators.h | 4 + vcpkg.json | 1 + 31 files changed, 2523 insertions(+), 1107 deletions(-) create mode 100644 data/augments/druid.toml create mode 100644 data/augments/knight.toml create mode 100644 data/augments/paladin.toml create mode 100644 data/augments/sorceror.toml create mode 100644 src/augment.cpp create mode 100644 src/augment.h create mode 100644 src/augments.cpp create mode 100644 src/augments.h create mode 100644 src/damagemodifier.cpp create mode 100644 src/damagemodifier.h diff --git a/data/augments/druid.toml b/data/augments/druid.toml new file mode 100644 index 00000000..8a2b60da --- /dev/null +++ b/data/augments/druid.toml @@ -0,0 +1,11 @@ +[DruidicBlessing] +name = "Druidic Blessing" +description = "a vital blessing from the Great Meridan to only those worthy of such favor" +modifiers = [ + # Manasteal with poison spells + { stance = "attack", type = "manasteal", value = 20, damageType = "earth", originType = "spell" }, + # Staminasteal + { stance = "attack", type = "staminasteal", value = 1, chance = 5, flatRate = true, damageType = "earth", originType = "condition" }, + # Soulsteal + { stance = "attack", type = "soulsteal", chance = 10, damageType = "ice", originType = "spell" }, +] \ No newline at end of file diff --git a/data/augments/knight.toml b/data/augments/knight.toml new file mode 100644 index 00000000..a34ae598 --- /dev/null +++ b/data/augments/knight.toml @@ -0,0 +1,21 @@ +[LancelotsLance] +name = "Lancelot's Lance" +description = "a powerful ability whose origins trace back to the White Knight Lancelot" +modifiers = [ + # Lifesteal + { stance = "attack", type = "lifesteal", value = 35, chance = 85, damageType = "physical", originType = "melee"}, + # Melee Crits + { stance = "attack", type = "critical", value = 35, chance = 98, damageType = "physical", originType = "melee", monster = "Rat"}, +] + +[GuardiansShield] +name = "Guardian's Shield" +description = "This enhancement comes from years of skilled craftsmanship, and just a touch of magic" +modifiers = [ + # Condition Resistance + { stance = "defense", type = "resist", value = 20, originType = "condition" }, + # Spell Deflection + { stance = "defense", type = "deflect", chance = 10, damageType = "all", originType = "spell" }, + # Physical Absorb + { stance = "defense", type = "absorb", value = 45, chance = 80, damageType = "physical"}, +] \ No newline at end of file diff --git a/data/augments/paladin.toml b/data/augments/paladin.toml new file mode 100644 index 00000000..2706783f --- /dev/null +++ b/data/augments/paladin.toml @@ -0,0 +1,11 @@ +[RangersResolve] +name = "Ranger's Resolve" +description = "An ancient shamanic blessing, endowing archers with the light needed to defeat the darkness" +modifiers = [ + # Piercing Shot + { stance = "attack", type = "piercing", value = 20, flatRate = true, originType = "ranged"}, + # Death Resistance + { stance = "defense", type = "resist", value = 30, damageType = "death"}, + # Soul Restoration + { stance = "attack", type = "soulsteal", value = 1, chance = 60, flatRate = true, originType = "ranged" }, +] \ No newline at end of file diff --git a/data/augments/sorceror.toml b/data/augments/sorceror.toml new file mode 100644 index 00000000..8b35e02a --- /dev/null +++ b/data/augments/sorceror.toml @@ -0,0 +1,11 @@ +[MerlinsRage] +name = "Merlins Rage" +description = "A portion of The Great Mage Merlin's power! It takes great power just to wield this power!" +modifiers = [ + # Piercing curse damage + { stance = "attack", type = "piercing", value = 20, damageType = "death", originType="condition"}, + # Spell Crits + { stance = "attack", type = "critical", value = 30, chance = 25, originType = "spell"}, + # Spell Death Conversion + { stance = "attack", type = "conversion", value = 10, toDamageType = "death", originType = "spell" }, +] \ No newline at end of file diff --git a/data/scripts/#discord.lua b/data/scripts/#discord.lua index 4f3faa18..8b137891 100644 --- a/data/scripts/#discord.lua +++ b/data/scripts/#discord.lua @@ -1,20 +1 @@ -local LoginEvent = CreatureEvent("DiscordHook") ---Discord webhook enums ---MESSAGE_NORMAL ---MESSAGE_ERROR; ---MESSAGE_LOG; ---MESSAGE_INFO; - -local webhookLink = "TOKEN HERE" - -function LoginEvent.onLogin(player) - Game.sendDiscordMessage(webhookLink, MESSAGE_INFO, "Player: " .. player:getName() .. " has logged in") - Game.sendDiscordMessage(webhookLink, MESSAGE_ERROR, "Player: " .. player:getName() .. " has logged in") - Game.sendDiscordMessage(webhookLink, MESSAGE_NORMAL, "Player: " .. player:getName() .. " has logged in") - Game.sendDiscordMessage(webhookLink, MESSAGE_LOG, "Player: " .. player:getName() .. " has logged in") - return true -end - -LoginEvent:type("login") -LoginEvent:register() diff --git a/premake5.lua b/premake5.lua index 896184b5..bae03748 100644 --- a/premake5.lua +++ b/premake5.lua @@ -12,27 +12,32 @@ workspace "Black-Tek-Server" objdir "build/%{cfg.buildcfg}/obj" location "" files { "src/**.cpp", "src/**.h" } - flags {"LinkTimeOptimization", "MultiProcessorCompile"} + flags {"MultiProcessorCompile", "LinkTimeOptimization", "NoIncrementalLink"} enableunitybuild "On" intrinsics "On" filter "configurations:Debug" defines { "DEBUG" } + runtime "Debug" symbols "On" optimize "Debug" filter {} filter "configurations:Release" defines { "NDEBUG" } - symbols "On" - optimize "Speed" + runtime "Release" + symbols "Off" + editandcontinue "Off" + optimize "Full" filter {} filter "platforms:64" architecture "x86_64" + filter {} filter "platforms:ARM64" architecture "ARM64" + filter {} filter "system:not windows" buildoptions { "-Wall", "-Wextra", "-pedantic", "-pipe", "-fvisibility=hidden", "-Wno-unused-local-typedefs" } @@ -42,9 +47,9 @@ workspace "Black-Tek-Server" filter "system:windows" openmp "On" characterset "MBCS" - debugformat "c7" linkoptions {"/IGNORE:4099"} vsprops { VcpkgEnableManifest = "true" } + symbolspath '$(OutDir)$(TargetName).pdb' filter {} filter "architecture:amd64" diff --git a/src/augment.cpp b/src/augment.cpp new file mode 100644 index 00000000..4c343c6d --- /dev/null +++ b/src/augment.cpp @@ -0,0 +1,37 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + + +#include "augment.h" + +Augment::Augment(std::string_view name, std::string_view description) : m_name(name), m_description(description), m_mod_list(std::make_shared()) { + m_mod_list->initializeSharedPointer(); +} + +Augment::Augment(std::shared_ptr& original) : m_name(original->m_name), m_mod_list(original->m_mod_list) { + m_mod_list->initializeSharedPointer(); +} + +std::vector>& Augment::getAttackModifiers() +{ + return m_mod_list->getAttackModifiers(); +} + +std::vector>& Augment::getDefenseModifiers() +{ + return m_mod_list->getDefenseModifiers(); +} + +std::vector>& Augment::getAttackModifiers(uint8_t modType) +{ + return m_mod_list->getAttackModifiers(modType); +} + +std::vector>& Augment::getDefenseModifiers(uint8_t modType) +{ + return m_mod_list->getDefenseModifiers(modType); +} diff --git a/src/augment.h b/src/augment.h new file mode 100644 index 00000000..3259a763 --- /dev/null +++ b/src/augment.h @@ -0,0 +1,92 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + + +#ifndef FS_AUGMENT_H +#define FS_AUGMENT_H + +#include "damagemodifier.h" + +class Augment : public std::enable_shared_from_this { + +public: + Augment() = default; + Augment(std::string_view name, std::string_view description = ""); + Augment(std::shared_ptr& original); + + ~Augment() = default; + + // allow copying + explicit Augment(const Augment&) = default; + Augment& operator=(const Augment&) = default; + + // comparison operator + std::strong_ordering operator<=>(const Augment& other) const = default; + + const std::string_view getName() const; + const std::string_view getDescription() const; + + void setName(std::string_view name); + void setDescription(std::string_view description); + + static std::shared_ptr MakeAugment(std::string_view augmentName, std::string_view description = ""); + static std::shared_ptr MakeAugment(std::shared_ptr& originalPointer); + + void addModifier(std::shared_ptr modifier); + void removeModifier(std::shared_ptr& modifier); + + std::vector>& getAttackModifiers(); + std::vector>& getDefenseModifiers(); + + std::vector>& getAttackModifiers(uint8_t modType); + std::vector>& getDefenseModifiers(uint8_t modType); + +private: + + std::shared_ptr m_mod_list; + std::string_view m_name; + std::string_view m_description; +}; + + +inline std::shared_ptr Augment::MakeAugment(std::string_view augmentName, std::string_view description) { + auto augment = std::make_shared(augmentName); + return augment; +} + +inline std::shared_ptr Augment::MakeAugment(std::shared_ptr& originalRef) +{ + auto augmentClone = std::make_shared(originalRef); + return augmentClone; +} + +inline const std::string_view Augment::getName() const { + return m_name; +} + +inline const std::string_view Augment::getDescription() const +{ + return m_description; +} + +inline void Augment::setName(std::string_view name) { + m_name = name; +} + +inline void Augment::setDescription(std::string_view description) { + m_description = description; +} + +inline void Augment::addModifier(std::shared_ptr modifier) { + m_mod_list->addModifier(modifier); +} + +inline void Augment::removeModifier(std::shared_ptr& modifier) { + m_mod_list->removeModifier(modifier); +} + +#endif \ No newline at end of file diff --git a/src/augments.cpp b/src/augments.cpp new file mode 100644 index 00000000..5d213cc8 --- /dev/null +++ b/src/augments.cpp @@ -0,0 +1,348 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + +#include +#include +#include +#include + +#include "augments.h" + +static std::unordered_map> global_augments {}; + +std::shared_ptr Augments::MakeAugment(std::string_view augmentName) +{ + auto it = global_augments.find(augmentName.data()); + + if (it != global_augments.end()) { + auto augmentClone = Augment::MakeAugment(it->second); + return augmentClone; + } + std::cout << "Failed to find augment named : " << augmentName; + return nullptr; +} + +void Augments::loadAll() { + for (const auto& entry : std::filesystem::directory_iterator(path)) { + if (entry.is_regular_file() && entry.path().extension() == ".toml") { + + try { + + auto file = toml::parse_file(entry.path().string()); + + for (const auto& [index, entry] : file) { + + toml::table augment_info = *entry.as_table(); + auto modifier_data = augment_info["modifiers"]; + const std::string& name = augment_info["name"].value_or("unknown"); + + if (name == "unkown") { + std::cout << "Error: All augments require a name \n"; + break; + } + std::shared_ptr augment = Augment::MakeAugment(name); + + if (auto mod_list = modifier_data.as_array()) { + mod_list->for_each([augment](auto&& prop) { + if (prop.is_table()) { + auto& table = *prop.as_table(); + std::string_view stance = table["stance"].value_or("none"); + std::string_view modType = table["type"].value_or("none"); + uint16_t amount = table["value"].value_or(0); + uint8_t factor = table["factor"].value_or(0); + uint8_t chance = table["chance"].value_or(100); + std::string_view damageType = table["damageType"].value_or("none"); + std::string_view originType = table["originType"].value_or("none"); + std::string_view creatureType = table["creatureType"].value_or("none"); + std::string_view race = table["race"].value_or("none"); + std::string_view creatureName = table["monster"].value_or("none"); + + // To-do: Change all static methods used below to accept const values and use const variables above. + // also change the 'Get' methods into 'parse' methods for clarity + if (ParseStance(stance) == ATTACK_MOD) { + std::shared_ptr damage_modifier = DamageModifier::makeModifier( + ParseStance(stance), + ParseAttackModifier(modType), + amount, + std::bit_cast(factor), + chance, + ParseDamage(damageType), + ParseOrigin(originType), + ParseCreatureType(creatureType), + ParseRaceType(race)); + + // To-do : create a new variable for storing monster names to not conflict with other aux variables + if (modType == "conversion") { + damage_modifier->setTransformDamageType((CombatType_t)table["toDamageType"].value_or(0)); + } + + if (creatureName != "none") { + std::cout << "Found valid creature name " << creatureName.data() << "\n"; + damage_modifier->setCreatureName(creatureName); + } + + augment->addModifier(damage_modifier); + + } else if (ParseStance(stance) == DEFENSE_MOD) { + std::shared_ptr damage_modifier = DamageModifier::makeModifier( + ParseStance(stance), + ParseDefenseModifier(modType), + amount, + std::bit_cast(factor), + chance, + ParseDamage(damageType), + ParseOrigin(originType), + ParseCreatureType(creatureType), + ParseRaceType(race)); + + if (modType == "reform") { + damage_modifier->setTransformDamageType((CombatType_t)table["toDamageType"].value_or(0)); + } + + if (creatureName != "none") { + damage_modifier->setCreatureName(creatureName); + } + + augment->addModifier(damage_modifier); + + } else { + + std::cout << "Modifier has unknown stance " << table["stance"] << "\n"; + } + } + }); + } + std::cout << "Loaded Augment named : " << augment->getName() << std::endl; + AddAugment(augment); + } + } catch (const toml::parse_error& err) { + std::cerr << "Error parsing file " << entry.path() << ": " << err << "\n"; + } + } + } +} + +void Augments::clearAll() +{ + global_augments.clear(); +} + +void Augments::reload() +{ + clearAll(); + loadAll(); + // if (config::deleteOldAugments) { CleanPlayerAugments(); } +} + +const ModifierStance Augments::ParseStance(std::string_view stanceName) noexcept +{ + if (stanceName == "attack") { + return ATTACK_MOD; + } else if (stanceName == "defense") { + return DEFENSE_MOD; + } // to-do : add warning here + return NO_MOD; +} + +const CombatType_t Augments::ParseDamage(std::string_view damageName) noexcept +{ // Note : If you add values to the list you must increase the size manually + // current size is : 12 + const std::array, 12> static_map{ { + {"none", COMBAT_NONE}, + {"physical", COMBAT_PHYSICALDAMAGE}, + {"energy", COMBAT_ENERGYDAMAGE}, + {"earth", COMBAT_EARTHDAMAGE}, + {"fire", COMBAT_FIREDAMAGE}, + {"lifedrain", COMBAT_LIFEDRAIN}, + {"manadrain", COMBAT_MANADRAIN}, + {"healing", COMBAT_HEALING}, + {"drown", COMBAT_DROWNDAMAGE}, + {"ice", COMBAT_ICEDAMAGE}, + {"holy", COMBAT_HOLYDAMAGE}, + {"death", COMBAT_DEATHDAMAGE}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == damageName) { + return value; + } + } + + return COMBAT_NONE; +} + +const CombatOrigin Augments::ParseOrigin(std::string_view originName) noexcept +{ // Note : If you add values to the list you must increase the size manually + // current size is : 11 + const std::array, 11> static_map{ { + {"none", ORIGIN_NONE}, + {"condition", ORIGIN_CONDITION}, + {"spell", ORIGIN_SPELL}, + {"melee", ORIGIN_MELEE}, + {"ranged", ORIGIN_RANGED}, + {"reflect", ORIGIN_REFLECT}, + {"deflect", ORIGIN_DEFLECT}, + {"ricochet", ORIGIN_RICOCHET}, + {"modifier", ORIGIN_DMGMOD}, + {"augment", ORIGIN_AUGMENT}, + {"imbuement", ORIGIN_IMBUEMENT}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == originName) { + return value; + } + } + + return ORIGIN_NONE; +} + +const ModifierAttackType Augments::ParseAttackModifier(std::string_view modName) noexcept { + // Note : If you add values to the list you must increase the size manually + // current size is : 8 + const std::array, 8> static_map{ { + {"none", ATTACK_MODIFIER_NONE}, + {"lifesteal", ATTACK_MODIFIER_LIFESTEAL}, + {"manasteal", ATTACK_MODIFIER_MANASTEAL}, + {"staminasteal", ATTACK_MODIFIER_STAMINASTEAL}, + {"soulsteal", ATTACK_MODIFIER_SOULSTEAL}, + {"critical", ATTACK_MODIFIER_CRITICAL}, + {"piercing", ATTACK_MODIFIER_PIERCING}, + {"conversion", ATTACK_MODIFIER_CONVERSION}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == modName) { + return value; + } + } + + return ATTACK_MODIFIER_NONE; +} + +const ModifierDefenseType Augments::ParseDefenseModifier(std::string_view modName) noexcept +{ // Note : If you add values to the list you must increase the size manually + // current size is : 10 + const std::array, 10> static_map{ { + {"none", DEFENSE_MODIFIER_NONE}, + {"absorb", DEFENSE_MODIFIER_ABSORB}, + {"restore", DEFENSE_MODIFIER_RESTORE}, + {"replenish", DEFENSE_MODIFIER_REPLENISH}, + {"revive", DEFENSE_MODIFIER_REVIVE}, + {"reflect", DEFENSE_MODIFIER_REFLECT}, + {"deflect", DEFENSE_MODIFIER_DEFLECT}, + {"ricochet", DEFENSE_MODIFIER_RICOCHET}, + {"resist", DEFENSE_MODIFIER_RESIST}, + {"reform", DEFENSE_MODIFIER_REFORM}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == modName) { + return value; + } + } + + return DEFENSE_MODIFIER_NONE; +} + + +const RaceType_t Augments::ParseRaceType(std::string_view raceType) noexcept { + // Note : If you add values to the list you must increase the size manually + // current size is : 6 + const std::array, 6> static_map{ { + {"none", RACE_NONE}, + {"venom", RACE_VENOM}, + {"blood", RACE_BLOOD}, + {"undead", RACE_UNDEAD}, + {"fire", RACE_FIRE}, + {"energy", RACE_ENERGY}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == raceType) { + return value; + } + } + + return RACE_NONE; +} + +const ModFactor Augments::ParseModFactor(std::string_view modFactor) noexcept { + if (modFactor.data() == "percent") { + return PERCENT_MODIFIER; + } else if (modFactor.data() == "flat") { + return FLAT_MODIFIER; + } else { + std::cout << "Unknown Mod Factor : " << modFactor << " only options are percent or flat \n"; + } + + return PERCENT_MODIFIER; +} + +const CreatureType_t Augments::ParseCreatureType(std::string_view creatureType) noexcept { + // Note : If you add values to the list you must increase the size manually + // current size is : 9 + const std::array, 9> static_map{ { + {"player", CREATURETYPE_PLAYER}, + {"monster", CREATURETYPE_MONSTER}, + {"npc", CREATURETYPE_NPC}, + {"ownedsummon", CREATURETYPE_SUMMON_OWN}, + {"unownedsummon", CREATURETYPE_SUMMON_OTHERS}, + {"guildsummon", CREATURETYPE_SUMMON_GUILD}, + {"partysummon", CREATURETYPE_SUMMON_PARTY}, + {"boss", CREATURETYPE_BOSS}, + {"none", CREATURETYPE_ATTACKABLE}, + } }; + + for (const auto& [key, value] : static_map) { + if (key == creatureType) { + return value; + } + } + + return CREATURETYPE_ATTACKABLE; +} + +void Augments::AddAugment(std::shared_ptr augment) { + auto [it, inserted] = global_augments.try_emplace(augment->getName().data(), augment); + if (!inserted) { + std::cout << "[Warning][Augments] " << augment->getName() << " already exists! \n"; + } +} + +void Augments::RemoveAugment(std::shared_ptr augment) { + auto it = global_augments.find(augment->getName().data()); + if (it != global_augments.end()) { + global_augments.erase(it); + } +} + +void Augments::RemoveAugment(std::string_view augName) { + auto it = global_augments.find(std::string(augName)); + if (it != global_augments.end()) { + global_augments.erase(it); + } +} + +void Augments::RemoveAugment(std::string augName) { + auto it = global_augments.find(augName); + if (it != global_augments.end()) { + global_augments.erase(it); + } +} + +std::shared_ptr Augments::GetAugment(std::string_view augName) +{ + std::cout << "Augment name passed : " << augName << "\n"; + auto it = global_augments.find(augName.data()); + if (it != global_augments.end()) { + std::cout << "Found the augment! \n"; + auto augment = Augment::MakeAugment(it->second); + return augment; + } + return nullptr; +} diff --git a/src/augments.h b/src/augments.h new file mode 100644 index 00000000..6699600c --- /dev/null +++ b/src/augments.h @@ -0,0 +1,48 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + + +#ifndef FS_AUGMENTS_H +#define FS_AUGMENTS_H + +#include "augment.h" + +class Augments { + +public: + // No Constructors! Purely static class. + Augments() = delete; + ~Augments() = delete; + Augments(const Augments&) = delete; + Augments& operator=(const Augments&) = delete; + Augments(Augments&&) = delete; + Augments& operator=(Augments&&) = delete; + + static constexpr auto path = "data/augments/"; + static const ModifierStance ParseStance(std::string_view stanceName) noexcept; + static const ModifierAttackType ParseAttackModifier(std::string_view modName) noexcept; + static const ModifierDefenseType ParseDefenseModifier(std::string_view modName) noexcept; + static const CombatType_t ParseDamage(std::string_view damageName) noexcept; + static const CombatOrigin ParseOrigin(std::string_view originName) noexcept; + static const RaceType_t ParseRaceType(std::string_view raceType) noexcept; + static const ModFactor ParseModFactor(std::string_view modFactor) noexcept; + static const CreatureType_t ParseCreatureType(std::string_view creatureType) noexcept; + + static std::shared_ptr MakeAugment(std::string_view augmentName); + + static void loadAll(); + static void clearAll(); + static void reload(); + static void AddAugment(std::shared_ptr augment); + static void RemoveAugment(std::shared_ptr augment); + static void RemoveAugment(std::string_view augName); + static void RemoveAugment(std::string augName); + static std::shared_ptr GetAugment(std::string_view augName); +}; + + +#endif \ No newline at end of file diff --git a/src/combat.cpp b/src/combat.cpp index e91d4595..45cbfaf6 100644 --- a/src/combat.cpp +++ b/src/combat.cpp @@ -9,6 +9,8 @@ #include "configmanager.h" #include "events.h" +#include + extern Game g_game; extern Weapons* g_weapons; extern ConfigManager g_config; @@ -59,6 +61,37 @@ std::vector getCombatArea(const Position& centerPos, const Position& targ return {tile}; } +static void processConversion(std::unordered_map conversionList, CombatDamage& damage, Creature& caster, Creature& target) { + for (const auto& [targetDamage, mod] : conversionList) { + if (damage.primary.value && mod.percentTotal || mod.flatTotal) { + CombatDamage converted; + converted.primary.type = indexToCombatType(targetDamage); + auto maximumDiff = damage.primary.value; + auto actualDiff = 0; + auto acceptedDamage = 0; + + if (mod.percentTotal) { + actualDiff += (damage.primary.value / 100) * mod.percentTotal; + } + + if (mod.flatTotal) { + actualDiff += mod.flatTotal; + } + + acceptedDamage = std::clamp(actualDiff, 0, maximumDiff); + converted.primary.value = acceptedDamage; + + if (g_game.combatChangeHealth(caster.getCreature(), target.getCreature(), converted)) { + damage.primary.value -= acceptedDamage; + } + + if (damage.primary.value <= 0) { + return; + } + } + } +} + CombatDamage Combat::getCombatDamage(Creature* creature, Creature* target) const { CombatDamage damage; @@ -788,91 +821,361 @@ void Combat::doCombat(Creature* caster, const Position& position) const } } -void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params) -{ - if (caster && target && params.distanceEffect != CONST_ANI_NONE) { + +void Combat::doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params, bool sendDistanceEffect) +{ + // To-do : I need to properly handle augment based damage which requires entire reworking of this method. + // The thing that needs to happen is for augment based damage should not interact again with other aumgent + // based damage. Instead of using origin for this, would possibly be better as fields on the combat or combat params. + + if (params.distanceEffect != CONST_ANI_NONE && sendDistanceEffect) { addDistanceEffect(caster, caster->getPosition(), target->getPosition(), params.distanceEffect); } + // To-do : Get rid of these optionals (unsure, but I think they are a leak) + // by adding isPlayer and isMonster member methods to all the Creature classes. + std::optional casterPlayer = caster && caster->getPlayer() ? std::optional(caster->getPlayer()) : std::nullopt; + std::optional targetPlayer = target && target->getPlayer() ? std::optional(target->getPlayer()) : std::nullopt; + std::optional casterMonster = caster && caster->getMonster() ? std::optional(caster->getMonster()) : std::nullopt; + std::optional targetMonster = target && target->getMonster() ? std::optional(caster->getMonster()) : std::nullopt; - Player* casterPlayer = caster ? caster->getPlayer() : nullptr; + std::unordered_map attackModData; bool success = false; - if (damage.primary.type != COMBAT_MANADRAIN) { + + if (casterPlayer && target) { + attackModData.reserve(ATTACK_MODIFIER_LAST); + attackModData = casterPlayer.value()->getAttackModifierTotals(damage.primary.type, damage.origin, target->getType(), target->getRace(), target->getName()); + /// we do conversion here incase someone wants to convert say healing to mana or mana to death. + + auto conversionTotals = casterPlayer.value()->getConvertedTotals(ATTACK_MODIFIER_CONVERSION, damage.primary.type, damage.origin, target->getType(), target->getRace(), target->getName()) ; + if (!conversionTotals.empty()) { + std::cout << "Conversion Modifier Activated : on " << damage.primary.value << " damage \n"; + // to-do : change processConversion's return type to pass back the damage + // we are probably breaking it without knowing yet by passing the damage and not returning it. + processConversion(conversionTotals, damage, *caster->getCreature(), *target->getCreature()); + } + + if (damage.primary.type != COMBAT_MANADRAIN && damage.primary.type != COMBAT_HEALING) { + if (!attackModData.empty()) { + auto& [piercingPercentTotal, piercingFlatTotal] = attackModData[ATTACK_MODIFIER_PIERCING]; + // we handle piercing ourselves so that we can exit this call stack early + // in the case that all the damage was converted to piercing + if (piercingPercentTotal || piercingFlatTotal) { + auto piercingDamage = 0; + if (piercingPercentTotal) { + if (piercingPercentTotal <= 100) { + const auto difference = damage.primary.value * (piercingPercentTotal / 100.0); + piercingDamage += difference; + damage.primary.value -= difference; + } else { + piercingDamage += damage.primary.value; + damage.primary.value = 0; + } + } + + if (piercingFlatTotal) { + if (piercingFlatTotal <= damage.primary.value) { + piercingDamage += piercingFlatTotal; + } else { + piercingDamage += damage.primary.value; + damage.primary.value = 0; + } + } + + if (piercingDamage) { + CombatDamage piercing; + piercing.origin = ORIGIN_AUGMENT; + piercing.primary.value = piercingDamage; + piercing.primary.type = COMBAT_UNDEFINEDDAMAGE; + std::cout << "Piercing Modifier Activated : on " << damage.primary.value << " damage \n"; + g_game.combatChangeHealth(caster, target, piercing); + } + // we return early incase all the damage is piercing now. + // please note, due to this nature of piercing damage, + // piercing only interacts with conversion + // and does not interact with any of the other modifiers. + // in future rewrites of healthchange, lets allow piercing + // damage to also interact with other modifiers. + if (damage.primary.value <= 0) { + return; + } + } + } + + // All damage modifiers besides Critical are applied before armor/defense calculations. + if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0, params.ignoreResistances)) { + // if the damage is blocked return early + return; + } + + if (!damage.critical && damage.origin != ORIGIN_CONDITION) { + auto percentTotal = 0; + auto flatTotal = 0; + if (!attackModData.empty()) { + percentTotal = attackModData[ATTACK_MODIFIER_CRITICAL].percentTotal; + flatTotal = attackModData[ATTACK_MODIFIER_CRITICAL].flatTotal; + } + + // normal crits are the old ones and are percent based + auto normalCritChance = casterPlayer.value()->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE); + auto normalCritDamage = casterPlayer.value()->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT); + + // note : the way this works, its own damage increase is independent allowing for more than 100 + // and also at the same time, its chance is independent, so it doesn't add to augmented crit's chance. + if (normalCritChance > 0 && normalCritDamage > 0 && normal_random(1, 100) <= normalCritChance) { + percentTotal += normalCritDamage; + } + + // we do percent based crits first, so that the flat damage doesn't add to the percent increase. + if (percentTotal) { + auto damageIncrease = std::round(damage.primary.value * (percentTotal / 100.0)); + damage.primary.value += damageIncrease; + damage.critical = true; + std::cout << "Critical Percent Modifier Activated : on " << damage.primary.value << " damage \n"; + } + + if (flatTotal) { + damage.primary.value += flatTotal; + damage.critical = true; + std::cout << "Critical Flat Modifier Activated : on " << damage.primary.value << " damage \n"; + } + } + + if (targetPlayer && casterPlayer != targetPlayer) { + + auto reformTotals = targetPlayer.value()->getConvertedTotals(DEFENSE_MODIFIER_REFORM, damage.primary.type, damage.origin, caster->getType(), caster->getRace(), caster->getName()); + if (!reformTotals.empty()) { + std::cout << "Reform Modifier Activated : on " << damage.primary.value << " damage \n"; + // to-do : change processConversion's return type to pass back the damage + // we are probably breaking it without knowing yet by passing the damage and not returning it. + processConversion(reformTotals, damage, *caster->getCreature(), *target->getCreature()); + } + + auto defenseModData = targetPlayer.value()->getDefenseModifierTotals(damage.primary.type, damage.origin, caster->getType(), caster->getRace(), caster->getName()); + if (!defenseModData.empty()) { + + // To-do : We are passing creatures and players around by pointers right now + // we need to convert to using references on both players and creatures. + // Note: this is a huge to-do, due to derived creature class references not being copyable. + for (const auto& [modkind, modTotals] : defenseModData) { + if (modTotals.percentTotal || modTotals.flatTotal) { + std::cout << "Damage Reduction Modifier Activated : on " << damage.primary.value << " damage \n"; + // To-do : change applyDamageReductionMOdifier to return the damage, again, we are probably silently breaking it. + applyDamageReductionModifier(modkind, damage, *targetPlayer.value()->getPlayer(), *caster->getCreature(), modTotals.percentTotal, modTotals.flatTotal); + } + } + } + } + + } + } else if (casterMonster) { if (g_game.combatBlockHit(damage, caster, target, params.blockedByShield, params.blockedByArmor, params.itemId != 0, params.ignoreResistances)) { return; } - if (casterPlayer) { - Player* targetPlayer = target ? target->getPlayer() : nullptr; - if (targetPlayer && casterPlayer != targetPlayer && targetPlayer->getSkull() != SKULL_BLACK && damage.primary.type != COMBAT_HEALING) { - damage.primary.value /= 2; - damage.secondary.value /= 2; + if (targetPlayer) { + // to-do change caster->getType() and other caster calls to casterMonster calls when std::optionals are changed out. + auto defenseModData = targetPlayer.value()->getDefenseModifierTotals(damage.primary.type, damage.origin, caster->getType(), caster->getRace(), caster->getName()); + auto reformTotals = targetPlayer.value()->getConvertedTotals(DEFENSE_MODIFIER_REFORM, damage.primary.type, damage.origin, caster->getType(), caster->getRace(), caster->getName()); + if (!reformTotals.empty()) { + std::cout << "Reform Modifier Activated : on " << damage.primary.value << " damage \n"; + // to-do : change processConversion's return type to pass back the damage + // we are probably breaking it without knowing yet by passing the damage and not returning it. + processConversion(reformTotals, damage, *caster->getCreature(), *target->getCreature()); } - if (!damage.critical && damage.primary.type != COMBAT_HEALING && damage.origin != ORIGIN_CONDITION) { - uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE); - uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT); - if (chance > 0 && skill > 0 && normal_random(1, 100) <= chance) { - damage.primary.value += std::round(damage.primary.value * (skill / 100.)); - damage.secondary.value += std::round(damage.secondary.value * (skill / 100.)); - damage.critical = true; + if (!defenseModData.empty()) { + // we use *targetPlayer->getPlayer() instead of *targetPlayer as to not use a dereferenced pointer later. + // not entirely sure the above statement is true. I think its false actually, because its the same pointer? + // if it so happens I am causing dereference, solution is to have the method return the player when its done. + for (const auto& [modkind, modTotals] : defenseModData) { + if (modTotals.percentTotal || modTotals.flatTotal) { + std::cout << "Damage Reduction Modifier Activated : on " << damage.primary.value << " damage \n"; + // To-do : change applyDamageReductionMOdifier to return the damage, again, we are probably silently breaking it. + applyDamageReductionModifier(modkind, damage, *targetPlayer.value()->getPlayer(), *caster->getCreature(), modTotals.percentTotal, modTotals.flatTotal); + } } } } + } - success = g_game.combatChangeHealth(caster, target, damage); - } else { + if (damage.primary.type == COMBAT_MANADRAIN) { success = g_game.combatChangeMana(caster, target, damage); + } else { + success = g_game.combatChangeHealth(caster, target, damage); } if (success) { - if (damage.blockType == BLOCK_NONE || damage.blockType == BLOCK_ARMOR) { + + if (target) { + if (damage.critical) { + g_game.addMagicEffect(target->getPosition(), CONST_ME_CRITICAL_DAMAGE); + } + for (const auto& condition : params.conditionList) { - if (caster == target || !target->isImmune(condition->getType())) { + if (!target->isImmune(condition->getType())) { Condition* conditionCopy = condition->clone(); if (caster) { conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); } - //TODO: infight condition until all aggressive conditions has ended target->addCombatCondition(conditionCopy); } } } - if (damage.critical) { - g_game.addMagicEffect(target->getPosition(), CONST_ME_CRITICAL_DAMAGE); - } - - if (!damage.leeched && damage.primary.type != COMBAT_HEALING && casterPlayer && damage.origin != ORIGIN_CONDITION) { + if (target && !damage.leeched && damage.primary.type != COMBAT_HEALING && casterPlayer && damage.origin != ORIGIN_CONDITION) { CombatDamage leechCombat; leechCombat.origin = ORIGIN_NONE; leechCombat.leeched = true; + // Separate attackModData into damage altering, and damage leech/steal methods. + auto totalDamage = std::abs(damage.primary.value + damage.secondary.value); + + if (casterPlayer.value()->getHealth() < casterPlayer.value()->getMaxHealth()) { + // To-do : combine this if check with the one above to avoid unnecessary heap allocations + + auto lifeStealPercentTotal = 0; + auto lifeStealFlatTotal = 0; + + if (!attackModData.empty()) { + // to-do : when removing tibia crit/leech, bring this check up 2 branches to encompass all "steal" calculations. + lifeStealPercentTotal = attackModData[ATTACK_MODIFIER_LIFESTEAL].percentTotal; + lifeStealFlatTotal = attackModData[ATTACK_MODIFIER_LIFESTEAL].flatTotal; + } + + auto lifeLeechChance = casterPlayer.value()->getSpecialSkill(SPECIALSKILL_LIFELEECHCHANCE); + auto lifeLeechAmount = casterPlayer.value()->getSpecialSkill(SPECIALSKILL_LIFELEECHAMOUNT); + auto totalGain = 0; + + if (lifeLeechChance > 0 && lifeLeechAmount > 0 && normal_random(1, 100) <= lifeLeechChance) { + if (lifeStealPercentTotal) { + std::cout << "Life Leech Percent Modifier Activated from First Conditional with augment life steal : on " << damage.primary.value << " damage \n"; + std::cout << "TotalDamage : " << totalDamage << " \n"; + std::cout << "LifeStealPercentTotal : " << lifeStealPercentTotal << " \n"; + std::cout << "TotalGain : " << totalGain << " \n"; + totalGain += std::round(totalDamage * ((lifeLeechAmount + lifeStealPercentTotal) / 100.0)); + std::cout << "TotalGain after : " << totalGain << " \n"; + } else { + std::cout << "Life Leech Percent Modifier Activated from First Conditional without augment life steal : on " << damage.primary.value << " damage \n"; + std::cout << "TotalDamage : " << totalDamage << " \n"; + std::cout << "LifeStealPercentTotal : " << lifeStealPercentTotal << " \n"; + std::cout << "TotalGain before : " << totalGain << " \n"; + totalGain += std::round(totalDamage * (lifeLeechAmount / 100.0)); + std::cout << "TotalGain after : " << totalGain << " \n"; + } + } else { + if (lifeStealPercentTotal) { + std::cout << "Life Leech Percent Modifier Activated from Second Conditional : on " << damage.primary.value << " damage \n"; + std::cout << "TotalDamage : " << totalDamage << " \n"; + std::cout << "LifeStealPercentTotal : " << lifeStealPercentTotal << " \n"; + std::cout << "TotalGain : " << totalGain << " \n"; + totalGain += std::round(totalDamage * ((lifeStealPercentTotal) / 100.0)); + std::cout << "TotalGain after : " << totalGain << " \n"; + } + } + + if (lifeStealFlatTotal) { + std::cout << "Life Leech Flat Modifier Activated : on " << damage.primary.value << " damage \n"; + totalGain += lifeStealFlatTotal; + } - int32_t totalDamage = std::abs(damage.primary.value + damage.secondary.value); + if (totalGain) { + leechCombat.primary.value = totalGain; + std::cout << "Life Steal should be happening! \n"; + g_game.combatChangeHealth(nullptr, casterPlayer.value(), leechCombat); + casterPlayer.value()->sendMagicEffect(casterPlayer.value()->getPosition(), CONST_ME_MAGIC_RED); + } + + } + + /// ATTACK_MODIFIER_MANASTEAL + if (casterPlayer.value()->getMana() < casterPlayer.value()->getMaxMana()) { + + auto manaStealPercentTotal = 0; + auto manaStealFlatTotal = 0; - if (casterPlayer->getHealth() < casterPlayer->getMaxHealth()) { - uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHCHANCE); - uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHAMOUNT); - if (chance > 0 && skill > 0 && normal_random(1, 100) <= chance) { - leechCombat.primary.value = std::round(totalDamage * (skill / 100.)); - g_game.combatChangeHealth(nullptr, casterPlayer, leechCombat); - casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_RED); + if (!attackModData.empty()) { + // to-do : when removing tibia crit/leech, bring this check up 2 branches to encompass all "steal" calculations. + manaStealPercentTotal = attackModData[ATTACK_MODIFIER_MANASTEAL].percentTotal; + manaStealFlatTotal = attackModData[ATTACK_MODIFIER_MANASTEAL].flatTotal; + } + + auto manaLeechChance = casterPlayer.value()->getSpecialSkill(SPECIALSKILL_MANALEECHCHANCE); + auto manaLeechAmount = casterPlayer.value()->getSpecialSkill(SPECIALSKILL_MANALEECHAMOUNT); + auto totalGain = 0; + + if (manaLeechChance > 0 && manaLeechAmount > 0 && normal_random(1, 100) <= manaLeechChance) { + if (manaStealPercentTotal) { + totalGain += std::round(totalDamage * ((manaLeechAmount + manaStealPercentTotal) / 100.0)); + } else { + totalGain += std::round(totalDamage * (manaLeechAmount / 100.0)); + } + std::cout << "Mana Leech Percent Modifier Activated : on " << damage.primary.value << " damage \n"; } + + if (manaStealFlatTotal) { + std::cout << "Mana Leech Flat Modifier Activated : on " << damage.primary.value << " damage \n"; + totalGain += manaStealFlatTotal; + } + + if (totalGain) { + leechCombat.primary.value = totalGain; + g_game.combatChangeMana(nullptr, casterPlayer.value(), leechCombat); + casterPlayer.value()->sendMagicEffect(casterPlayer.value()->getPosition(), CONST_ME_MAGIC_BLUE); + } + } - if (casterPlayer->getMana() < casterPlayer->getMaxMana()) { - uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHCHANCE); - uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHAMOUNT); - if (chance > 0 && skill > 0 && normal_random(1, 100) <= chance) { - leechCombat.primary.value = std::round(totalDamage * (skill / 100.)); - g_game.combatChangeMana(nullptr, casterPlayer, leechCombat); - casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_BLUE); + /// ATTACK_MODIFIER_STAMINASTEAL + // To-do: move 2520 into a global constexpr + // or even better, make it have a real max var. + if (!attackModData.empty()) { + if (casterPlayer.value()->getStaminaMinutes() < 2520) { + auto& [staminaStealPercentTotal, staminaStealFlatTotal] = attackModData[ATTACK_MODIFIER_STAMINASTEAL]; + + auto totalGain = 0; + + if (staminaStealPercentTotal) { + std::cout << "Stamina Steal Percent Modifier Activated : on " << damage.primary.value << " damage \n"; + totalGain += std::round(totalDamage * (staminaStealPercentTotal / 100.0)); + } + + if (staminaStealFlatTotal) { + std::cout << "Stamina Steal Modifier Activated : on " << damage.primary.value << " damage \n"; + totalGain += staminaStealFlatTotal; + } + + if (totalGain) { + casterPlayer.value()->changeStamina(totalGain); + } + + } + + /// ATTACK_MODIFIER_SOULSTEAL + if (casterPlayer.value()->getSoul() < casterPlayer.value()->getVocation()->getSoulMax()) { + auto& [soulStealPercentTotal, soulStealFlatTotal] = attackModData[ATTACK_MODIFIER_SOULSTEAL]; + + auto totalGain = 0; + + if (soulStealPercentTotal) { + std::cout << "Soul Steal Percent Modifier Activated : on " << damage.primary.value << " damage \n"; + totalGain += std::round(totalDamage * (soulStealPercentTotal / 100.0)); + } + + if (soulStealFlatTotal) { + std::cout << "Soul Steal Flat Modifier Activated : on " << damage.primary.value << " damage \n"; + totalGain += soulStealFlatTotal; + } + + if (totalGain) { + casterPlayer.value()->changeSoul(totalGain); + } + } } } - if (params.dispelType == CONDITION_PARALYZE) { + if (params.dispelType && CONDITION_PARALYZE) { target->removeCondition(CONDITION_PARALYZE); } else { target->removeCombatCondition(params.dispelType); @@ -889,17 +1192,6 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area auto tiles = caster ? getCombatArea(caster->getPosition(), position, area) : getCombatArea(position, position, area); Player* casterPlayer = caster ? caster->getPlayer() : nullptr; - int32_t criticalPrimary = 0; - int32_t criticalSecondary = 0; - if (!damage.critical && damage.primary.type != COMBAT_HEALING && casterPlayer && damage.origin != ORIGIN_CONDITION) { - uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITCHANCE); - uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_CRITICALHITAMOUNT); - if (chance > 0 && skill > 0 && uniform_random(1, 100) <= chance) { - criticalPrimary = std::round(damage.primary.value * (skill / 100.)); - criticalSecondary = std::round(damage.secondary.value * (skill / 100.)); - damage.critical = true; - } - } uint32_t maxX = 0; uint32_t maxY = 0; @@ -961,88 +1253,79 @@ void Combat::doAreaCombat(Creature* caster, const Position& position, const Area } } - CombatDamage leechCombat; - leechCombat.origin = ORIGIN_NONE; - leechCombat.leeched = true; - - for (Creature* creature : toDamageCreatures) { + for (Creature* target : toDamageCreatures) { CombatDamage damageCopy = damage; // we cannot avoid copying here, because we don't know if it's player combat or not, so we can't modify the initial damage. - bool playerCombatReduced = false; - if ((damageCopy.primary.value < 0 || damageCopy.secondary.value < 0) && caster) { - Player* targetPlayer = creature->getPlayer(); - if (casterPlayer && targetPlayer && casterPlayer != targetPlayer && targetPlayer->getSkull() != SKULL_BLACK) { - damageCopy.primary.value /= 2; - damageCopy.secondary.value /= 2; - playerCombatReduced = true; - } - } + Combat::doTargetCombat(caster, target, damageCopy, params, false); + } +} + + +void Combat::applyDamageIncreaseModifier(uint8_t modifierType, CombatDamage& damage, uint8_t percentValue, uint8_t flatValue) { - if (damageCopy.critical) { - damageCopy.primary.value += playerCombatReduced ? criticalPrimary / 2 : criticalPrimary; - damageCopy.secondary.value += playerCombatReduced ? criticalSecondary / 2 : criticalSecondary; - g_game.addMagicEffect(creature->getPosition(), CONST_ME_CRITICAL_DAMAGE); + if (percentValue) { + if (percentValue <= 100) { + damage.primary.value += damage.primary.value * (percentValue / 100.0); + } else { + damage.primary.value *= 2; } + } + if (percentValue) { + damage.primary.value += percentValue; + } - bool success = false; - if (damageCopy.primary.type != COMBAT_MANADRAIN) { - if (g_game.combatBlockHit(damageCopy, caster, creature, params.blockedByShield, params.blockedByArmor, params.itemId != 0, params.ignoreResistances)) { - continue; - } - success = g_game.combatChangeHealth(caster, creature, damageCopy); +} + +void Combat::applyDamageReductionModifier(uint8_t modifierType, CombatDamage& damage, Player& damageTarget, Creature& attacker, uint8_t percentValue, uint8_t flatValue) { + auto damageChange = 0; + if (percentValue) { + if (percentValue <= 100) { + damageChange += damage.primary.value * (percentValue / 100.0); } else { - success = g_game.combatChangeMana(caster, creature, damageCopy); + damageChange += damage.primary.value * 2; } + } + if (flatValue) { + damageChange += flatValue; + } + if (damageChange) { + damage.primary.value -= damageChange; - if (success) { - if (damage.blockType == BLOCK_NONE || damage.blockType == BLOCK_ARMOR) { - for (const auto& condition : params.conditionList) { - if (caster == creature || !creature->isImmune(condition->getType())) { - Condition* conditionCopy = condition->clone(); - if (caster) { - conditionCopy->setParam(CONDITION_PARAM_OWNER, caster->getID()); - } + switch (modifierType) { + case DEFENSE_MODIFIER_ABSORB: + // should we make the healer the attacker, the player wearing the items, or null? + damageTarget.gainHealth(nullptr, damageChange); + std::cout << "Absorb Activated! \n"; + return; - //TODO: infight condition until all aggressive conditions has ended - creature->addCombatCondition(conditionCopy); - } - } - } + case DEFENSE_MODIFIER_RESTORE: + damageTarget.changeMana(damageChange); + std::cout << "Restore Activated! \n"; + return; - int32_t totalDamage = std::abs(damageCopy.primary.value + damageCopy.secondary.value); + case DEFENSE_MODIFIER_REPLENISH: + damageTarget.changeStamina(damageChange); + std::cout << "Replenish Activated! \n"; + return; - if (casterPlayer && !damage.leeched && damage.primary.type != COMBAT_HEALING && damage.origin != ORIGIN_CONDITION) { - int32_t targetsCount = toDamageCreatures.size(); + case DEFENSE_MODIFIER_REVIVE: + damageTarget.changeSoul(damageChange); + std::cout << "Revive Activated! \n"; + return; - if (casterPlayer->getHealth() < casterPlayer->getMaxHealth()) { - uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHCHANCE); - uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_LIFELEECHAMOUNT); - if (chance > 0 && skill > 0 && normal_random(1, 100) <= chance) { - leechCombat.primary.value = std::ceil(totalDamage * ((skill / 100.) + ((targetsCount - 1) * ((skill / 100.) / 10.))) / targetsCount); - g_game.combatChangeHealth(nullptr, casterPlayer, leechCombat); - casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_RED); - } - } + case DEFENSE_MODIFIER_REFLECT: + damageTarget.reflectDamage(attacker, damage.primary.type, damageChange); + return; - if (casterPlayer->getMana() < casterPlayer->getMaxMana()) { - uint16_t chance = casterPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHCHANCE); - uint16_t skill = casterPlayer->getSpecialSkill(SPECIALSKILL_MANALEECHAMOUNT); - if (chance > 0 && skill > 0 && normal_random(1, 100) <= chance) { - leechCombat.primary.value = std::ceil(totalDamage * ((skill / 100.) + ((targetsCount - 1) * ((skill / 100.) / 10.))) / targetsCount); - g_game.combatChangeMana(nullptr, casterPlayer, leechCombat); - casterPlayer->sendMagicEffect(casterPlayer->getPosition(), CONST_ME_MAGIC_BLUE); - } - } - } + case DEFENSE_MODIFIER_DEFLECT: + damageTarget.deflectDamage(damage.primary.type, damageChange); + return; - if (params.dispelType == CONDITION_PARALYZE) { - creature->removeCondition(CONDITION_PARALYZE); - } else { - creature->removeCombatCondition(params.dispelType); - } - } + case DEFENSE_MODIFIER_RICOCHET: + damageTarget.ricochetDamage(damage.primary.type, damageChange); + return; - if (params.targetCallback) { - params.targetCallback->onTargetCombat(caster, creature); + default: + return; } } } diff --git a/src/combat.h b/src/combat.h index aab6b735..2fe5df1f 100644 --- a/src/combat.h +++ b/src/combat.h @@ -103,9 +103,12 @@ class Combat void doCombat(Creature* caster, Creature* target) const; void doCombat(Creature* caster, const Position& position) const; - static void doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params); + static void doTargetCombat(Creature* caster, Creature* target, CombatDamage& damage, const CombatParams& params, bool sendDistanceEffect = true); static void doAreaCombat(Creature* caster, const Position& position, const AreaCombat* area, CombatDamage& damage, const CombatParams& params); + static void applyDamageIncreaseModifier(uint8_t modifierType, CombatDamage& damage, uint8_t percentValue, uint8_t flatValue); + static void applyDamageReductionModifier(uint8_t modifierType, CombatDamage& damage, Player& damageTarget, Creature& attacker, uint8_t percentValue, uint8_t flatValue); + bool setCallback(CallBackParam_t key); CallBack* getCallback(CallBackParam_t key); diff --git a/src/creature.cpp b/src/creature.cpp index 5e672780..e6ee6d97 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -881,11 +881,6 @@ BlockType_t Creature::blockHit(Creature* attacker, CombatType_t combatType, int3 continue; } - const uint16_t boostPercent = item->getBoostPercent(combatType); - if (boostPercent != 0) { - damage += std::round(damage * (boostPercent / 100.)); - } - if (item->hasImbuements() && blockType == BLOCK_NONE) { for (auto imbuement : item->getImbuements()) { diff --git a/src/creature.h b/src/creature.h index 0d64385a..5a16e114 100644 --- a/src/creature.h +++ b/src/creature.h @@ -157,6 +157,10 @@ class Creature : virtual public Thing hiddenHealth = b; } + bool isBoss() const { + return false; + } + int32_t getThrowRange() const override final { return 1; } diff --git a/src/damagemodifier.cpp b/src/damagemodifier.cpp new file mode 100644 index 00000000..8f62dcbf --- /dev/null +++ b/src/damagemodifier.cpp @@ -0,0 +1,137 @@ +#include "damagemodifier.h" + +std::shared_ptr DamageModifier::makeModifier(uint8_t stance, uint8_t modType, uint16_t amount, ModFactor factor, uint8_t chance, CombatType_t combatType, CombatOrigin source, CreatureType_t creatureType, RaceType_t race, std::string_view creatureName) { + auto mod = std::make_shared(stance, modType, amount, factor, chance, combatType, source, creatureType, race, creatureName.data()); + mod->initializeSharedPointer(); + return mod; +} + +std::shared_ptr& DamageModifier::getModifier() { + return m_shared_this; +} + +const std::string DamageModifier::getMonsterName() const +{ // Dangerous. Please only use if confirmed it is a Monster or Boss Based Mod. + if (m_aux_attribute.has_value() && m_aux_attribute.type() == typeid(std::string)) { + return any_cast(m_aux_attribute); + } + return std::string("ERROR"); +} + +const CombatType_t DamageModifier::conversionType() const +{ + if ((isAttackStance() && m_mod_type == ATTACK_MODIFIER_CONVERSION) || (isDefenseStance() && m_mod_type == DEFENSE_MODIFIER_REFORM)) { + try { + auto value = std::any_cast(m_aux_attribute); + return value; + } // to-do : make use of 'e' exception data here + catch (const std::bad_any_cast& e) { + std::cout << "[Error] Damage Modifier has invalid auxillary value for Conversion Type. Must be uint8_t ranged numerical value! Returning 0\n"; + } + } + return COMBAT_NONE; +} + +// Note : Make sure to confirm the mod type is a conversion type +void DamageModifier::setTransformDamageType(CombatType_t damageType) { + m_aux_attribute = std::any_cast(damageType); +} + +void DamageModifier::increaseValue(uint16_t amount) { + if ((m_value + amount) <= std::numeric_limits::max()) { + m_value += amount; + } else { + m_value = std::numeric_limits::max(); + std::cout << "[WARNING] Amount exceded numeric limits for uint16_t. m_value set to limit." << "\n"; + } +} + +void DamageModifier::decreaseValue(uint16_t amount) { + if (m_value >= amount) { + m_value -= amount; + }else { + m_value = 0; + std::cout << "[WARNING] Amount is greater than m_value. m_value set to zero. " << "\n"; + } +} + +void DamageModifier::serialize(PropWriteStream& propWriteStream) const { + propWriteStream.write(m_mod_stance); + propWriteStream.write(m_mod_type); + propWriteStream.write(m_value); + propWriteStream.write(m_chance); + propWriteStream.write(m_damage_type); + propWriteStream.write(m_origin_type); + /// To-do : add missing member data +} + +bool DamageModifier::unserialize(PropStream& propReadStream) { + uint8_t stance, modType, chance, originType = 0; + bool flatRate, onAny, origin = false; + uint16_t damageType, value = 0; + + if (!propReadStream.read(stance) || + !propReadStream.read(modType) || + !propReadStream.read(value) || + !propReadStream.read(chance) || + !propReadStream.read(damageType) || + !propReadStream.read(originType) || + !propReadStream.read(flatRate) || + !propReadStream.read(onAny) || + !propReadStream.read(origin)) { + return false; + } + + m_mod_stance = stance; + m_mod_type = modType; + m_value = value; + m_chance = chance; + m_damage_type = static_cast(damageType); + m_origin_type = static_cast(originType); + + return true; +} + +//////// ModifierList Members \\\\\\\\\\ + +void ModifierList::addModifier(std::shared_ptr& mod) { + if (mod->getStance() == ATTACK_MOD) { + m_attack_modifiers.push_back(std::move(mod)); + } + else if (mod->getStance() == DEFENSE_MOD) { + m_defense_modifiers.push_back(std::move(mod)); + } +} + +void ModifierList::removeModifier(std::shared_ptr& mod) { + if (mod->getStance() == ATTACK_MOD) { + m_attack_modifiers.erase(std::remove(m_attack_modifiers.begin(), m_attack_modifiers.end(), mod), m_attack_modifiers.end()); + } + else if (mod->getStance() == DEFENSE_MOD) { + m_defense_modifiers.erase(std::remove(m_defense_modifiers.begin(), m_defense_modifiers.end(), mod), m_defense_modifiers.end()); + } +} + +std::vector>& ModifierList::getAttackModifiers(uint8_t modType) { + static std::vector> modifiers; + modifiers.clear(); + for (auto& mod : m_attack_modifiers) { + + if (mod->getType() == modType) { + modifiers.push_back(mod); + } + } + return modifiers; +} + +std::vector>& ModifierList::getDefenseModifiers(uint8_t modType) { + static std::vector> modifiers; + modifiers.clear(); + for (auto& mod : m_defense_modifiers) { + + if (mod->getType() == modType) { + modifiers.push_back(mod); + } + } + return modifiers; +} diff --git a/src/damagemodifier.h b/src/damagemodifier.h new file mode 100644 index 00000000..715a1ee2 --- /dev/null +++ b/src/damagemodifier.h @@ -0,0 +1,297 @@ +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. +// All code found in this file is licensed under MIT and can be found in the LICENSE file. + +#ifndef FS_DAMAGEMODIFIER_H +#define FS_DAMAGEMODIFIER_H + +#include "otpch.h" +#include "tools.h" +#include "const.h" +#include "fileloader.h" + +struct ModifierTotals { + ModifierTotals() = default; + ModifierTotals(uint8_t flat, uint8_t percent) : flatTotal(flat), percentTotal(percent) {} + uint8_t flatTotal = 0; + uint8_t percentTotal = 0; +}; + +enum ModifierAttackType : uint8_t { + ATTACK_MODIFIER_NONE, // default + ATTACK_MODIFIER_LIFESTEAL, // damage is converted to health + ATTACK_MODIFIER_MANASTEAL, // damage is converted to mana + ATTACK_MODIFIER_STAMINASTEAL, // damage is converted stamina + ATTACK_MODIFIER_SOULSTEAL, // damage is converted soul + ATTACK_MODIFIER_CRITICAL, // damage can critcally hit + ATTACK_MODIFIER_PIERCING, // damage ignores defenses + ATTACK_MODIFIER_CONVERSION, // damage is converted to different type + + ATTACK_MODIFIER_LAST +}; + +enum ModifierDefenseType : uint8_t { + DEFENSE_MODIFIER_NONE, // default + DEFENSE_MODIFIER_ABSORB, // damage is converted to health + DEFENSE_MODIFIER_RESTORE, // damage is converted to mana + DEFENSE_MODIFIER_REPLENISH, // damage is converted to stamina + DEFENSE_MODIFIER_REVIVE, // damage is converted to soul + DEFENSE_MODIFIER_REFLECT, // damage is reduced on defender and returns to attacker + DEFENSE_MODIFIER_DEFLECT, // damage is negated on defender but hits all nearby enemies + DEFENSE_MODIFIER_RICOCHET, // damage is negated on defender but hits one random enemy + DEFENSE_MODIFIER_RESIST, // damage reduction + DEFENSE_MODIFIER_REFORM, // convert damage to another type + + + DEFENSE_MODIFIER_LAST +}; + +enum ModFactor : uint8_t { + PERCENT_MODIFIER, + FLAT_MODIFIER +}; + +enum ModifierStance : uint8_t { + NO_MOD, + ATTACK_MOD, + DEFENSE_MOD +}; + +class DamageModifier : public std::enable_shared_from_this { + +public: + DamageModifier() = default; + ~DamageModifier() = default; + + // allow copying + DamageModifier(const DamageModifier&) = default; + DamageModifier& operator=(const DamageModifier&) = default; + auto operator<=>(const DamageModifier&) const = default; + + void initializeSharedPointer() { m_shared_this = shared_from_this(); } + + DamageModifier(uint8_t stance, uint8_t modType, uint16_t amount, ModFactor factorType, uint8_t chance, CombatType_t combatType = COMBAT_NONE , CombatOrigin source = ORIGIN_NONE, CreatureType_t creatureType = CREATURETYPE_ATTACKABLE, RaceType_t race = RACE_NONE, std::string creatureName = "none") : + m_mod_stance(stance), // attack / defense + m_mod_type(modType), // the enum specific type + m_value(amount), // value to modify; default = percent + m_factor(factorType), // flat or percent based? defaults to percent. + m_chance(chance), // chance; if chance is 0, chance is not used. + m_damage_type(combatType), // if none, defaults to all damage types + m_origin_type(source), // if none, is used on all origin types + m_creature_type(creatureType), // defaults to all creatures if not set + m_race_type(race), // if none, all races. + m_creature_name(creatureName) // if none, all creatures. + {} + + std::shared_ptr& getModifier(); + static std::shared_ptr makeModifier(uint8_t stance, uint8_t modType, uint16_t amount, ModFactor factorType, uint8_t chance, CombatType_t combatType = COMBAT_NONE, CombatOrigin source = ORIGIN_NONE, CreatureType_t creatureType = CREATURETYPE_ATTACKABLE, RaceType_t race = RACE_NONE, std::string_view creatureName = "none"); + + const uint8_t getStance() const; + const uint8_t getType() const; + const uint8_t getValue() const; + const uint16_t getChance() const; + + const CombatType_t getDamageType() const; + const CombatOrigin getOriginType() const; + + const bool isPercent() const; + const bool isFlatValue() const; + const bool appliesToDamage(const CombatType_t damageType) const; + const bool appliesToOrigin(const CombatOrigin origin) const; + const bool appliesToTarget(const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) const; + const bool isAttackStance() const; + const bool isDefenseStance() const; + const std::string getMonsterName() const; + const CombatType_t conversionType() const; + + void serialize(PropWriteStream& propWriteStream) const; + bool unserialize(PropStream& propReadStream); + + void setValue(uint16_t amount); + void setFactor(uint8_t factor); + void setCombatType(CombatType_t combatType); + void setOriginType(CombatOrigin origin); + void increaseValue(uint16_t amount); + void decreaseValue(uint16_t amount); + void setTransformDamageType(CombatType_t damageType); + void setCreatureName(std::string_view creatureName); + +private: + std::shared_ptr m_shared_this; + uint8_t m_mod_stance = 0; // 0 = none, 1 = attack, 2 = defense; + uint8_t m_mod_type = 0; + uint16_t m_value = 0; + uint8_t m_factor = 0; + uint8_t m_chance = 0; + CombatType_t m_damage_type = COMBAT_NONE; + CombatOrigin m_origin_type = ORIGIN_NONE; + CreatureType_t m_creature_type = CREATURETYPE_ATTACKABLE; + RaceType_t m_race_type = RACE_NONE; + std::string m_creature_name = "none"; + std::any m_aux_attribute; // can be used for determining a type of damage, when transforming from one type to another. +}; + +class ModifierList : public std::enable_shared_from_this { + +public: + + ModifierList() { } + auto operator<=>(const ModifierList&) const = default; + + + void initializeSharedPointer() { m_shared_this = shared_from_this(); } + + //void serialize(PropWriteStream& propWriteStream) const { + // if (!m_modifiers.empty()) { + // propWriteStream.write(m_modifiers.size()); + // for (const auto& modifier : m_modifiers) { + // modifier->serialize(propWriteStream); + // } + // } + //} + + //bool unserialize(PropStream& propReadStream) { + // uint16_t size = 0; + // if (!propReadStream.read(size)) { + // return false; + // } + + // m_modifiers.clear(); + // for (uint16_t i = 0; i < size; ++i) { + // auto modifier = std::make_shared(); + // if (!modifier->unserialize(propReadStream)) { + // return false; + // } + // m_modifiers.push_back(modifier); + // } + // return true; + //} + + void addModifier(std::shared_ptr& mod); + void removeModifier(std::shared_ptr& mod); + + std::vector>& getAttackModifiers(); + std::vector>& getDefenseModifiers(); + + std::vector>& getAttackModifiers(uint8_t modType); + std::vector>& getDefenseModifiers(uint8_t modType); + +private: + std::shared_ptr m_shared_this; + std::vector> m_attack_modifiers; + std::vector> m_defense_modifiers; +}; + + +/// Inline Methods' Definitions + +inline void DamageModifier::setValue(uint16_t amount) { + m_value = amount; +} + +inline void DamageModifier::setFactor(uint8_t factor) +{ + m_factor = std::bit_cast(factor); +} + +inline void DamageModifier::setCombatType(CombatType_t combatType) { + m_damage_type = combatType; +} + +inline void DamageModifier::setOriginType(CombatOrigin origin) { + m_origin_type = origin; +} + +inline void DamageModifier::setCreatureName(std::string_view creatureName) { + m_creature_name = creatureName.data(); +} + +inline const bool DamageModifier::isPercent() const { + return m_factor == PERCENT_MODIFIER; +} + +inline const bool DamageModifier::isFlatValue() const { + return m_factor == FLAT_MODIFIER; +} + +inline const bool DamageModifier::appliesToDamage(const CombatType_t damageType) const { + return m_damage_type == COMBAT_NONE || m_damage_type == damageType; +} + +inline const bool DamageModifier::appliesToOrigin(const CombatOrigin origin) const { + return m_origin_type == ORIGIN_NONE || m_origin_type == origin; +} + +inline const bool DamageModifier::appliesToTarget(const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) const { + bool matchesType = (m_creature_type == CREATURETYPE_ATTACKABLE || m_creature_type == creatureType); + bool isValidTarget = + (m_creature_type == CREATURETYPE_MONSTER || m_creature_type == CREATURETYPE_SUMMON_ALL && ( + creatureType == CREATURETYPE_MONSTER || + creatureType == CREATURETYPE_SUMMON_ALL || + creatureType == CREATURETYPE_SUMMON_OWN || + creatureType == CREATURETYPE_SUMMON_GUILD || + creatureType == CREATURETYPE_SUMMON_OTHERS || + creatureType == CREATURETYPE_SUMMON_PARTY + )); + bool matchesRace = (m_race_type == RACE_NONE || m_race_type == race); + + bool attackableTarget = false; + + if (isValidTarget && matchesType && matchesRace) { + + if (m_creature_name.empty() || m_creature_name == "none") { + attackableTarget = true; + }else { + attackableTarget = (m_creature_name == creatureName.data()); + } + } + return attackableTarget; +} + +inline const uint8_t DamageModifier::getStance() const { + return m_mod_stance; +} + +inline const uint8_t DamageModifier::getType() const { + return m_mod_type; +} + +inline const uint8_t DamageModifier::getValue() const { + return m_value; +} + +inline const uint16_t DamageModifier::getChance() const { + return m_chance; +} + +inline const CombatType_t DamageModifier::getDamageType() const { + return m_damage_type; +} + +inline const CombatOrigin DamageModifier::getOriginType() const { + return m_origin_type; +} + +inline const bool DamageModifier::isAttackStance() const +{ + return m_mod_stance == ATTACK_MOD; +} + +inline const bool DamageModifier::isDefenseStance() const +{ + return m_mod_stance == DEFENSE_MOD; +} + +/// ModifierList Inline Functions + +inline std::vector>& ModifierList::getAttackModifiers() { + return m_attack_modifiers; +} + +inline std::vector>& ModifierList::getDefenseModifiers() { + return m_defense_modifiers; +} +#endif \ No newline at end of file diff --git a/src/enums.h b/src/enums.h index e2b36bee..3daea241 100644 --- a/src/enums.h +++ b/src/enums.h @@ -1,5 +1,8 @@ -// Copyright 2024 Black Tek Server Authors. All rights reserved. -// Use of this source code is governed by the GPL-2.0 License that can be found in the LICENSE file. +// Credits: BlackTek Server Creator Codinablack@github.com. +// This project is based of otland's The Forgottenserver. +// Any and all code taken from otland's The Forgottenserver is licensed under GPL 2.0 +// Any code Authored by: Codinablack or BlackTek contributers, that is not already licensed, is hereby licesned MIT. +// The GPL 2.0 License that can be found in the LICENSE file. #ifndef FS_ENUMS_H #define FS_ENUMS_H @@ -122,6 +125,11 @@ enum CreatureType_t : uint8_t { CREATURETYPE_NPC = 2, CREATURETYPE_SUMMON_OWN = 3, CREATURETYPE_SUMMON_OTHERS = 4, + CREATURETYPE_SUMMON_GUILD = 5, + CREATURETYPE_SUMMON_PARTY = 6, + CREATURETYPE_BOSS = 7, + CREATURETYPE_SUMMON_ALL, + CREATURETYPE_ATTACKABLE = 8 }; enum OperatingSystem_t : uint8_t { @@ -580,6 +588,10 @@ enum CombatOrigin ORIGIN_MELEE, ORIGIN_RANGED, ORIGIN_REFLECT, + ORIGIN_DEFLECT, + ORIGIN_RICOCHET, + ORIGIN_DMGMOD, + ORIGIN_AUGMENT, ORIGIN_IMBUEMENT, }; @@ -609,18 +621,4 @@ enum MonstersEvent_t : uint8_t { MONSTERS_EVENT_SAY = 5, }; -struct Reflect { - Reflect() = default; - Reflect(uint16_t percent, uint16_t chance) : percent(percent), chance(chance) {}; - - Reflect& operator+=(const Reflect& other) { - percent += other.percent; - chance = static_cast(std::min(100, chance + other.chance)); - return *this; - } - - uint16_t percent = 0; - uint16_t chance = 0; -}; - #endif // FS_ENUMS_H_ diff --git a/src/game.cpp b/src/game.cpp index d3c1129e..535ad3d4 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -4063,12 +4063,17 @@ void Game::combatGetTypeInfo(CombatType_t combatType, Creature* target, TextColo bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage& damage) { + // Get the position of the target for later use in displaying messages and effects const Position& targetPos = target->getPosition(); + + // If the damage value is positive (indicating healing or positive health change) if (damage.primary.value > 0) { + // Early exit if the target is already dead if (target->getHealth() <= 0) { return false; } + // Determine if the attacker is a player, if not, set to nullptr Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); @@ -4076,22 +4081,31 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage attackerPlayer = nullptr; } + // Check if the target is a player Player* targetPlayer = target->getPlayer(); + + // If the attacker is a player with a black skull and the target is not marked with a skull, + // the attack should not proceed. if (attackerPlayer && targetPlayer && attackerPlayer->getSkull() == SKULL_BLACK && attackerPlayer->getSkullClient(targetPlayer) == SKULL_NONE) { return false; } + // Handle health change events if any exist if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); if (!events.empty()) { + // Execute each health change event for (CreatureEvent* creatureEvent : events) { creatureEvent->executeHealthChange(target, attacker, damage); } + // Reset the damage origin to prevent recursive calls from re-triggering the event damage.origin = ORIGIN_NONE; + // Recursively call the function to process the final health change return combatChangeHealth(attacker, target, damage); } } + // Calculate the actual health change and apply it to the target int32_t realHealthChange = target->getHealth(); target->gainHealth(attacker, damage.primary.value); realHealthChange = target->getHealth() - realHealthChange; @@ -4112,16 +4126,19 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // If health was gained and the target is not in ghost mode, send notifications to spectators if (realHealthChange > 0 && !target->isInGhostMode()) { auto damageString = fmt::format("{:d} hitpoint{:s}", realHealthChange, realHealthChange != 1 ? "s" : ""); std::string spectatorMessage; + // Prepare the message for spectators about the healing TextMessage message; message.position = targetPos; message.primary.value = realHealthChange; message.primary.color = TEXTCOLOR_PASTELRED; + // Get all spectators around the target SpectatorVec spectators; map.getSpectators(spectators, targetPos, false, true); for (Creature* spectator : spectators) { @@ -4158,6 +4175,9 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } } else { + // If the damage value is non-positive (indicating damage or negative health change) + + // Check if the target is attackable; if not, create a visual effect and return if (!target->isAttackable()) { if (!target->isInGhostMode()) { addMagicEffect(targetPos, CONST_ME_POFF); @@ -4165,6 +4185,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return true; } + // Similar logic for attacker and target player checks as before Player* attackerPlayer; if (attacker) { attackerPlayer = attacker->getPlayer(); @@ -4177,6 +4198,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return false; } + // Convert the damage values to positive for health reduction damage.primary.value = std::abs(damage.primary.value); damage.secondary.value = std::abs(damage.secondary.value); @@ -4185,10 +4207,13 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return true; } + // Prepare message for spectators about the damage TextMessage message; message.position = targetPos; SpectatorVec spectators; + + // Check if the target has a mana shield, which can absorb some or all of the damage if (targetPlayer && target->hasCondition(CONDITION_MANASHIELD) && damage.primary.type != COMBAT_UNDEFINEDDAMAGE) { int32_t manaDamage = std::min(targetPlayer->getMana(), healthChange); if (manaDamage != 0) { @@ -4206,6 +4231,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // Drain mana from the target and create a visual effect targetPlayer->drainMana(attacker, manaDamage); map.getSpectators(spectators, targetPos, true, true); addMagicEffect(spectators, targetPos, CONST_ME_LOSEENERGY); @@ -4215,6 +4241,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage message.primary.value = manaDamage; message.primary.color = TEXTCOLOR_BLUE; + // Notify spectators about the mana drain for (Creature* spectator : spectators) { assert(dynamic_cast(spectator) != nullptr); @@ -4253,6 +4280,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage spectatorPlayer->sendTextMessage(message); } + // Adjust the damage values after mana absorption damage.primary.value -= manaDamage; if (damage.primary.value < 0) { damage.secondary.value = std::max(0, damage.secondary.value + damage.primary.value); @@ -4261,11 +4289,13 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // Calculate the total damage remaining after any mana absorption int32_t realDamage = damage.primary.value + damage.secondary.value; if (realDamage == 0) { return true; } + // Handle health change events again if necessary if (damage.origin != ORIGIN_NONE) { const auto& events = target->getCreatureEvents(CREATURE_EVENT_HEALTHCHANGE); if (!events.empty()) { @@ -4277,6 +4307,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // Cap the primary damage at the target's current health int32_t targetHealth = target->getHealth(); if (damage.primary.value >= targetHealth) { damage.primary.value = targetHealth; @@ -4285,11 +4316,13 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage damage.secondary.value = std::min(damage.secondary.value, targetHealth - damage.primary.value); } + // Recalculate total damage after adjustments realDamage = damage.primary.value + damage.secondary.value; if (realDamage == 0) { return true; } + // Get spectators if not already done if (spectators.empty()) { map.getSpectators(spectators, targetPos, true, true); } @@ -4353,8 +4386,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage tmpPlayer->sendTextMessage(message); } } - - // rewardboss player attacking boss +// rewardboss player attacking boss if (target && target->getMonster() && target->getMonster()->isRewardBoss()) { uint32_t monsterId = target->getMonster()->getID(); @@ -4381,7 +4413,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage rewardBossTracking[monsterId].playerScoreTable[playerGuid].damageTaken += realDamage * g_config.getFloat(ConfigManager::REWARD_RATE_DAMAGE_TAKEN); } } - + // If the damage is enough to kill the target, execute death preparation events if (realDamage >= targetHealth) { for (CreatureEvent* creatureEvent : target->getCreatureEvents(CREATURE_EVENT_PREPAREDEATH)) { if (!creatureEvent->executeOnPrepareDeath(target, attacker)) { @@ -4390,6 +4422,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage } } + // Apply the damage to the target's health and update spectators target->drainHealth(attacker, realDamage); addCreatureHealth(spectators, target); } @@ -4397,6 +4430,7 @@ bool Game::combatChangeHealth(Creature* attacker, Creature* target, CombatDamage return true; } + bool Game::combatChangeMana(Creature* attacker, Creature* target, CombatDamage& damage) { Player* targetPlayer = target->getPlayer(); @@ -5955,4 +5989,4 @@ bool Game::reload(ReloadTypes_t reloadType) void Game::resetDamageTracking(uint32_t monsterId) { rewardBossTracking.erase(monsterId); -} \ No newline at end of file +} diff --git a/src/item.cpp b/src/item.cpp index 3df42cf8..0eb1abb1 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -31,9 +31,6 @@ void handleWeaponMeleeDescription(std::ostringstream& s, const ItemType& it, con void handleSkillsDescription(std::ostringstream& s, const ItemType& it, bool& begin); void handleStatsDescription(std::ostringstream& s, const ItemType& it, bool& begin); void handleStatsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin); -void handleAbsorbsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin); -void handleReflectPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin); -void handleAbsorbsFieldsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin); void handleAbilitiesDescription(std::ostringstream& s, const ItemType& it, bool& begin); void handleMiscDescription(std::ostringstream& s, const ItemType& it, bool& begin); @@ -672,44 +669,6 @@ Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) break; } - case ATTR_REFLECT: { - uint16_t size; - if (!propStream.read(size)) { - return ATTR_READ_ERROR; - } - - for (uint16_t i = 0; i < size; ++i) { - CombatType_t combatType; - Reflect reflect; - - if (!propStream.read(combatType) || !propStream.read(reflect.percent) || !propStream.read(reflect.chance)) { - return ATTR_READ_ERROR; - } - - getAttributes()->reflect[combatType] = reflect; - } - break; - } - - case ATTR_BOOST: { - uint16_t size; - if (!propStream.read(size)) { - return ATTR_READ_ERROR; - } - - for (uint16_t i = 0; i < size; ++i) { - CombatType_t combatType; - uint16_t percent; - - if (!propStream.read(combatType) || !propStream.read(percent)) { - return ATTR_READ_ERROR; - } - - getAttributes()->boostPercent[combatType] = percent; - } - break; - } - case ATTR_IMBUEMENTS: { uint16_t size; if (!propStream.read(size)) { @@ -985,31 +944,6 @@ void Item::serializeAttr(PropWriteStream& propWriteStream) const } } - if (attributes) { - const auto& reflects = attributes->reflect; - if (!reflects.empty()) { - propWriteStream.write(ATTR_REFLECT); - propWriteStream.write(reflects.size()); - - for (const auto& reflect : reflects) { - propWriteStream.write(reflect.first); - propWriteStream.write(reflect.second.percent); - propWriteStream.write(reflect.second.chance); - } - } - - const auto& boosts = attributes->boostPercent; - if (!boosts.empty()) { - propWriteStream.write(ATTR_BOOST); - propWriteStream.write(boosts.size()); - - for (const auto& boost : boosts) { - propWriteStream.write(boost.first); - propWriteStream.write(boost.second); - } - } - } - if (hasImbuements()) { propWriteStream.write(ATTR_IMBUEMENTS); propWriteStream.write(imbuements.size()); @@ -1466,43 +1400,10 @@ LightInfo Item::getLightInfo() const return {it.lightLevel, it.lightColor}; } -Reflect Item::getReflect(CombatType_t combatType, bool total /* = true */) const -{ - const ItemType& it = Item::items[id]; - - Reflect reflect; - if (attributes) { - reflect += attributes->getReflect(combatType); - } - - if (total && it.abilities) { - reflect += it.abilities->reflect[combatTypeToIndex(combatType)]; - } - - return reflect; -} - -uint16_t Item::getBoostPercent(CombatType_t combatType, bool total /* = true */) const -{ - const ItemType& it = Item::items[id]; - - uint16_t boostPercent = 0; - if (attributes) { - boostPercent += attributes->getBoostPercent(combatType); - } - - if (total && it.abilities) { - boostPercent += it.abilities->boostPercent[combatTypeToIndex(combatType)]; - } - - return boostPercent; -} - std::string ItemAttributes::emptyString; int64_t ItemAttributes::emptyInt; double ItemAttributes::emptyDouble; bool ItemAttributes::emptyBool; -Reflect ItemAttributes::emptyReflect; const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const { @@ -1623,18 +1524,6 @@ bool Item::hasMarketAttributes() const return true; } - // discard items with custom boost and reflect - for (uint16_t i = 0; i < COMBAT_COUNT; ++i) { - if (getBoostPercent(indexToCombatType(i), false) > 0) { - return false; - } - - Reflect tmpReflect = getReflect(indexToCombatType(i), false); - if (tmpReflect.chance != 0 || tmpReflect.percent != 0) { - return false; - } - } - // discard items with other modified attributes for (const auto& attr : attributes->getList()) { if (attr.type == ITEM_ATTRIBUTE_CHARGES) { @@ -1847,6 +1736,73 @@ std::vector>& Item::getImbuements() { return imbuements; } +const bool Item::addAugment(std::shared_ptr& augment) +{ + if (std::find(augments.begin(), augments.end(), augment) != augments.end()) { + return false; + } + + augments.push_back(augment); + return true; +} + +const bool Item::addAugment(std::string_view augmentName) +{ + if (auto augment = Augments::GetAugment(augmentName)) { + augments.emplace_back(augment); + return true; + } + return false; +} + +const bool Item::removeAugment(std::shared_ptr& augment) +{ + auto originalSize = augments.size(); + augments.erase(std::remove(augments.begin(), augments.end(), augment), augments.end()); + return augments.size() < originalSize; +} + +const bool Item::removeAugment(std::string_view name) +{ + augments.erase(std::remove_if(augments.begin(), augments.end(), + [&name](const std::shared_ptr& augment) { + return augment->getName() == name; + }), augments.end()); + return false; +} + +// To-do: Move to const inline next three methods at least. +bool Item::isAugmented() +{ + return augments.size() > 0; +} + +bool Item::hasAugment(std::string_view name) +{ + for (const auto& aug : augments) { + if (aug->getName() == name) { + return true; + } + } + return false; +} + +bool Item::hasAugment(const std::shared_ptr& augment) +{ + for (const auto& aug : augments) { + if (aug == augment) { + return true; + } + } + return false; +} + + +const std::vector>& Item::getAugments() +{ + return augments; +} + void Item::decayImbuements(bool infight) { for (auto& imbue : imbuements) { if (imbue->isEquipDecay()) { @@ -2056,102 +2012,6 @@ void handleStatsPercentDescription(std::ostringstream& s, const ItemType& it, bo } } -void handleAbsorbsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin) { - int16_t show = it.abilities->absorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (show == 0) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "absorb "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "absorb all " << std::showpos << show << std::noshowpos << '%'; - } -} - -void handleAbsorbsFieldsPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin) { - int16_t show = it.abilities->fieldAbsorbPercent[0]; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] != show) { - show = 0; - break; - } - } - } - - if (show == 0) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->fieldAbsorbPercent[i] == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "absorb "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "absorb all fields " << std::showpos << show << std::noshowpos << '%'; - } -} - void handleMiscDescription(std::ostringstream& s, const ItemType& it, bool& begin) { if (it.abilities->speed) { if (begin) { @@ -2165,60 +2025,9 @@ void handleMiscDescription(std::ostringstream& s, const ItemType& it, bool& begi } } -void handleReflectPercentDescription(std::ostringstream& s, const ItemType& it, bool& begin) { - int16_t show = it.abilities->reflect[0].percent; - if (show != 0) { - for (size_t i = 1; i < COMBAT_COUNT; ++i) { - if (it.abilities->reflect[i].percent != show) { - show = 0; - break; - } - } - } - - if (show == 0) { - bool tmp = true; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->reflect[i].percent == 0) { - continue; - } - - if (tmp) { - tmp = false; - - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "reflect "; - } else { - s << ", "; - } - - s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->reflect[i].percent << std::noshowpos << '%'; - } - } else { - if (begin) { - begin = false; - s << " ("; - } else { - s << ", "; - } - - s << "reflect all " << std::showpos << show << std::noshowpos << '%'; - } -} - void handleAbilitiesDescription(std::ostringstream& s, const ItemType& it, bool& begin) { handleSkillsDescription(s, it, begin); handleStatsDescription(s, it, begin); handleStatsPercentDescription(s, it, begin); - handleReflectPercentDescription(s, it, begin); - handleAbsorbsPercentDescription(s, it, begin); - handleAbsorbsFieldsPercentDescription(s, it, begin); handleMiscDescription(s, it, begin); } \ No newline at end of file diff --git a/src/item.h b/src/item.h index c40f959c..fd732193 100644 --- a/src/item.h +++ b/src/item.h @@ -10,8 +10,8 @@ #include "luascript.h" #include "tools.h" #include "imbuement.h" +#include "augments.h" #include - #include #include @@ -91,8 +91,8 @@ enum AttrTypes_t { ATTR_WRAPID = 36, ATTR_STOREITEM = 37, ATTR_ATTACK_SPEED = 38, - ATTR_REFLECT = 39, - ATTR_BOOST = 40, + ATTR_PLACE_HOLDER = 39, + ATTR_PLACE_HOLDERTOO = 40, ATTR_CLASSIFICATION = 41, ATTR_TIER = 42, ATTR_IMBUESLOTS = 43, @@ -350,7 +350,6 @@ class ItemAttributes static int64_t emptyInt; static double emptyDouble; static bool emptyBool; - static Reflect emptyReflect; typedef std::unordered_map CustomAttributeMap; @@ -419,18 +418,6 @@ class ItemAttributes std::vector attributes; uint32_t attributeBits = 0; - std::map reflect; - std::map boostPercent; - - const Reflect& getReflect(CombatType_t combatType) { - auto it = reflect.find(combatType); - return it != reflect.end() ? it->second : emptyReflect; - } - int16_t getBoostPercent(CombatType_t combatType) { - auto it = boostPercent.find(combatType); - return it != boostPercent.end() ? it->second : 0; - } - const std::string& getStrAttr(itemAttrTypes type) const; void setStrAttr(itemAttrTypes type, std::string_view value); @@ -921,16 +908,6 @@ class Item : virtual public Thing uint32_t getWorth() const; LightInfo getLightInfo() const; - void setReflect(CombatType_t combatType, const Reflect& reflect) { - getAttributes()->reflect[combatType] = reflect; - } - Reflect getReflect(CombatType_t combatType, bool total = true) const; - - void setBoostPercent(CombatType_t combatType, uint16_t value) { - getAttributes()->boostPercent[combatType] = value; - } - uint16_t getBoostPercent(CombatType_t combatType, bool total = true) const; - bool hasProperty(ITEMPROPERTY prop) const; bool isBlocking() const { return items[id].blockSolid; @@ -1105,6 +1082,18 @@ class Item : virtual public Thing bool removeImbuement(std::shared_ptr imbuement, bool decayed = false); std::vector>& getImbuements(); + const bool addAugment(std::string_view augmentName); + const bool addAugment(std::shared_ptr& augment); + + const bool removeAugment(std::string_view name); + const bool removeAugment(std::shared_ptr& augment); + + bool isAugmented(); + bool hasAugment(std::string_view name); + bool hasAugment(const std::shared_ptr& augment); + + const std::vector>& getAugments(); + protected: Cylinder* parent = nullptr; @@ -1117,7 +1106,7 @@ class Item : virtual public Thing uint16_t imbuementSlots = 0; std::vector> imbuements; - + std::vector> augments; uint32_t referenceCounter = 0; uint8_t count = 1; // number of stacked items diff --git a/src/items.cpp b/src/items.cpp index 186d972a..8e67f03a 100644 --- a/src/items.cpp +++ b/src/items.cpp @@ -86,75 +86,6 @@ const std::unordered_map ItemParseAttributes {"lifeleechamount", ITEM_PARSE_LIFELEECHAMOUNT}, {"manaleechchance", ITEM_PARSE_MANALEECHCHANCE}, {"manaleechamount", ITEM_PARSE_MANALEECHAMOUNT}, - {"fieldabsorbpercentenergy", ITEM_PARSE_FIELDABSORBPERCENTENERGY}, - {"fieldabsorbpercentfire", ITEM_PARSE_FIELDABSORBPERCENTFIRE}, - {"fieldabsorbpercentpoison", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, - {"fieldabsorbpercentearth", ITEM_PARSE_FIELDABSORBPERCENTPOISON}, - {"absorbpercentall", ITEM_PARSE_ABSORBPERCENTALL}, - {"absorbpercentallelements", ITEM_PARSE_ABSORBPERCENTALL}, - {"absorbpercentelements", ITEM_PARSE_ABSORBPERCENTELEMENTS}, - {"absorbpercentmagic", ITEM_PARSE_ABSORBPERCENTMAGIC}, - {"absorbpercentenergy", ITEM_PARSE_ABSORBPERCENTENERGY}, - {"absorbpercentfire", ITEM_PARSE_ABSORBPERCENTFIRE}, - {"absorbpercentpoison", ITEM_PARSE_ABSORBPERCENTPOISON}, - {"absorbpercentearth", ITEM_PARSE_ABSORBPERCENTPOISON}, - {"absorbpercentice", ITEM_PARSE_ABSORBPERCENTICE}, - {"absorbpercentholy", ITEM_PARSE_ABSORBPERCENTHOLY}, - {"absorbpercentdeath", ITEM_PARSE_ABSORBPERCENTDEATH}, - {"absorbpercentlifedrain", ITEM_PARSE_ABSORBPERCENTLIFEDRAIN}, - {"absorbpercentmanadrain", ITEM_PARSE_ABSORBPERCENTMANADRAIN}, - {"absorbpercentdrown", ITEM_PARSE_ABSORBPERCENTDROWN}, - {"absorbpercentphysical", ITEM_PARSE_ABSORBPERCENTPHYSICAL}, - {"absorbpercenthealing", ITEM_PARSE_ABSORBPERCENTHEALING}, - {"absorbpercentundefined", ITEM_PARSE_ABSORBPERCENTUNDEFINED}, - {"reflectpercentall", ITEM_PARSE_REFLECTPERCENTALL}, - {"reflectpercentallelements", ITEM_PARSE_REFLECTPERCENTALL}, - {"reflectpercentelements", ITEM_PARSE_REFLECTPERCENTELEMENTS}, - {"reflectpercentmagic", ITEM_PARSE_REFLECTPERCENTMAGIC}, - {"reflectpercentenergy", ITEM_PARSE_REFLECTPERCENTENERGY}, - {"reflectpercentfire", ITEM_PARSE_REFLECTPERCENTFIRE}, - {"reflectpercentpoison", ITEM_PARSE_REFLECTPERCENTEARTH}, - {"reflectpercentearth", ITEM_PARSE_REFLECTPERCENTEARTH}, - {"reflectpercentice", ITEM_PARSE_REFLECTPERCENTICE}, - {"reflectpercentholy", ITEM_PARSE_REFLECTPERCENTHOLY}, - {"reflectpercentdeath", ITEM_PARSE_REFLECTPERCENTDEATH}, - {"reflectpercentlifedrain", ITEM_PARSE_REFLECTPERCENTLIFEDRAIN}, - {"reflectpercentmanadrain", ITEM_PARSE_REFLECTPERCENTMANADRAIN}, - {"reflectpercentdrown", ITEM_PARSE_REFLECTPERCENTDROWN}, - {"reflectpercentphysical", ITEM_PARSE_REFLECTPERCENTPHYSICAL}, - {"reflectpercenthealing", ITEM_PARSE_REFLECTPERCENTHEALING}, - {"reflectchanceall", ITEM_PARSE_REFLECTCHANCEALL}, - {"reflectchanceallelements", ITEM_PARSE_REFLECTCHANCEALL}, - {"reflectchanceelements", ITEM_PARSE_REFLECTCHANCEELEMENTS}, - {"reflectchancemagic", ITEM_PARSE_REFLECTCHANCEMAGIC}, - {"reflectchanceenergy", ITEM_PARSE_REFLECTCHANCEENERGY}, - {"reflectchancefire", ITEM_PARSE_REFLECTCHANCEFIRE}, - {"reflectchancepoison", ITEM_PARSE_REFLECTCHANCEEARTH}, - {"reflectchanceearth", ITEM_PARSE_REFLECTCHANCEEARTH}, - {"reflectchanceice", ITEM_PARSE_REFLECTCHANCEICE}, - {"reflectchanceholy", ITEM_PARSE_REFLECTCHANCEHOLY}, - {"reflectchancedeath", ITEM_PARSE_REFLECTCHANCEDEATH}, - {"reflectchancelifedrain", ITEM_PARSE_REFLECTCHANCELIFEDRAIN}, - {"reflectchancemanadrain", ITEM_PARSE_REFLECTCHANCEMANADRAIN}, - {"reflectchancedrown", ITEM_PARSE_REFLECTCHANCEDROWN}, - {"reflectchancephysical", ITEM_PARSE_REFLECTCHANCEPHYSICAL}, - {"reflectchancehealing", ITEM_PARSE_REFLECTCHANCEHEALING}, - {"boostpercentall", ITEM_PARSE_BOOSTPERCENTALL}, - {"boostpercentallelements", ITEM_PARSE_BOOSTPERCENTALL}, - {"boostpercentelements", ITEM_PARSE_BOOSTPERCENTELEMENTS}, - {"boostpercentmagic", ITEM_PARSE_BOOSTPERCENTMAGIC}, - {"boostpercentenergy", ITEM_PARSE_BOOSTPERCENTENERGY}, - {"boostpercentfire", ITEM_PARSE_BOOSTPERCENTFIRE}, - {"boostpercentpoison", ITEM_PARSE_BOOSTPERCENTEARTH}, - {"boostpercentearth", ITEM_PARSE_BOOSTPERCENTEARTH}, - {"boostpercentice", ITEM_PARSE_BOOSTPERCENTICE}, - {"boostpercentholy", ITEM_PARSE_BOOSTPERCENTHOLY}, - {"boostpercentdeath", ITEM_PARSE_BOOSTPERCENTDEATH}, - {"boostpercentlifedrain", ITEM_PARSE_BOOSTPERCENTLIFEDRAIN}, - {"boostpercentmanadrain", ITEM_PARSE_BOOSTPERCENTMANADRAIN}, - {"boostpercentdrown", ITEM_PARSE_BOOSTPERCENTDROWN}, - {"boostpercentphysical", ITEM_PARSE_BOOSTPERCENTPHYSICAL}, - {"boostpercenthealing", ITEM_PARSE_BOOSTPERCENTHEALING}, {"suppressdrunk", ITEM_PARSE_SUPPRESSDRUNK}, {"suppressenergy", ITEM_PARSE_SUPPRESSENERGY}, {"suppressfire", ITEM_PARSE_SUPPRESSFIRE}, @@ -1059,358 +990,6 @@ void Items::parseItemNode(const pugi::xml_node& itemNode, uint16_t id) break; } - case ITEM_PARSE_FIELDABSORBPERCENTENERGY: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_FIELDABSORBPERCENTFIRE: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_FIELDABSORBPERCENTPOISON: { - abilities.fieldAbsorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTALL: { - int16_t value = pugi::cast(valueAttribute.value()); - for (auto& i : abilities.absorbPercent) { - i += value; - } - break; - } - - case ITEM_PARSE_ABSORBPERCENTELEMENTS: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; - break; - } - - case ITEM_PARSE_ABSORBPERCENTMAGIC: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += value; - abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += value; - break; - } - - case ITEM_PARSE_ABSORBPERCENTENERGY: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTFIRE: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTPOISON: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTICE: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTHOLY: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTDEATH: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTLIFEDRAIN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTMANADRAIN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTDROWN: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTPHYSICAL: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTHEALING: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_HEALING)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_ABSORBPERCENTUNDEFINED: { - abilities.absorbPercent[combatTypeToIndex(COMBAT_UNDEFINEDDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTALL: { - int16_t value = pugi::cast(valueAttribute.value()); - for (auto& i : abilities.reflect) { - i.percent += value; - } - break; - } - - case ITEM_PARSE_REFLECTPERCENTELEMENTS: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].percent += value; - abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].percent += value; - abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].percent += value; - abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].percent += value; - break; - } - - case ITEM_PARSE_REFLECTPERCENTMAGIC: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].percent += value; - abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].percent += value; - abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].percent += value; - abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].percent += value; - abilities.reflect[combatTypeToIndex(COMBAT_HOLYDAMAGE)].percent += value; - abilities.reflect[combatTypeToIndex(COMBAT_DEATHDAMAGE)].percent += value; - break; - } - - case ITEM_PARSE_REFLECTPERCENTENERGY: { - abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTFIRE: { - abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTEARTH: { - abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTICE: { - abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTHOLY: { - abilities.reflect[combatTypeToIndex(COMBAT_HOLYDAMAGE)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTDEATH: { - abilities.reflect[combatTypeToIndex(COMBAT_DEATHDAMAGE)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTLIFEDRAIN: { - abilities.reflect[combatTypeToIndex(COMBAT_LIFEDRAIN)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTMANADRAIN: { - abilities.reflect[combatTypeToIndex(COMBAT_MANADRAIN)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTDROWN: { - abilities.reflect[combatTypeToIndex(COMBAT_DROWNDAMAGE)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTPHYSICAL: { - abilities.reflect[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTPERCENTHEALING: { - abilities.reflect[combatTypeToIndex(COMBAT_HEALING)].percent += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEALL: { - int16_t value = pugi::cast(valueAttribute.value()); - for (auto& i : abilities.reflect) { - i.chance += value; - } - break; - } - - case ITEM_PARSE_REFLECTCHANCEELEMENTS: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].chance += value; - abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].chance += value; - abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].chance += value; - abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].chance += value; - break; - } - - case ITEM_PARSE_REFLECTCHANCEMAGIC: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].chance += value; - abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].chance += value; - abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].chance += value; - abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].chance += value; - abilities.reflect[combatTypeToIndex(COMBAT_HOLYDAMAGE)].chance += value; - abilities.reflect[combatTypeToIndex(COMBAT_DEATHDAMAGE)].chance += value; - break; - } - - case ITEM_PARSE_REFLECTCHANCEENERGY: { - abilities.reflect[combatTypeToIndex(COMBAT_ENERGYDAMAGE)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEFIRE: { - abilities.reflect[combatTypeToIndex(COMBAT_FIREDAMAGE)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEEARTH: { - abilities.reflect[combatTypeToIndex(COMBAT_EARTHDAMAGE)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEICE: { - abilities.reflect[combatTypeToIndex(COMBAT_ICEDAMAGE)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEHOLY: { - abilities.reflect[combatTypeToIndex(COMBAT_HOLYDAMAGE)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEDEATH: { - abilities.reflect[combatTypeToIndex(COMBAT_DEATHDAMAGE)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCELIFEDRAIN: { - abilities.reflect[combatTypeToIndex(COMBAT_LIFEDRAIN)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEMANADRAIN: { - abilities.reflect[combatTypeToIndex(COMBAT_MANADRAIN)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEDROWN: { - abilities.reflect[combatTypeToIndex(COMBAT_DROWNDAMAGE)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEPHYSICAL: { - abilities.reflect[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_REFLECTCHANCEHEALING: { - abilities.reflect[combatTypeToIndex(COMBAT_HEALING)].chance += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTALL: { - int16_t value = pugi::cast(valueAttribute.value()); - for (auto& i : abilities.boostPercent) { - i += value; - } - break; - } - - case ITEM_PARSE_BOOSTPERCENTELEMENTS: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.boostPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; - abilities.boostPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; - abilities.boostPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; - abilities.boostPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; - break; - } - - case ITEM_PARSE_BOOSTPERCENTMAGIC: { - int16_t value = pugi::cast(valueAttribute.value()); - abilities.boostPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += value; - abilities.boostPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += value; - abilities.boostPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += value; - abilities.boostPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += value; - abilities.boostPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += value; - abilities.boostPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += value; - break; - } - - case ITEM_PARSE_BOOSTPERCENTENERGY: { - abilities.boostPercent[combatTypeToIndex(COMBAT_ENERGYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTFIRE: { - abilities.boostPercent[combatTypeToIndex(COMBAT_FIREDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTEARTH: { - abilities.boostPercent[combatTypeToIndex(COMBAT_EARTHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTICE: { - abilities.boostPercent[combatTypeToIndex(COMBAT_ICEDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTHOLY: { - abilities.boostPercent[combatTypeToIndex(COMBAT_HOLYDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTDEATH: { - abilities.boostPercent[combatTypeToIndex(COMBAT_DEATHDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTLIFEDRAIN: { - abilities.boostPercent[combatTypeToIndex(COMBAT_LIFEDRAIN)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTMANADRAIN: { - abilities.boostPercent[combatTypeToIndex(COMBAT_MANADRAIN)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTDROWN: { - abilities.boostPercent[combatTypeToIndex(COMBAT_DROWNDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTPHYSICAL: { - abilities.boostPercent[combatTypeToIndex(COMBAT_PHYSICALDAMAGE)] += pugi::cast(valueAttribute.value()); - break; - } - - case ITEM_PARSE_BOOSTPERCENTHEALING: { - abilities.boostPercent[combatTypeToIndex(COMBAT_HEALING)] += pugi::cast(valueAttribute.value()); - break; - } - case ITEM_PARSE_SUPPRESSDRUNK: { if (valueAttribute.as_bool()) { abilities.conditionSuppressions |= CONDITION_DRUNK; diff --git a/src/items.h b/src/items.h index d92ea0f7..4ad84255 100644 --- a/src/items.h +++ b/src/items.h @@ -112,66 +112,6 @@ enum ItemParseAttributes_t { ITEM_PARSE_LIFELEECHAMOUNT, ITEM_PARSE_MANALEECHCHANCE, ITEM_PARSE_MANALEECHAMOUNT, - ITEM_PARSE_FIELDABSORBPERCENTENERGY, - ITEM_PARSE_FIELDABSORBPERCENTFIRE, - ITEM_PARSE_FIELDABSORBPERCENTPOISON, - ITEM_PARSE_ABSORBPERCENTALL, - ITEM_PARSE_ABSORBPERCENTELEMENTS, - ITEM_PARSE_ABSORBPERCENTMAGIC, - ITEM_PARSE_ABSORBPERCENTENERGY, - ITEM_PARSE_ABSORBPERCENTFIRE, - ITEM_PARSE_ABSORBPERCENTPOISON, - ITEM_PARSE_ABSORBPERCENTICE, - ITEM_PARSE_ABSORBPERCENTHOLY, - ITEM_PARSE_ABSORBPERCENTDEATH, - ITEM_PARSE_ABSORBPERCENTLIFEDRAIN, - ITEM_PARSE_ABSORBPERCENTMANADRAIN, - ITEM_PARSE_ABSORBPERCENTDROWN, - ITEM_PARSE_ABSORBPERCENTPHYSICAL, - ITEM_PARSE_ABSORBPERCENTHEALING, - ITEM_PARSE_ABSORBPERCENTUNDEFINED, - ITEM_PARSE_REFLECTPERCENTALL, - ITEM_PARSE_REFLECTPERCENTELEMENTS, - ITEM_PARSE_REFLECTPERCENTMAGIC, - ITEM_PARSE_REFLECTPERCENTENERGY, - ITEM_PARSE_REFLECTPERCENTFIRE, - ITEM_PARSE_REFLECTPERCENTEARTH, - ITEM_PARSE_REFLECTPERCENTICE, - ITEM_PARSE_REFLECTPERCENTHOLY, - ITEM_PARSE_REFLECTPERCENTDEATH, - ITEM_PARSE_REFLECTPERCENTLIFEDRAIN, - ITEM_PARSE_REFLECTPERCENTMANADRAIN, - ITEM_PARSE_REFLECTPERCENTDROWN, - ITEM_PARSE_REFLECTPERCENTPHYSICAL, - ITEM_PARSE_REFLECTPERCENTHEALING, - ITEM_PARSE_REFLECTCHANCEALL, - ITEM_PARSE_REFLECTCHANCEELEMENTS, - ITEM_PARSE_REFLECTCHANCEMAGIC, - ITEM_PARSE_REFLECTCHANCEENERGY, - ITEM_PARSE_REFLECTCHANCEFIRE, - ITEM_PARSE_REFLECTCHANCEEARTH, - ITEM_PARSE_REFLECTCHANCEICE, - ITEM_PARSE_REFLECTCHANCEHOLY, - ITEM_PARSE_REFLECTCHANCEDEATH, - ITEM_PARSE_REFLECTCHANCELIFEDRAIN, - ITEM_PARSE_REFLECTCHANCEMANADRAIN, - ITEM_PARSE_REFLECTCHANCEDROWN, - ITEM_PARSE_REFLECTCHANCEPHYSICAL, - ITEM_PARSE_REFLECTCHANCEHEALING, - ITEM_PARSE_BOOSTPERCENTALL, - ITEM_PARSE_BOOSTPERCENTELEMENTS, - ITEM_PARSE_BOOSTPERCENTMAGIC, - ITEM_PARSE_BOOSTPERCENTENERGY, - ITEM_PARSE_BOOSTPERCENTFIRE, - ITEM_PARSE_BOOSTPERCENTEARTH, - ITEM_PARSE_BOOSTPERCENTICE, - ITEM_PARSE_BOOSTPERCENTHOLY, - ITEM_PARSE_BOOSTPERCENTDEATH, - ITEM_PARSE_BOOSTPERCENTLIFEDRAIN, - ITEM_PARSE_BOOSTPERCENTMANADRAIN, - ITEM_PARSE_BOOSTPERCENTDROWN, - ITEM_PARSE_BOOSTPERCENTPHYSICAL, - ITEM_PARSE_BOOSTPERCENTHEALING, ITEM_PARSE_SUPPRESSDRUNK, ITEM_PARSE_SUPPRESSENERGY, ITEM_PARSE_SUPPRESSFIRE, @@ -222,14 +162,6 @@ struct Abilities { int32_t speed = 0; - // field damage abilities modifiers - std::array fieldAbsorbPercent = {0}; - - //damage abilities modifiers - std::array absorbPercent = {0}; - std::array reflect; - int16_t boostPercent[COMBAT_COUNT] = { 0 }; - //elemental damage uint16_t elementDamage = 0; CombatType_t elementType = COMBAT_NONE; diff --git a/src/luascript.cpp b/src/luascript.cpp index 40e65003..ec5012f6 100644 --- a/src/luascript.cpp +++ b/src/luascript.cpp @@ -28,6 +28,7 @@ #include "script.h" #include "weapons.h" #include "luavariant.h" +#include "augments.h" extern Chat* g_chat; extern Game g_game; @@ -822,14 +823,6 @@ InstantSpell* LuaScriptInterface::getInstantSpell(lua_State* L, int32_t arg) return spell; } -Reflect LuaScriptInterface::getReflect(lua_State* L, int32_t arg) -{ - uint16_t percent = getField(L, arg, "percent"); - uint16_t chance = getField(L, arg, "chance"); - lua_pop(L, 2); - return Reflect(percent, chance); -} - Thing* LuaScriptInterface::getThing(lua_State* L, int32_t arg) { Thing* thing; @@ -1015,13 +1008,6 @@ void LuaScriptInterface::pushLoot(lua_State* L, const std::vector& lo } } -void LuaScriptInterface::pushReflect(lua_State* L, const Reflect& reflect) -{ - lua_createtable(L, 0, 2); - setField(L, "percent", reflect.percent); - setField(L, "chance", reflect.chance); -} - #define registerEnum(value) { std::string enumName = #value; registerGlobalVariable(enumName.substr(enumName.find_last_of(':') + 1), value); } #define registerEnumIn(tableName, value) { std::string enumName = #value; registerVariable(tableName, enumName.substr(enumName.find_last_of(':') + 1), value); } @@ -2350,11 +2336,6 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "setStoreItem", LuaScriptInterface::luaItemSetStoreItem); registerMethod("Item", "isStoreItem", LuaScriptInterface::luaItemIsStoreItem); - registerMethod("Item", "setReflect", LuaScriptInterface::luaItemSetReflect); - registerMethod("Item", "getReflect", LuaScriptInterface::luaItemGetReflect); - - registerMethod("Item", "setBoostPercent", LuaScriptInterface::luaItemSetBoostPercent); - registerMethod("Item", "getBoostPercent", LuaScriptInterface::luaItemGetBoostPercent); registerMethod("Item", "getImbuementSlots", LuaScriptInterface::luaItemGetImbuementSlots); registerMethod("Item", "getFreeImbuementSlots", LuaScriptInterface::luaItemGetFreeImbuementSlots); @@ -2367,6 +2348,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Item", "removeImbuement", LuaScriptInterface::luaItemRemoveImbuement); registerMethod("Item", "getImbuements", LuaScriptInterface::luaItemGetImbuements); + registerMethod("Item", "addAugment", LuaScriptInterface::luaItemAddAugment); + registerMethod("Item", "removeAugment", LuaScriptInterface::luaItemRemoveAugment); + registerMethod("Item", "isAugmented", LuaScriptInterface::luaItemIsAugmented); + registerMethod("Item", "hasAugment", LuaScriptInterface::luaItemHasAugment); + registerMethod("Item", "getAugments", LuaScriptInterface::luaItemGetAugments); + // Imbuement registerClass("Imbuement", "", LuaScriptInterface::luaImbuementCreate); registerMetaMethod("Imbuement", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2385,6 +2372,26 @@ void LuaScriptInterface::registerFunctions() registerMethod("Imbuement", "isEquipDecayed", LuaScriptInterface::luaImbuementIsEquipDecay); registerMethod("Imbuement", "isInfightDecayed", LuaScriptInterface::luaImbuementIsInfightDecay); + // DamageModifer + registerClass("DamageModifier", "", LuaScriptInterface::luaDamageModifierCreate); + registerMetaMethod("DamageModifier", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMethod("DamageModifier", "setValue", LuaScriptInterface::luaDamageModifierSetValue); + registerMethod("DamageModifier", "setRateFactor", LuaScriptInterface::luaDamageModifierSetRateFactor); + registerMethod("DamageModifier", "setCombatFilter", LuaScriptInterface::luaDamageModifierSetCombatFilter); + registerMethod("DamageModifier", "setOriginFilter", LuaScriptInterface::luaDamageModifierSetOriginFilter); + + // Augment + registerClass("Augment", "", LuaScriptInterface::luaAugmentCreate); + registerMetaMethod("Augment", "__eq", LuaScriptInterface::luaUserdataCompare); + registerMethod("Augment", "setName", LuaScriptInterface::luaAugmentSetName); + registerMethod("Augment", "setDescription", LuaScriptInterface::luaAugmentSetDescription); + registerMethod("Augment", "getName", LuaScriptInterface::luaAugmentGetName); + registerMethod("Augment", "getDescription", LuaScriptInterface::luaAugmentGetDescription); + registerMethod("Augment", "addDamageModifier", LuaScriptInterface::luaAugmentAddDamageModifier); + registerMethod("Augment", "removeDamageModifier", LuaScriptInterface::luaAugmentRemoveDamageModifier); + registerMethod("Augment", "getAttackModifiers", LuaScriptInterface::luaAugmentGetDefenseModifiers); + registerMethod("Augment", "getDefenseModifiers", LuaScriptInterface::luaAugmentGetDefenseModifiers); + // Container registerClass("Container", "Item", LuaScriptInterface::luaContainerCreate); registerMetaMethod("Container", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -2674,6 +2681,12 @@ void LuaScriptInterface::registerFunctions() registerMethod("Player", "sendCreatureSquare", LuaScriptInterface::luaPlayerSendCreatureSquare); + registerMethod("Player", "addAugment", LuaScriptInterface::luaPlayerAddAugment); + registerMethod("Player", "removeAugment", LuaScriptInterface::luaPlayerRemoveAugment); + registerMethod("Player", "isAugmented", LuaScriptInterface::luaPlayerIsAugmented); + registerMethod("Player", "hasAugment", LuaScriptInterface::luaPlayerHasAugment); + registerMethod("Player", "getAugments", LuaScriptInterface::luaPlayerGetAugments); + // Monster registerClass("Monster", "Creature", LuaScriptInterface::luaMonsterCreate); registerMetaMethod("Monster", "__eq", LuaScriptInterface::luaUserdataCompare); @@ -5881,19 +5894,24 @@ int LuaScriptInterface::luaTileQueryAdd(lua_State* L) return 1; } - if (Item* item = getUserdata(L, 2)) { - uint32_t flags = getNumber(L, 3, 0); - lua_pushnumber(L, tile->queryAdd(*item, flags)); + Thing* thing = getThing(L, 2); + if (!thing) { + lua_pushnil(L); return 1; } - if (Creature* creature = getUserdata(L, 2)) { + if (Creature* creature = thing->getCreature()) { uint32_t flags = getNumber(L, 3, 0); lua_pushnumber(L, tile->queryAdd(*creature, flags)); return 1; } - + if (Item* item = thing->getItem()) { + uint32_t flags = getNumber(L, 3, 0); + lua_pushnumber(L, tile->queryAdd(*item, flags)); + return 1; + } + lua_pushnil(L); return 1; } @@ -7284,60 +7302,6 @@ int LuaScriptInterface::luaItemIsStoreItem(lua_State* L) return 1; } -int LuaScriptInterface::luaItemSetReflect(lua_State* L) -{ - // item:setReflect(combatType, reflect) - Item* item = getUserdata(L, 1); - if (!item) { - lua_pushnil(L); - return 1; - } - - item->setReflect(getNumber(L, 2), getReflect(L, 3)); - pushBoolean(L, true); - return 1; -} - -int LuaScriptInterface::luaItemGetReflect(lua_State* L) -{ - // item:getReflect(combatType[, total = true]) - Item* item = getUserdata(L, 1); - if (item) { - pushReflect(L, item->getReflect(getNumber(L, 2), getBoolean(L, 3, true))); - } - else { - lua_pushnil(L); - } - return 1; -} - -int LuaScriptInterface::luaItemSetBoostPercent(lua_State* L) -{ - // item:setBoostPercent(combatType, percent) - Item* item = getUserdata(L, 1); - if (!item) { - lua_pushnil(L); - return 1; - } - - item->setBoostPercent(getNumber(L, 2), getNumber(L, 3)); - pushBoolean(L, true); - return 1; -} - -int LuaScriptInterface::luaItemGetBoostPercent(lua_State* L) -{ - // item:getBoostPercent(combatType[, total = true]) - Item* item = getUserdata(L, 1); - if (item) { - lua_pushnumber(L, item->getBoostPercent(getNumber(L, 2), getBoolean(L, 3, true))); - } - else { - lua_pushnil(L); - } - return 1; -} - int LuaScriptInterface::luaItemGetImbuementSlots(lua_State* L) { // item:getImbuementSlots() -- returns how many total slots @@ -7501,6 +7465,117 @@ int LuaScriptInterface::luaItemGetImbuements(lua_State* L) return 1; } +int LuaScriptInterface::luaItemAddAugment(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + std::cout << getString(L, 2) << " \n"; + if (auto augment = Augments::GetAugment(getString(L, 2))) { + lua_pushboolean(L, item->addAugment(augment)); + } else { + lua_pushnil(L); + reportError(__FUNCTION__, "Item::addAugment() argument not found as any name in augments loaded on startup! \n"); + } + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, item->addAugment(augment)); + } else { + lua_pushnil(L); + reportError(__FUNCTION__, "Item::addAugment() invalid userdata passed as argument! \n"); + } + } else { + reportError(__FUNCTION__, "Item::addAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemRemoveAugment(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + auto name = getString(L, 2); + lua_pushboolean(L, item->removeAugment(name)); + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, item->removeAugment(augment)); + } else { + reportError(__FUNCTION__, "Item::removeAugment() invalid userdata type passed as argument! \n"); + lua_pushnil(L); + } + } else { + reportError(__FUNCTION__, "Item::removeAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemIsAugmented(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + lua_pushboolean(L, item->isAugmented()); + return 1; +} + +int LuaScriptInterface::luaItemHasAugment(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + auto name = getString(L, 2); + lua_pushboolean(L, item->hasAugment(name)); + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, item->hasAugment(augment)); + } else { + reportError(__FUNCTION__, "Item::hasAugment() invalid userdata type passed as argument! \n"); + lua_pushnil(L); + } + } else { + reportError(__FUNCTION__, "Item::hasAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaItemGetAugments(lua_State* L) +{ + Item* item = getUserdata(L, 1); + if (!item) { + lua_pushnil(L); + return 1; + } + + std::vector> augments = item->getAugments(); + lua_createtable(L, augments.size(), 0); + + int index = 0; + for (std::shared_ptr augment : augments) { + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + lua_rawseti(L, -2, ++index); + } + return 1; +} + // Imbuement int LuaScriptInterface::luaImbuementCreate(lua_State* L) @@ -7689,6 +7764,311 @@ int LuaScriptInterface::luaImbuementIsInfightDecay(lua_State* L) return 1; } +int LuaScriptInterface::luaDamageModifierCreate(lua_State* L) +{ // To-do : DamageModifier(DamageModifier) + // DamageModifier(stance, type, value, percent/flat, chance, combatType, originType, creatureType, race) + auto stance = getNumber(L, 2); + auto modType = getNumber(L, 3); + auto amount = getNumber(L, 4); + auto factor = getNumber(L, 5); + auto chance = getNumber(L, 6); + auto combatType = getNumber(L, 7, COMBAT_NONE); + auto originType = getNumber(L, 8, ORIGIN_NONE); + auto creatureType = getNumber(L, 9, CREATURETYPE_ATTACKABLE); + auto race = getNumber(L, 10, RACE_NONE); + auto creatureName = getString(L, 11); + + // to-do: handle no param defaults and throw error + if (stance && modType && amount && factor) { + pushSharedPtr(L, DamageModifier::makeModifier(stance, modType, amount, factor, chance, combatType, originType, creatureType, race, creatureName)); + setMetatable(L, -1, "DamageModifier"); + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDamageModifierSetValue(lua_State* L) +{ + std::shared_ptr modifier = getSharedPtr(L, 1); + if (modifier) { + // to-do: handle no param defaults and throw error + uint8_t amount = getNumber(L, 2); + if (amount) { + modifier->setValue(amount); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDamageModifierSetRateFactor(lua_State* L) +{ + std::shared_ptr modifier = getSharedPtr(L, 1); + if (modifier) { + // to-do: handle no param defaults and throw error + uint8_t factor = getNumber(L, 2); + if (factor >= 0) { + modifier->setFactor(factor); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDamageModifierSetCombatFilter(lua_State* L) +{ + std::shared_ptr modifier = getSharedPtr(L, 1); + if (modifier) { + // to-do: handle no param defaults and throw error + CombatType_t combatType = getNumber(L, 2); + if (combatType >= 0) { + modifier->setCombatType(combatType); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaDamageModifierSetOriginFilter(lua_State* L) +{ + std::shared_ptr modifier = getSharedPtr(L, 1); + if (modifier) { + // to-do: handle no param defaults and throw error + CombatOrigin origin = getNumber(L, 2); + if (origin >= 0) { + modifier->setOriginType(origin); + } + } else { + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaAugmentCreate(lua_State* L) +{ // To-do : Augment(augment) and Augment(name) <-- where name is looked up from global collection. + // Augment(name, description, modifier or table_of_modifiers) + + if (isString(L, 2)) { + auto name = getString(L, 2); + auto augment = Augments::GetAugment(name); + + if (augment) { + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + return 1; // return early here because we found a global augment with this name + } + + auto description = getString(L, 3); + if (isUserdata(L, 4)) { + auto modifier = getSharedPtr(L, 4); + if (modifier) { + auto augment = Augment::MakeAugment(name, description); + augment->addModifier(modifier); + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + } else { + reportError(__FUNCTION__, "Invalid Userdata For Modifier Parameter used during Augment Creation \n"); + lua_pushnil(L); + } + } else if (isTable(L, 4)) { + auto list = std::vector>(); + list.reserve(24); + + // Iterate over the table at index 4 + lua_pushnil(L); // First key for lua_next + while (lua_next(L, 4) != 0) { + // Check if the value is userdata and of type DamageModifier + if (isUserdata(L, -1)) { + auto modifier = getSharedPtr(L, -1); + if (modifier) { + list.emplace_back(modifier); + } else { + reportError(__FUNCTION__, "Invalid userdata in table element\n"); + } + } else { + reportError(__FUNCTION__, "Non-userdata found in table element\n"); + } + // Remove the value, keep the key for lua_next + lua_pop(L, 1); + } + + // Create augment with all modifiers + // To-do : Add augments created this particular way to global table + auto augment = Augment::MakeAugment(name, description); + for (const auto& modifier : list) { + augment->addModifier(modifier); + } + pushSharedPtr(L, augment); + setMetatable(L, -1, "Augment"); + } else { + reportError(__FUNCTION__, "Invalid parameter for Augment Creation\n"); + lua_pushnil(L); + } + } + + + return 1; +} + + +int LuaScriptInterface::luaAugmentSetName(lua_State* L) +{ + // Augment:setName(newName) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + return 0; + } + + auto newName = getString(L, 2); + augment->setName(newName); + return 0; +} + +int LuaScriptInterface::luaAugmentSetDescription(lua_State* L) +{ + // Augment:getDescription(newDescription) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + return 0; + } + + auto newDescription = getString(L, 2); + augment->setDescription(newDescription); + return 0; +} + +int LuaScriptInterface::luaAugmentGetName(lua_State* L) { + // Augment:getName() + auto augment = getSharedPtr(L, 1); // Get augment object + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + lua_pushnil(L); + return 1; + } + + std::string_view name = augment->getName(); + lua_pushlstring(L, name.data(), name.size()); + return 1; +} + +int LuaScriptInterface::luaAugmentGetDescription(lua_State* L) { + // Augment:getDescription() + auto augment = getSharedPtr(L, 1); // Get augment object + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + lua_pushnil(L); + return 1; + } + + std::string_view description = augment->getDescription(); // Get the description as string_view + lua_pushlstring(L, description.data(), description.size()); // Push the description onto the Lua stack + return 1; +} + +// To-do : The following methods that return 0 should all be converted to return something (boolean for most). +int LuaScriptInterface::luaAugmentAddDamageModifier(lua_State* L) +{ + // Augment:addDamageModifier(modifier) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + return 0; + } + + auto modifier = getSharedPtr(L, 2); + if (!modifier) { + reportError(__FUNCTION__, "Invalid DamageModifier userdata\n"); + return 0; + } + + augment->addModifier(modifier); + return 0; +} + +int LuaScriptInterface::luaAugmentRemoveDamageModifier(lua_State* L) +{ + // Augment:RemoveDamageModifier(modifier) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + return 0; + } + + auto modifier = getSharedPtr(L, 2); + if (!modifier) { + reportError(__FUNCTION__, "Invalid DamageModifier userdata\n"); + return 0; + } + + augment->removeModifier(modifier); + return 0; +} + +int LuaScriptInterface::luaAugmentGetAttackModifiers(lua_State* L) { + // Augment:GetAttackModifiers([modType]) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + lua_pushnil(L); + return 1; + } + + std::vector> modifiers; + + if (lua_gettop(L) > 1 && lua_isinteger(L, 2)) { + uint8_t modType = static_cast(lua_tointeger(L, 2)); + modifiers = augment->getAttackModifiers(modType); + } else { + modifiers = augment->getAttackModifiers(); + } + + lua_newtable(L); + int index = 1; + for (const auto& modifier : modifiers) { + pushSharedPtr(L, modifier); + setMetatable(L, -1, "DamageModifier"); + lua_rawseti(L, -2, index++); + } + + return 1; +} + +int LuaScriptInterface::luaAugmentGetDefenseModifiers(lua_State* L) { + // Augment:GetDefenseModifiers([modType]) + auto augment = getSharedPtr(L, 1); + if (!augment) { + reportError(__FUNCTION__, "Invalid Augment userdata\n"); + lua_pushnil(L); + return 1; + } + + std::vector> modifiers; + + if (lua_gettop(L) > 1 && lua_isinteger(L, 2)) { + uint8_t modType = static_cast(lua_tointeger(L, 2)); + modifiers = augment->getDefenseModifiers(modType); + } else { + modifiers = augment->getDefenseModifiers(); + } + + lua_newtable(L); + int index = 1; + for (const auto& modifier : modifiers) { + pushSharedPtr(L, modifier); + setMetatable(L, -1, "DamageModifier"); + lua_rawseti(L, -2, index++); + } + + return 1; +} + + // Container int LuaScriptInterface::luaContainerCreate(lua_State* L) { @@ -11247,6 +11627,75 @@ int LuaScriptInterface::luaPlayerSendCreatureSquare(lua_State* L) return 1; } +int LuaScriptInterface::luaPlayerAddAugment(lua_State* L) +{ + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + if (auto augment = Augments::GetAugment(getString(L, 2))) { + lua_pushboolean(L, player->addAugment(augment)); + } else { + lua_pushnil(L); + reportError(__FUNCTION__, "Player::addAugment() argument not found as any name in augments loaded on startup! \n"); + } + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, player->addAugment(augment)); + } else { + lua_pushnil(L); + reportError(__FUNCTION__, "Player::addAugment() invalid userdata passed as argument! \n"); + } + } else { + reportError(__FUNCTION__, "Player::addAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerRemoveAugment(lua_State* L) +{ + Player* player = getUserdata(L, 1); + if (!player) { + lua_pushnil(L); + return 1; + } + + if (isString(L, 2)) { + auto name = getString(L, 2); + lua_pushboolean(L, player->removeAugment(name)); + } else if (isUserdata(L, 2)) { + if (std::shared_ptr augment = getSharedPtr(L, 2)) { + lua_pushboolean(L, player->removeAugment(augment)); + } else { + reportError(__FUNCTION__, "Player::removeAugment() invalid userdata type passed as argument! \n"); + lua_pushnil(L); + } + } else { + reportError(__FUNCTION__, "Player::removeAugment() passed invalid type, must be string or userdata! \n"); + lua_pushnil(L); + } + return 1; +} + +int LuaScriptInterface::luaPlayerIsAugmented(lua_State* L) +{ + return 0; +} + +int LuaScriptInterface::luaPlayerHasAugment(lua_State* L) +{ + return 0; +} + +int LuaScriptInterface::luaPlayerGetAugments(lua_State* L) +{ + return 0; +} + // Monster int LuaScriptInterface::luaMonsterCreate(lua_State* L) { @@ -13390,22 +13839,6 @@ int LuaScriptInterface::luaItemTypeGetAbilities(lua_State* L) lua_rawseti(L, -2, i + 1); } lua_setfield(L, -2, "specialSkills"); - - // Field absorb percent - lua_createtable(L, 0, COMBAT_COUNT); - for (int32_t i = 0; i < COMBAT_COUNT; i++) { - lua_pushnumber(L, abilities.fieldAbsorbPercent[i]); - lua_rawseti(L, -2, i + 1); - } - lua_setfield(L, -2, "fieldAbsorbPercent"); - - // Absorb percent - lua_createtable(L, 0, COMBAT_COUNT); - for (int32_t i = 0; i < COMBAT_COUNT; i++) { - lua_pushnumber(L, abilities.absorbPercent[i]); - lua_rawseti(L, -2, i + 1); - } - lua_setfield(L, -2, "absorbPercent"); } return 1; } diff --git a/src/luascript.h b/src/luascript.h index 17ec47fd..9416c398 100644 --- a/src/luascript.h +++ b/src/luascript.h @@ -338,7 +338,6 @@ class LuaScriptInterface static Outfit getOutfitClass(lua_State* L, int32_t arg); static InstantSpell* getInstantSpell(lua_State* L, int32_t arg); - static Reflect getReflect(lua_State* L, int32_t arg); static Thing* getThing(lua_State* L, int32_t arg); static Creature* getCreature(lua_State* L, int32_t arg); @@ -398,7 +397,6 @@ class LuaScriptInterface static void pushOutfit(lua_State* L, const Outfit* outfit); static void pushMount(lua_State* L, const Mount* mount); static void pushLoot(lua_State* L, const std::vector& lootList); - static void pushReflect(lua_State* L, const Reflect& reflect); // static void setField(lua_State* L, const char* index, lua_Number value) @@ -772,12 +770,6 @@ class LuaScriptInterface static int luaItemSetStoreItem(lua_State* L); static int luaItemIsStoreItem(lua_State* L); - static int luaItemSetReflect(lua_State* L); - static int luaItemGetReflect(lua_State* L); - - static int luaItemSetBoostPercent(lua_State* L); - static int luaItemGetBoostPercent(lua_State* L); - static int luaItemGetImbuementSlots(lua_State* L); static int luaItemGetFreeImbuementSlots(lua_State* L); static int luaItemCanImbue(lua_State* L); @@ -790,6 +782,13 @@ class LuaScriptInterface static int luaItemRemoveImbuement(lua_State* L); static int luaItemGetImbuements(lua_State* L); + static int luaItemAddAugment(lua_State* L); + static int luaItemRemoveAugment(lua_State* L); + static int luaItemIsAugmented(lua_State* L); + static int luaItemHasAugment(lua_State* L); + static int luaItemGetAugments(lua_State* L); + + // Imbuement static int luaImbuementCreate(lua_State* L); @@ -808,6 +807,24 @@ class LuaScriptInterface static int luaImbuementIsEquipDecay(lua_State* L); static int luaImbuementIsInfightDecay(lua_State* L); + // DamageModifier + static int luaDamageModifierCreate(lua_State* L); + static int luaDamageModifierSetValue(lua_State* L); + static int luaDamageModifierSetRateFactor(lua_State* L); + static int luaDamageModifierSetCombatFilter(lua_State* L); + static int luaDamageModifierSetOriginFilter(lua_State* L); + + // Augment + static int luaAugmentCreate(lua_State* L); + static int luaAugmentSetName(lua_State* L); + static int luaAugmentSetDescription(lua_State* L); + static int luaAugmentGetName(lua_State* L); + static int luaAugmentGetDescription(lua_State* L); + static int luaAugmentAddDamageModifier(lua_State* L); + static int luaAugmentRemoveDamageModifier(lua_State* L); + static int luaAugmentGetAttackModifiers(lua_State* L); + static int luaAugmentGetDefenseModifiers(lua_State* L); + // Container static int luaContainerCreate(lua_State* L); @@ -1094,6 +1111,12 @@ class LuaScriptInterface static int luaPlayerSendCreatureSquare(lua_State* L); + static int luaPlayerAddAugment(lua_State* L); + static int luaPlayerRemoveAugment(lua_State* L); + static int luaPlayerIsAugmented(lua_State* L); + static int luaPlayerHasAugment(lua_State* L); + static int luaPlayerGetAugments(lua_State* L); + // Monster static int luaMonsterCreate(lua_State* L); diff --git a/src/monster.h b/src/monster.h index 51ae0aca..12426d70 100644 --- a/src/monster.h +++ b/src/monster.h @@ -64,6 +64,7 @@ class Monster final : public Creature } CreatureType_t getType() const override { + // to-do : write the logic for all the various summons return CREATURETYPE_MONSTER; } diff --git a/src/otserv.cpp b/src/otserv.cpp index c13024ce..50719cc3 100644 --- a/src/otserv.cpp +++ b/src/otserv.cpp @@ -21,6 +21,7 @@ #include "script.h" #include #include +#include "augments.h" #if __has_include("gitmetadata.h") #include "gitmetadata.h" #endif @@ -286,6 +287,9 @@ void mainLoader(int, char*[], ServiceManager* services) return; } + std::cout << ">> Loading augments" << std::endl; + Augments::loadAll(); + std::cout << ">> Initializing gamestate" << std::endl; g_game.setGameState(GAME_STATE_INIT); diff --git a/src/player.cpp b/src/player.cpp index 46dfe2f8..25bb1584 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -16,6 +16,7 @@ #include "scheduler.h" #include "weapons.h" #include "rewardchest.h" +#include "player.h" extern ConfigManager g_config; extern Game g_game; @@ -1987,8 +1988,7 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ } if (!ignoreResistances) { - Reflect reflect; - + size_t combatIndex = combatTypeToIndex(combatType); for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { if (!isItemAbilityEnabled(static_cast(slot))) { @@ -2006,32 +2006,12 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ damage = 0; return BLOCK_ARMOR; } - continue; } - const int16_t& absorbPercent = it.abilities->absorbPercent[combatIndex]; - if (absorbPercent != 0) { - damage -= std::round(damage * (absorbPercent / 100.)); - - uint16_t charges = item->getCharges(); - if (charges != 0) { - g_game.transformItem(item, item->getID(), charges - 1); - } - } - - reflect += item->getReflect(combatType); - - if (field) { - const int16_t& fieldAbsorbPercent = it.abilities->fieldAbsorbPercent[combatIndex]; - if (fieldAbsorbPercent != 0) { - damage -= std::round(damage * (fieldAbsorbPercent / 100.)); - - uint16_t charges = item->getCharges(); - if (charges != 0) { - g_game.transformItem(item, item->getID(), charges - 1); - } - } + uint16_t charges = item->getCharges(); + if (charges != 0) { + g_game.transformItem(item, item->getID(), charges - 1); } if (item->hasImbuements()) { @@ -2070,17 +2050,7 @@ BlockType_t Player::blockHit(Creature* attacker, CombatType_t combatType, int32_ } } } - } - - if (attacker && reflect.chance > 0 && reflect.percent != 0 && uniform_random(1, 100) <= reflect.chance) { - CombatDamage reflectDamage; - reflectDamage.primary.type = combatType; - reflectDamage.primary.value = -std::round(damage * (reflect.percent / 100.)); - reflectDamage.origin = ORIGIN_REFLECT; - g_game.combatChangeHealth(this, attacker, reflectDamage); - } - } if (damage <= 0) { @@ -3871,6 +3841,15 @@ void Player::changeSoul(int32_t soulChange) sendStats(); } +void Player::changeStamina(int32_t amount) +{ + if (amount > 0) { + staminaMinutes += std::min(amount, 2520 - staminaMinutes); + } else { + staminaMinutes = std::max(0, staminaMinutes + amount); + } +} + bool Player::canWear(uint32_t lookType, uint8_t addons) const { if (group->access) { @@ -4733,6 +4712,95 @@ size_t Player::getMaxDepotItems() const return g_config.getNumber(isPremium() ? ConfigManager::DEPOT_PREMIUM_LIMIT : ConfigManager::DEPOT_FREE_LIMIT); } +const bool Player::addAugment(std::shared_ptr& augment) { + if (std::find(augments.begin(), augments.end(), augment) == augments.end()) { + augments.push_back(augment); + return true; + } + return false; +} + +const bool Player::addAugment(std::string_view augmentName) { + + if (auto augment = Augments::GetAugment(augmentName)) { + augments.emplace_back(augment); + return true; + } + return false; +} + +const bool Player::removeAugment(std::shared_ptr& augment) { + auto it = std::find(augments.begin(), augments.end(), augment); + if (it != augments.end()) { + augments.erase(it); + return true; + } + return false; +} + +const bool Player::isAugmented() +{ + return augments.size() > 0; +} + +const bool Player::hasAugment(const std::string_view augmentName, bool checkItems) +{ + for (const auto& augment : augments) { + if (augment->getName() == augmentName) { + return true; + } + } + + if (checkItems) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + for (const auto& aug : item->getAugments()) { + if (aug->getName() == augmentName) { + return true; + } + } + } + } + + return false; +} + +const bool Player::hasAugment(const std::shared_ptr& augment, bool checkItems) +{ + for (const auto& aug : augments) { + if (aug == augment) { + return true; + } + } + + if (checkItems) { + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + for (const auto& aug : item->getAugments()) { + if (aug == augment) { + return true; + } + } + } + } + + return false; +} + +const bool Player::removeAugment(std::string_view augmentName) { + auto it = std::find_if(augments.begin(), augments.end(), + [&](const std::shared_ptr& augment) { + return augment->getName() == augmentName; + }); + + if (it != augments.end()) { + augments.erase(it); + return true; + } + return false; +} + + std::forward_list Player::getMuteConditions() const { std::forward_list muteConditions; @@ -4992,7 +5060,6 @@ void Player::removeImbuementEffect(std::shared_ptr imbue) { void Player::addImbuementEffect(std::shared_ptr imbue) { - if (imbue->isSkill()) { switch (imbue->imbuetype) { case ImbuementType::IMBUEMENT_TYPE_FIST_SKILL: @@ -5052,3 +5119,255 @@ void Player::addImbuementEffect(std::shared_ptr imbue) { sendSkills(); sendStats(); } + +static ModifierTotals getValidatedTotals(const std::vector> modifierList, const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) { + auto percent = 0; + auto flat = 0; + // to-do: const and auto& + for (auto modifier : modifierList) { + if (modifier->appliesToDamage(damageType) && modifier->appliesToOrigin(originType) && modifier->appliesToTarget(creatureType, race, creatureName)) { + if (modifier->isFlatValue() && modifier->getChance() == 0 || modifier->isFlatValue() && modifier->getChance() == 100) { + flat += modifier->getValue(); + continue; + } else if (modifier->isFlatValue()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + flat += modifier->getValue(); + continue; + } + } + + if (modifier->isPercent() && modifier->getChance() == 0 || modifier->isPercent() && modifier->getChance() == 100) { + percent += modifier->getValue(); + continue; + } else if (modifier->isPercent()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + percent += modifier->getValue(); + continue; + } + } + } + } + percent = std::clamp(percent, 0, 100); + return ModifierTotals(flat, percent); +} + +std::unordered_map >> Player::getAttackModifiers() { + std::unordered_map>> modifierMap; + + if (!augments.empty()) { + for (auto aug : augments) { + for (auto& mod : aug->getAttackModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } + } + + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + if (item) { + for (auto aug : item->getAugments()) { + for (auto& mod : aug->getAttackModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } + } + } + + return modifierMap; +} + +std::unordered_map >> Player::getDefenseModifiers() { + std::unordered_map>> modifierMap; + + if (!augments.empty()) { + for (auto aug : augments) { + for (auto& mod : aug->getDefenseModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } + } + + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + if (item) { + for (auto aug : item->getAugments()) { + for (auto& mod : aug->getDefenseModifiers()) { + modifierMap[mod->getType()].emplace_back(mod); + } + } + } + } + + return modifierMap; +} + +std::unordered_map Player::getConvertedTotals(const uint8_t modType, const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) +{ + std::unordered_map conversionList; + conversionList.reserve(COMBAT_COUNT); + + // Note: Passing incorrect 'modType' param to this method can break it! + // Only two modTypes that should be used are : + // ATTACK_MODIFIER_CONVERSION + // DEFENSE_MODIFIER_REFORM + // Their current values are 7 and 9, if you pass anything else, this method breaks. + + if (!augments.empty()) { + for (const auto& aug : augments) { + const auto& modifiers = aug->getAttackModifiers(modType); + for (const auto& modifier : modifiers) { + if (modifier->appliesToDamage(damageType) && modifier->appliesToOrigin(originType) && modifier->appliesToTarget(creatureType, race, creatureName)) { + + auto flat = 0; + auto percent = 0; + + if (modifier->isFlatValue() && modifier->getChance() == 0 || modifier->isFlatValue() && modifier->getChance() == 100) { + flat += modifier->getValue(); + continue; + } else if (modifier->isFlatValue()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + flat += modifier->getValue(); + continue; + } + } + + if (modifier->isPercent() && modifier->getChance() == 0 || modifier->isPercent() && modifier->getChance() == 100) { + percent += modifier->getValue(); + continue; + } else if (modifier->isPercent()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + percent += modifier->getValue(); + continue; + } + } + auto& entry = conversionList[modifier->conversionType()]; + entry.flatTotal += flat; + entry.percentTotal += percent; + entry.percentTotal = std::clamp(entry.percentTotal, 0, 100); + } + } + } + } + + for (int32_t slot = CONST_SLOT_FIRST; slot <= CONST_SLOT_LAST; ++slot) { + Item* item = inventory[slot]; + if (item && !item->getAugments().empty()) { + for (const auto& aug : item->getAugments()) { + const auto& modifiers = aug->getAttackModifiers(modType); + for (const auto& modifier : modifiers) { + if (modifier->appliesToDamage(damageType) && modifier->appliesToOrigin(originType) && modifier->appliesToTarget(creatureType, race, creatureName)) { + + auto flat = 0; + auto percent = 0; + + if (modifier->isFlatValue() && modifier->getChance() == 0 || modifier->isFlatValue() && modifier->getChance() == 100) { + flat += modifier->getValue(); + continue; + } else if (modifier->isFlatValue()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + flat += modifier->getValue(); + continue; + } + } + + if (modifier->isPercent() && modifier->getChance() == 0 || modifier->isPercent() && modifier->getChance() == 100) { + percent += modifier->getValue(); + continue; + } else if (modifier->isPercent()) { + if (modifier->getChance() >= uniform_random(1, 100)) { + percent += modifier->getValue(); + continue; + } + } + auto& entry = conversionList[modifier->conversionType()]; + entry.flatTotal += flat; + entry.percentTotal += percent; + entry.percentTotal = std::clamp(entry.percentTotal, 0, 100); + } + } + } + } + } + return conversionList; +} + +std::unordered_map Player::getAttackModifierTotals(const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName) { + + std::unordered_map modMap; + modMap.reserve(ATTACK_MODIFIER_LAST); + + // To-do : Implement a cache for this, so we don't do this search on every single combat, always. + // May require a multi-map approach of storing augments, and getting rid of the vectors. + auto attackMods = getAttackModifiers(); + for (uint8_t i = ATTACK_MODIFIER_NONE; i < ATTACK_MODIFIER_LAST; ++i) { + auto modTotals = getValidatedTotals(attackMods[i], damageType, originType, creatureType, race, creatureName); + modMap.try_emplace(i, modTotals); + } + return modMap; +} + +std::unordered_map Player::getDefenseModifierTotals(const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, std::string_view creatureName) { + + std::unordered_map modMap; + modMap.reserve(DEFENSE_MODIFIER_LAST); + + // To-do : Implement a cache for this, so we don't do this search on every single combat, always. + // May require a multi-map approach of storing augments, and getting rid of the vectors. + auto defenseMods = getDefenseModifiers(); + for (uint8_t i = DEFENSE_MODIFIER_NONE; i < DEFENSE_MODIFIER_LAST; ++i) { + auto modTotals = getValidatedTotals(defenseMods[i], damageType, originType, creatureType, race, creatureName); + modMap.try_emplace(i, modTotals); + } + return modMap; +} + +// To-do : Add distance effects to the following three methods +void Player::reflectDamage(Creature& target, CombatType_t dmgType, uint8_t amount) { + // To-do : animations? + auto reflect = CombatDamage{}; + reflect.primary.type = dmgType; + reflect.primary.value = amount; + reflect.origin = ORIGIN_AUGMENT; + std::cout << "Reflect Activated! \n"; + g_game.combatChangeHealth(this, &target, reflect); +} + +void Player::deflectDamage(CombatType_t dmgType, uint8_t amount) { + // To-do : animations? + auto deflect = CombatDamage{}; + deflect.primary.type = dmgType; + deflect.origin = ORIGIN_AUGMENT; + SpectatorVec spectators; + std::cout << "Deflect Activated! \n"; + // To-do : Make ranges configurable here + g_game.map.getSpectators(spectators, position, false, false, 3, 3, 3, 3); + uint8_t targetNumber = spectators.size(); + deflect.primary.value = std::round( amount / targetNumber ); + for (auto& target : spectators) { + // I think skipping combat::doDamage here allows us + // to keep from looping over the same damage over and over + g_game.combatChangeHealth(this, target, deflect); + } +} + +void Player::ricochetDamage(CombatType_t dmgType, uint8_t amount) { + // To-do : animations? + auto ricochet = CombatDamage{}; + ricochet.primary.type = dmgType; + ricochet.primary.value += amount; + ricochet.origin = ORIGIN_AUGMENT; + + std::cout << "Ricochet Activated! \n"; + SpectatorVec spectators; + // To-do : Make ranges configurable here + g_game.map.getSpectators(spectators, position, false, false, 5, 5, 5, 5); + + auto target = spectators[uniform_random(0, spectators.size())]; + + // I think skipping combat::doDamage here allows us + // to keep from looping over the same damage over and over + if (target) { + std::cout << "Ricochet Found Target! \n"; + g_game.combatChangeHealth(this, target, ricochet); + } +} diff --git a/src/player.h b/src/player.h index 4e00b58f..cf6dbfe8 100644 --- a/src/player.h +++ b/src/player.h @@ -21,8 +21,10 @@ #include "mounts.h" #include "storeinbox.h" #include "rewardchest.h" +#include "augments.h" #include +#include "augment.h" class House; class NetworkMessage; @@ -598,6 +600,7 @@ class Player final : public Creature, public Cylinder void changeHealth(int32_t healthChange, bool sendHealthChange = true) override; void changeMana(int32_t manaChange); void changeSoul(int32_t soulChange); + void changeStamina(int32_t amount); bool isPzLocked() const { return pzLocked; @@ -1182,11 +1185,35 @@ class Player final : public Creature, public Cylinder bool hasLearnedInstantSpell(const std::string& spellName) const; void updateRegeneration(); + void addItemImbuements(Item* item); void removeItemImbuements(Item* item); void addImbuementEffect(std::shared_ptr imbue); void removeImbuementEffect(std::shared_ptr imbue); + // To-do : Make all these methods into const + std::unordered_map>> getAttackModifiers(); + std::unordered_map>> getDefenseModifiers(); + + std::unordered_map getConvertedTotals(const uint8_t modType, const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName); + + std::unordered_map getAttackModifierTotals(const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName); + std::unordered_map getDefenseModifierTotals(const CombatType_t damageType, const CombatOrigin originType, const CreatureType_t creatureType, const RaceType_t race, const std::string_view creatureName); + + const bool addAugment(std::string_view augmentName); + const bool addAugment(std::shared_ptr& augment); + + const bool removeAugment(std::string_view augmentName); + const bool removeAugment(std::shared_ptr& augment); + + const bool isAugmented(); + const bool hasAugment(const std::string_view augmentName, const bool checkItems); + const bool hasAugment(const std::shared_ptr& augmentName, const bool checkItems); + + void reflectDamage(Creature& target, CombatType_t dmgType, uint8_t amount); + void deflectDamage(const CombatType_t dmgType, const uint8_t amount); /* future feature, add configurable target ammounts via modifier.aux_attribute */ + void ricochetDamage(const CombatType_t dmgType, const uint8_t amount); /* future feature, add configurable target ammounts via modifier.aux_attribute */ + private: std::forward_list getMuteConditions() const; @@ -1240,6 +1267,8 @@ class Player final : public Creature, public Cylinder std::map depotChests; std::map storageMap; + std::vector> augments; + std::vector outfits; GuildWarVector guildWarVector; diff --git a/src/protocolgame.cpp b/src/protocolgame.cpp index ee4c005d..cabbf78d 100644 --- a/src/protocolgame.cpp +++ b/src/protocolgame.cpp @@ -1948,29 +1948,6 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId) msg.add(0x00); } - if (it.abilities) { - std::ostringstream ss; - bool separator = false; - - for (size_t i = 0; i < COMBAT_COUNT; ++i) { - if (it.abilities->absorbPercent[i] == 0) { - continue; - } - - if (separator) { - ss << ", "; - } else { - separator = true; - } - - ss << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%'; - } - - msg.addString(ss.str()); - } else { - msg.add(0x00); - } - if (it.minReqLevel != 0) { msg.addString(std::to_string(it.minReqLevel)); } else { diff --git a/src/spectators.h b/src/spectators.h index 270bc133..bfce128a 100644 --- a/src/spectators.h +++ b/src/spectators.h @@ -37,6 +37,10 @@ class SpectatorVec vec.pop_back(); } + Creature* operator[] (uint8_t index) { + return vec[index]; + } + size_t size() const { return vec.size(); } bool empty() const { return vec.empty(); } Iterator begin() { return vec.begin(); } diff --git a/vcpkg.json b/vcpkg.json index fed26a29..22ae7e28 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -8,6 +8,7 @@ "boost-system", "boost-variant", "fmt", + "tomlplusplus", { "name": "libiconv", "platform": "osx"