From e0e7ab0d1d2c23eadcaabac907fce2eaecffa933 Mon Sep 17 00:00:00 2001 From: Pedro Cruz Date: Mon, 13 May 2024 18:35:04 -0300 Subject: [PATCH] feat: augments (#2602) --- config.lua.dist | 14 ++- data/items/items.xml | 105 +++++++++++++++++++ src/config/config_enums.hpp | 5 +- src/config/configmanager.cpp | 3 + src/creatures/combat/combat.cpp | 4 + src/creatures/combat/spells.cpp | 41 +++++++- src/creatures/combat/spells.hpp | 3 + src/creatures/players/player.cpp | 29 +++++ src/creatures/players/player.hpp | 6 ++ src/enums/item_attribute.hpp | 1 + src/items/functions/item/item_parse.cpp | 53 ++++++++++ src/items/functions/item/item_parse.hpp | 8 ++ src/items/item.cpp | 12 +++ src/items/item.hpp | 29 +++++ src/items/items.cpp | 41 ++++++++ src/items/items.hpp | 22 ++++ src/items/items_definitions.hpp | 21 ++++ src/server/network/protocol/protocolgame.cpp | 7 +- src/utils/tools.cpp | 16 +++ src/utils/tools.hpp | 1 + 20 files changed, 417 insertions(+), 4 deletions(-) diff --git a/config.lua.dist b/config.lua.dist index b731fadbcd7..ed2561d4532 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -52,7 +52,8 @@ cleanProtectionZones = false -- Connection Config -- NOTE: allowOldProtocol can allow login on 10x protocol. (11.00) -- NOTE: maxPlayers set to 0 means no limit --- NOTE: MaxPacketsPerSeconds if you change you will be subject to bugs by WPE, keep the default value of 25 +-- NOTE: MaxPacketsPerSeconds if you change you will be subject to bugs by WPE, keep the default value of 25, +-- It's recommended to use a range like min 50 in this function, otherwise you will be disconnected after equipping two-handed distance weapons. ip = "127.0.0.1" allowOldProtocol = false bindOnlyGlobalAddress = false @@ -80,6 +81,17 @@ freeDepotLimit = 2000 premiumDepotLimit = 10000 depotBoxes = 20 +-- Augments System (Get more info in: https://github.com/opentibiabr/canary/pull/2602) +-- NOTE: the following values are for all weapons and equipments that have type of "increase damage", "powerful impact" and "strong impact". +-- To customize the percentage of a particular item with these augment types, please add to the item "augments" section on items.xml as the example above. +-- NOTE: The values represent percentage. +-- NOTE: augmentIncreasedDamagePercent = value between 1 and 100 (damage percent to increase. ex: 5 = 5%, 50 = 50%) +-- NOTE: augmentPowerfulImpactPercent = value between 1 and 100 (damage percent to increase. ex: 10 = 10%, 100 = 100%) +-- NOTE: augmentStrongImpactPercent = value between 1 and 100 (damage percent to increase. ex: 7 = 7%, 70 = 70%) +augmentIncreasedDamagePercent = 5 +augmentPowerfulImpactPercent = 7 +augmentStrongImpactPercent = 10 + -- Prey system -- NOTE: preyRerollPricePerLevel: Price multiplier in gold coin for rerolling prey list. -- NOTE: preySelectListPrice: Price to manually select creature on list and to lock prey slot. diff --git a/data/items/items.xml b/data/items/items.xml index 0858df99219..45cde62bd9d 100644 --- a/data/items/items.xml +++ b/data/items/items.xml @@ -75009,6 +75009,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75032,6 +75035,12 @@ Granted by TibiaGoals.com"/> + + + + + + @@ -75055,6 +75064,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75078,6 +75090,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75101,6 +75116,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75124,6 +75142,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75149,6 +75170,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75173,6 +75197,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75197,6 +75224,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75221,6 +75251,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75245,6 +75278,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75269,6 +75305,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75285,6 +75324,14 @@ Granted by TibiaGoals.com"/> + + + + + + + + @@ -75311,6 +75358,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75338,6 +75388,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75365,6 +75418,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75392,6 +75448,9 @@ Granted by TibiaGoals.com"/> + + + @@ -75407,6 +75466,14 @@ Granted by TibiaGoals.com"/> + + + + + + + + @@ -75430,6 +75497,10 @@ Granted by TibiaGoals.com"/> + + + + @@ -75459,6 +75530,10 @@ Granted by TibiaGoals.com"/> + + + + @@ -75483,6 +75558,17 @@ Granted by TibiaGoals.com"/> + + + + + + + + + + + @@ -75506,6 +75592,10 @@ Granted by TibiaGoals.com"/> + + + + @@ -75535,6 +75625,10 @@ Granted by TibiaGoals.com"/> + + + + @@ -75559,6 +75653,17 @@ Granted by TibiaGoals.com"/> + + + + + + + + + + + diff --git a/src/config/config_enums.hpp b/src/config/config_enums.hpp index 4abf29c04cb..30a4b172af1 100644 --- a/src/config/config_enums.hpp +++ b/src/config/config_enums.hpp @@ -17,6 +17,9 @@ enum ConfigKey_t : uint16_t { ALLOW_BLOCK_SPAWN, ALLOW_CHANGEOUTFIT, ALLOW_RELOAD, + AUGMENT_INCREASED_DAMAGE_PERCENT, + AUGMENT_POWERFUL_IMPACT_PERCENT, + AUGMENT_STRONG_IMPACT_PERCENT, AUTH_TYPE, AUTOBANK, AUTOLOOT, @@ -324,5 +327,5 @@ enum ConfigKey_t : uint16_t { WHEEL_POINTS_PER_LEVEL, WHITE_SKULL_TIME, WORLD_TYPE, - XP_DISPLAY_MODE, + XP_DISPLAY_MODE }; diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 8d4ba7a77da..0525c5a560a 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -345,6 +345,9 @@ bool ConfigManager::load() { loadIntConfig(L, WHEEL_ATELIER_ROTATE_REGULAR_COST, "wheelAtelierRotateRegularCost", 250000); loadIntConfig(L, WHEEL_POINTS_PER_LEVEL, "wheelPointsPerLevel", 1); loadIntConfig(L, WHITE_SKULL_TIME, "whiteSkullTime", 15 * 60 * 1000); + loadIntConfig(L, AUGMENT_INCREASED_DAMAGE_PERCENT, "augmentIncreasedDamagePercent", 5); + loadIntConfig(L, AUGMENT_POWERFUL_IMPACT_PERCENT, "augmentPowerfulImpactPercent", 10); + loadIntConfig(L, AUGMENT_STRONG_IMPACT_PERCENT, "augmentStrongImpactPercent", 7); loadStringConfig(L, CORE_DIRECTORY, "coreDirectory", "data"); loadStringConfig(L, DATA_DIRECTORY, "dataPackDirectory", "data-otservbr-global"); diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 56cca63fe56..06812095cbf 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -98,7 +98,11 @@ CombatDamage Combat::getCombatDamage(std::shared_ptr creature, std::sh } } } + if (attackerPlayer && wheelSpell && wheelSpell->isInstant()) { + wheelSpell->getCombatDataAugment(attackerPlayer, damage); + } } + return damage; } diff --git a/src/creatures/combat/spells.cpp b/src/creatures/combat/spells.cpp index 60cc5e7d2a9..3aa445b34aa 100644 --- a/src/creatures/combat/spells.cpp +++ b/src/creatures/combat/spells.cpp @@ -630,6 +630,43 @@ void Spell::setWheelOfDestinyBoost(WheelSpellBoost_t boost, WheelSpellGrade_t gr } } +void Spell::getCombatDataAugment(std::shared_ptr player, CombatDamage &damage) { + if (!(damage.instantSpellName).empty()) { + const auto equippedAugmentItems = player->getEquippedAugmentItems(); + for (const auto &item : equippedAugmentItems) { + const auto augments = item->getAugmentsBySpellName(damage.instantSpellName); + for (auto &augment : augments) { + if (augment->value == 0) { + continue; + } + if (augment->type == Augment_t::IncreasedDamage || augment->type == Augment_t::PowerfulImpact || augment->type == Augment_t::StrongImpact) { + const float augmentPercent = augment->value / 100.0; + damage.primary.value += static_cast(damage.primary.value * augmentPercent); + damage.secondary.value += static_cast(damage.secondary.value * augmentPercent); + } else if (augment->type != Augment_t::Cooldown) { + const int32_t augmentValue = augment->value * 100; + damage.lifeLeech += augment->type == Augment_t::LifeLeech ? augmentValue : 0; + damage.manaLeech += augment->type == Augment_t::ManaLeech ? augmentValue : 0; + damage.criticalDamage += augment->type == Augment_t::CriticalExtraDamage ? augmentValue : 0; + } + } + } + } +}; + +int32_t Spell::calculateAugmentSpellCooldownReduction(std::shared_ptr player) const { + int32_t spellCooldown = 0; + const auto equippedAugmentItems = player->getEquippedAugmentItemsByType(Augment_t::Cooldown); + for (const auto &item : equippedAugmentItems) { + const auto augments = item->getAugmentsBySpellNameAndType(getName(), Augment_t::Cooldown); + for (auto &augment : augments) { + spellCooldown += augment->value; + } + } + + return spellCooldown; +} + void Spell::applyCooldownConditions(std::shared_ptr player) const { WheelSpellGrade_t spellGrade = player->wheel()->getSpellUpgrade(getName()); bool isUpgraded = getWheelOfDestinyUpgraded() && static_cast(spellGrade) > 0; @@ -644,8 +681,10 @@ void Spell::applyCooldownConditions(std::shared_ptr player) const { if (isUpgraded) { spellCooldown -= getWheelOfDestinyBoost(WheelSpellBoost_t::COOLDOWN, spellGrade); } - g_logger().debug("[{}] spell name: {}, spellCooldown: {}, bonus: {}", __FUNCTION__, name, spellCooldown, player->wheel()->getSpellBonus(name, WheelSpellBoost_t::COOLDOWN)); + int32_t augmentCooldownReduction = calculateAugmentSpellCooldownReduction(player); + g_logger().debug("[{}] spell name: {}, spellCooldown: {}, bonus: {}, augment {}", __FUNCTION__, name, spellCooldown, player->wheel()->getSpellBonus(name, WheelSpellBoost_t::COOLDOWN), augmentCooldownReduction); spellCooldown -= player->wheel()->getSpellBonus(name, WheelSpellBoost_t::COOLDOWN); + spellCooldown -= augmentCooldownReduction; if (spellCooldown > 0) { std::shared_ptr condition = Condition::createCondition(CONDITIONID_DEFAULT, CONDITION_SPELLCOOLDOWN, spellCooldown / rateCooldown, 0, false, m_spellId); player->addCondition(condition); diff --git a/src/creatures/combat/spells.hpp b/src/creatures/combat/spells.hpp index cd1a7aaa623..dce7b3caba4 100644 --- a/src/creatures/combat/spells.hpp +++ b/src/creatures/combat/spells.hpp @@ -345,6 +345,9 @@ class Spell : public BaseSpell { m_separator = newSeparator.data(); } + void getCombatDataAugment(std::shared_ptr player, CombatDamage &damage); + int32_t calculateAugmentSpellCooldownReduction(std::shared_ptr player) const; + protected: void applyCooldownConditions(std::shared_ptr player) const; bool playerSpellCheck(std::shared_ptr player) const; diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 925086f2b53..78478953ade 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -4115,6 +4115,35 @@ std::vector> Player::getAllInventoryItems(bool ignoreEquip return itemVector; } +std::vector> Player::getEquippedAugmentItemsByType(Augment_t augmentType) const { + std::vector> equippedAugmentItemsByType; + const auto equippedAugmentItems = getEquippedItems(); + + for (const auto &item : equippedAugmentItems) { + for (auto &augment : item->getAugments()) { + if (augment->type == augmentType) { + equippedAugmentItemsByType.push_back(item); + } + } + } + + return equippedAugmentItemsByType; +} + +std::vector> Player::getEquippedAugmentItems() const { + std::vector> equippedAugmentItems; + const auto equippedItems = getEquippedItems(); + + for (const auto &item : equippedItems) { + if (item->getAugments().size() < 1) { + continue; + } + equippedAugmentItems.push_back(item); + } + + return equippedAugmentItems; +} + std::vector> Player::getEquippedItems() const { std::vector valid_slots { CONST_SLOT_HEAD, diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index da4c0cec9e8..b3bdd875b20 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -2601,6 +2601,12 @@ class Player final : public Creature, public Cylinder, public Bankable { // This get all blessings phmap::flat_hash_map getBlessingNames() const; + // Gets the equipped items with augment by type + std::vector> getEquippedAugmentItemsByType(Augment_t augmentType) const; + + // Gets the equipped items with augment + std::vector> getEquippedAugmentItems() const; + /** * @brief Get the equipped items of the player-> * @details This function returns a vector containing the items currently equipped by the player diff --git a/src/enums/item_attribute.hpp b/src/enums/item_attribute.hpp index adc49c11a19..be7b7445710 100644 --- a/src/enums/item_attribute.hpp +++ b/src/enums/item_attribute.hpp @@ -46,6 +46,7 @@ enum ItemAttribute_t : uint64_t { LOOTMESSAGE_SUFFIX = 33, STORE_INBOX_CATEGORY = 34, OBTAINCONTAINER = 35, + AUGMENTS = 36, }; enum ItemDecayState_t : uint8_t { diff --git a/src/items/functions/item/item_parse.cpp b/src/items/functions/item/item_parse.cpp index 2d1ba9301c5..1f247b611bc 100644 --- a/src/items/functions/item/item_parse.cpp +++ b/src/items/functions/item/item_parse.cpp @@ -68,6 +68,7 @@ void ItemParse::initParse(const std::string &tmpStrValue, pugi::xml_node attribu ItemParse::parseWalk(tmpStrValue, valueAttribute, itemType); ItemParse::parseAllowDistanceRead(tmpStrValue, valueAttribute, itemType); ItemParse::parseImbuement(tmpStrValue, attributeNode, valueAttribute, itemType); + ItemParse::parseAugment(tmpStrValue, attributeNode, valueAttribute, itemType); ItemParse::parseStackSize(tmpStrValue, valueAttribute, itemType); ItemParse::parseSpecializedMagicLevelPoint(tmpStrValue, valueAttribute, itemType); ItemParse::parseMagicShieldCapacity(tmpStrValue, valueAttribute, itemType); @@ -870,6 +871,58 @@ void ItemParse::parseImbuement(const std::string &tmpStrValue, pugi::xml_node at } } +void ItemParse::parseAugment(const std::string &tmpStrValue, pugi::xml_node attributeNode, pugi::xml_attribute valueAttribute, ItemType &itemType) { + if (tmpStrValue != "augments") { + return; + } + + // Check if the augments value is 1 or 0 (1 = enable - 0 = disable) + if (valueAttribute.as_bool()) { + for (const auto subAttributeNode : attributeNode.children()) { + const pugi::xml_attribute subKeyAttribute = subAttributeNode.attribute("key"); + if (!subKeyAttribute) { + continue; + } + + const pugi::xml_attribute subValueAttribute = subAttributeNode.attribute("value"); + if (!subValueAttribute) { + continue; + } + + const auto &augmentEnum = magic_enum::enum_cast(toPascalCase(subValueAttribute.as_string())); + if (augmentEnum.has_value()) { + const Augment_t augmentType = augmentEnum.value(); + g_logger().trace("[ParseAugment::initParseAugment] - Item '{}' has an augment '{}'", itemType.name, subValueAttribute.as_string()); + int32_t augmentValue = 0; + const bool hasValueDescrition = isAugmentWithoutValueDescription(augmentType); + + if (hasValueDescrition) { + const auto it = AugmentWithoutValueDescriptionDefaultKeys.find(augmentType); + if (it != AugmentWithoutValueDescriptionDefaultKeys.end()) { + augmentValue = g_configManager().getNumber(it->second, __FUNCTION__); + } + } + + const auto augmentName = asLowerCaseString(subKeyAttribute.as_string()); + const pugi::xml_object_range augmentValueAttributeNode = subAttributeNode.children(); + if (!augmentValueAttributeNode.empty()) { + const pugi::xml_node augmentValueNode = *augmentValueAttributeNode.begin(); + const pugi::xml_attribute augmentValueAttribute = augmentValueNode.attribute("value"); + augmentValue = augmentValueAttribute ? pugi::cast(augmentValueAttribute.value()) : augmentValue; + } else if (!hasValueDescrition) { + g_logger().warn("[{}] - Item '{}' has an augment '{}' without a value", __FUNCTION__, itemType.name, augmentName); + } + + if (augmentType != Augment_t::None) { + itemType.addAugment(augmentName, augmentType, augmentValue); + } + } else { + g_logger().warn("[{}] - Unknown type '{}'", __FUNCTION__, subValueAttribute.as_string()); + } + } + } +} + void ItemParse::parseStackSize(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType) { std::string stringValue = tmpStrValue; if (stringValue == "stacksize") { diff --git a/src/items/functions/item/item_parse.hpp b/src/items/functions/item/item_parse.hpp index 7859a77fe9c..f63b6bc0689 100644 --- a/src/items/functions/item/item_parse.hpp +++ b/src/items/functions/item/item_parse.hpp @@ -158,6 +158,7 @@ const phmap::flat_hash_map ItemParseAttribut { "primarytype", ITEM_PARSE_PRIMARYTYPE }, { "usedbyhouseguests", ITEM_PARSE_USEDBYGUESTS }, { "script", ITEM_PARSE_SCRIPT }, + { "augments", ITEM_PARSE_AUGMENT } }; const phmap::flat_hash_map ItemTypesMap = { @@ -247,6 +248,12 @@ const phmap::flat_hash_map ImbuementsTypeMap = { { "increase capacity", IMBUEMENT_INCREASE_CAPACITY } }; +const phmap::flat_hash_map AugmentWithoutValueDescriptionDefaultKeys = { + { Augment_t::IncreasedDamage, AUGMENT_INCREASED_DAMAGE_PERCENT }, + { Augment_t::PowerfulImpact, AUGMENT_POWERFUL_IMPACT_PERCENT }, + { Augment_t::StrongImpact, AUGMENT_STRONG_IMPACT_PERCENT }, +}; + class ItemParse : public Items { public: static void initParse(const std::string &tmpStrValue, pugi::xml_node attributeNode, pugi::xml_attribute valueAttribute, ItemType &itemType); @@ -304,6 +311,7 @@ class ItemParse : public Items { static void parseWalk(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseAllowDistanceRead(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseImbuement(const std::string &tmpStrValue, pugi::xml_node attributeNode, pugi::xml_attribute valueAttribute, ItemType &itemType); + static void parseAugment(const std::string &tmpStrValue, pugi::xml_node attributeNode, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseStackSize(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseSpecializedMagicLevelPoint(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); static void parseMagicShieldCapacity(const std::string &tmpStrValue, pugi::xml_attribute valueAttribute, ItemType &itemType); diff --git a/src/items/item.cpp b/src/items/item.cpp index 81d24a790e7..104efc7f925 100644 --- a/src/items/item.cpp +++ b/src/items/item.cpp @@ -1437,6 +1437,11 @@ Item::getDescriptions(const ItemType &it, std::shared_ptr item /*= nullptr } } + std::string augmentsDescription = parseAugmentDescription(item, true); + if (!augmentsDescription.empty()) { + descriptions.emplace_back("Augments", augmentsDescription); + } + if (it.isKey()) { ss.str(""); ss << fmt::format("{:04}", item->getAttribute(ItemAttribute_t::ACTIONID)); @@ -1789,6 +1794,11 @@ Item::getDescriptions(const ItemType &it, std::shared_ptr item /*= nullptr descriptions.emplace_back("Imbuement Slots", std::to_string(it.imbuementSlot)); } + std::string augmentsDescription = it.parseAugmentDescription(true); + if (!augmentsDescription.empty()) { + descriptions.emplace_back("Augments", augmentsDescription); + } + if (it.isKey()) { ss.str(""); ss << fmt::format("{:04}", 0); @@ -3017,6 +3027,8 @@ std::string Item::getDescription(const ItemType &it, int32_t lookDistance, std:: s << '.'; } + s << parseAugmentDescription(item); + s << parseImbuementDescription(item); s << parseClassificationDescription(item); diff --git a/src/items/item.hpp b/src/items/item.hpp index 9ff0d3d15aa..a9774c902cd 100644 --- a/src/items/item.hpp +++ b/src/items/item.hpp @@ -291,6 +291,12 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { return isStoreItem() || hasOwner(); } + static std::string parseAugmentDescription(std::shared_ptr item, bool inspect = false) { + if (!item) { + return ""; + } + return items[item->getID()].parseAugmentDescription(inspect); + } static std::string parseImbuementDescription(std::shared_ptr item); static std::string parseShowDurationSpeed(int32_t speed, bool &begin); static std::string parseShowDuration(std::shared_ptr item); @@ -419,6 +425,29 @@ class Item : virtual public Thing, public ItemProperties, public SharedObject { } return items[id].extraDefense; } + std::vector> getAugments() const { + return items[id].augments; + } + std::vector> getAugmentsBySpellNameAndType(std::string spellName, Augment_t augmentType) const { + std::vector> augments; + for (auto &augment : items[id].augments) { + if (strcasecmp(augment->spellName.c_str(), spellName.c_str()) == 0 && augment->type == augmentType) { + augments.push_back(augment); + } + } + + return augments; + } + std::vector> getAugmentsBySpellName(std::string spellName) const { + std::vector> augments; + for (auto &augment : items[id].augments) { + if (strcasecmp(augment->spellName.c_str(), spellName.c_str()) == 0) { + augments.push_back(augment); + } + } + + return augments; + } uint8_t getImbuementSlot() const { if (hasAttribute(ItemAttribute_t::IMBUEMENT_SLOT)) { return getAttribute(ItemAttribute_t::IMBUEMENT_SLOT); diff --git a/src/items/items.cpp b/src/items/items.cpp index 9e31f5f105f..7d8ff0cf7c0 100644 --- a/src/items/items.cpp +++ b/src/items/items.cpp @@ -67,6 +67,47 @@ ItemTypes_t Items::getLootType(const std::string &strValue) { return ITEM_TYPE_NONE; } +const std::string Items::getAugmentNameByType(Augment_t augmentType) { + std::string augmentTypeName = magic_enum::enum_name(augmentType).data(); + augmentTypeName = toStartCaseWithSpace(augmentTypeName); + if (!isAugmentWithoutValueDescription(augmentType)) { + toLowerCaseString(augmentTypeName); + } + return augmentTypeName; +} + +std::string ItemType::parseAugmentDescription(bool inspect /*= false*/) const { + if (augments.empty()) { + return ""; + } + + std::vector descriptions; + for (const auto &augment : augments) { + descriptions.push_back(getFormattedAugmentDescription(augment)); + } + + if (inspect) { + return fmt::format("{}.", fmt::join(descriptions.begin(), descriptions.end(), ", ")); + } else { + return fmt::format("\nAugments: ({}).", fmt::join(descriptions.begin(), descriptions.end(), ", ")); + } +} + +std::string ItemType::getFormattedAugmentDescription(const std::shared_ptr &augmentInfo) const { + const std::string augmentName = Items::getAugmentNameByType(augmentInfo->type); + std::string augmentSpellNameCapitalized = augmentInfo->spellName; + capitalizeWordsIgnoringString(augmentSpellNameCapitalized, " of "); + + char signal = augmentInfo->value > 0 ? '-' : '+'; + + if (Items::isAugmentWithoutValueDescription(augmentInfo->type)) { + return fmt::format("{} -> {}", augmentSpellNameCapitalized, augmentName); + } else if (augmentInfo->type == Augment_t::Cooldown) { + return fmt::format("{} -> {}{}s {}", augmentSpellNameCapitalized, signal, augmentInfo->value / 1000, augmentName); + } + return fmt::format("{} -> {:+}% {}", augmentSpellNameCapitalized, augmentInfo->value, augmentName); +} + bool Items::reload() { clear(); loadFromProtobuf(); diff --git a/src/items/items.hpp b/src/items/items.hpp index f9e48ae1032..82af0659af4 100644 --- a/src/items/items.hpp +++ b/src/items/items.hpp @@ -252,6 +252,14 @@ class ItemType { return str; } + std::string parseAugmentDescription(bool inspect = false) const; + std::string getFormattedAugmentDescription(const std::shared_ptr &augmentInfo) const; + + void addAugment(std::string spellName, Augment_t augmentType, int32_t value) { + auto augmentInfo = std::make_shared(spellName, augmentType, value); + augments.emplace_back(augmentInfo); + } + void setImbuementType(ImbuementTypes_t imbuementType, uint16_t slotMaxTier) { imbuementTypes[imbuementType] = std::min(IMBUEMENT_MAX_TIER, slotMaxTier); } @@ -331,6 +339,8 @@ class ItemType { int8_t hitChance = 0; + std::vector> augments; + // 12.90 bool wearOut = false; bool clockExpire = false; @@ -435,6 +445,18 @@ class Items { return dummys; } + static const std::string getAugmentNameByType(Augment_t augmentType); + + static const bool isAugmentWithoutValueDescription(Augment_t augmentType) { + static std::vector vector = { + Augment_t::IncreasedDamage, + Augment_t::PowerfulImpact, + Augment_t::StrongImpact, + }; + + return std::find(vector.begin(), vector.end(), augmentType) != vector.end(); + } + private: std::vector items; std::vector ladders; diff --git a/src/items/items_definitions.hpp b/src/items/items_definitions.hpp index f044d4b5248..badac532951 100644 --- a/src/items/items_definitions.hpp +++ b/src/items/items_definitions.hpp @@ -270,6 +270,17 @@ enum ImbuementTypes_t : int64_t { IMBUEMENT_INCREASE_CAPACITY = 17 }; +enum class Augment_t : uint8_t { + None, + PowerfulImpact, + StrongImpact, + IncreasedDamage, + Cooldown, + CriticalExtraDamage, + LifeLeech, + ManaLeech +}; + enum class ContainerCategory_t : uint8_t { All, Ammunition, @@ -606,9 +617,19 @@ enum ItemParseAttributes_t { ITEM_PARSE_PRIMARYTYPE, ITEM_PARSE_USEDBYGUESTS, ITEM_PARSE_SCRIPT, + ITEM_PARSE_AUGMENT, }; struct ImbuementInfo { Imbuement* imbuement; uint32_t duration = 0; }; + +struct AugmentInfo { + AugmentInfo(std::string spellName, Augment_t type, int32_t value) : + spellName(std::move(spellName)), type(type), value(value) { } + + std::string spellName; + Augment_t type; + int32_t value; +}; diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index d924e0bfb0e..ab111fd3c8f 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -5717,7 +5717,12 @@ void ProtocolGame::sendMarketDetail(uint16_t itemId, uint8_t tier) { } if (!oldProtocol) { - msg.add(0x00); // Augment + std::string augmentsDescription = it.parseAugmentDescription(true); + if (!augmentsDescription.empty()) { + msg.addString(augmentsDescription, "ProtocolGame::sendMarketDetail - augmentsDescription"); + } else { + msg.add(0x00); // no augments + } } if (it.imbuementSlot > 0) { diff --git a/src/utils/tools.cpp b/src/utils/tools.cpp index 0d8b39a485d..b1fe671f416 100644 --- a/src/utils/tools.cpp +++ b/src/utils/tools.cpp @@ -1558,6 +1558,22 @@ void capitalizeWords(std::string &source) { } } +void capitalizeWordsIgnoringString(std::string &source, const std::string stringToIgnore) { + toLowerCaseString(source); + uint8_t size = (uint8_t)source.size(); + uint8_t indexFound = source.find(stringToIgnore); + for (uint8_t i = 0; i < size; i++) { + if (indexFound != std::string::npos && (i > indexFound - 1) && i < (indexFound + stringToIgnore.size())) { + continue; + } + if (i == 0) { + source[i] = (char)toupper(source[i]); + } else if (source[i - 1] == ' ' || source[i - 1] == '\'') { + source[i] = (char)toupper(source[i]); + } + } +} + /** * @details * Prevents the console from closing so there is time to read the error information diff --git a/src/utils/tools.hpp b/src/utils/tools.hpp index 769fb5d68d3..25683bf22bb 100644 --- a/src/utils/tools.hpp +++ b/src/utils/tools.hpp @@ -138,6 +138,7 @@ const char* getReturnMessage(ReturnValue value); void sleep_for(uint64_t ms); void capitalizeWords(std::string &source); +void capitalizeWordsIgnoringString(std::string &source, const std::string stringToIgnore); void consoleHandlerExit(); NameEval_t validateName(const std::string &name);