diff --git a/data/XML/outfits.xml b/data/XML/outfits.xml index 9cb5279e..9e69bf9c 100644 --- a/data/XML/outfits.xml +++ b/data/XML/outfits.xml @@ -1,5 +1,25 @@ + diff --git a/markdowns/CHANGELOG.md b/markdowns/CHANGELOG.md index a95e5a0a..00e70f7c 100644 --- a/markdowns/CHANGELOG.md +++ b/markdowns/CHANGELOG.md @@ -1,6 +1,26 @@ # Changelog for Crystal Server +## Version 4.1.3 + +### Features + +- Added new attributes to outfits. Check `outfits.xml` for examples. ([Tryller](https://github.com/jprzimba)) +- Added Vibrancy imbuement. ([pennaor](https://github.com/pennaor)) + +## Added files + +- notting + +## Modified files + +- data/XML/outfits.xml + +### Bug Fixes + +- notting + + ## Version 4.1.2 ### Features diff --git a/src/creatures/appearance/mounts/mounts.cpp b/src/creatures/appearance/mounts/mounts.cpp index 746f41d8..b171b735 100644 --- a/src/creatures/appearance/mounts/mounts.cpp +++ b/src/creatures/appearance/mounts/mounts.cpp @@ -59,10 +59,10 @@ bool Mounts::loadFromXml() { std::shared_ptr Mounts::getMountByID(uint8_t id) { auto it = std::find_if(mounts.begin(), mounts.end(), [id](const std::shared_ptr &mount) { - return mount->id == id; // Note the use of -> operator to access the members of the Mount object + return mount->id == id; }); - return it != mounts.end() ? *it : nullptr; // Returning the shared_ptr to the Mount object + return it != mounts.end() ? *it : nullptr; } std::shared_ptr Mounts::getMountByName(const std::string &name) { @@ -76,8 +76,8 @@ std::shared_ptr Mounts::getMountByName(const std::string &name) { std::shared_ptr Mounts::getMountByClientID(uint16_t clientId) { auto it = std::find_if(mounts.begin(), mounts.end(), [clientId](const std::shared_ptr &mount) { - return mount->clientId == clientId; // Note the use of -> operator to access the members of the Mount object + return mount->clientId == clientId; }); - return it != mounts.end() ? *it : nullptr; // Returning the shared_ptr to the Mount object + return it != mounts.end() ? *it : nullptr; } diff --git a/src/creatures/appearance/outfit/outfit.cpp b/src/creatures/appearance/outfit/outfit.cpp index ce7f28bf..2122d0f7 100644 --- a/src/creatures/appearance/outfit/outfit.cpp +++ b/src/creatures/appearance/outfit/outfit.cpp @@ -16,7 +16,8 @@ //////////////////////////////////////////////////////////////////////// #include "creatures/appearance/outfit/outfit.hpp" - +#include "creatures/combat/condition.hpp" +#include "creatures/creatures_definitions.hpp" #include "config/configmanager.hpp" #include "creatures/players/player.hpp" #include "game/game.hpp" @@ -76,17 +77,98 @@ bool Outfits::loadFromXml() { continue; } - outfits[type].emplace_back(std::make_shared( + auto outfit = std::make_shared( outfitNode.attribute("name").as_string(), - pugi::cast(lookTypeAttribute.value()), + outfitNode.attribute("from").as_string(), outfitNode.attribute("premium").as_bool(), outfitNode.attribute("unlocked").as_bool(true), - outfitNode.attribute("from").as_string() - )); + pugi::cast(lookTypeAttribute.value()) + ); + + outfit->manaShield = outfitNode.attribute("manaShield").as_bool() || outfitNode.attribute("manashield").as_bool(); + outfit->invisible = outfitNode.attribute("invisible").as_bool(); + outfit->speed = outfitNode.attribute("speed").as_int(); + outfit->attackSpeed = outfitNode.attribute("attackSpeed").as_int() || outfitNode.attribute("attackspeed").as_int(); + + if (auto healthGainAttr = outfitNode.attribute("healthGain")) { + outfit->healthGain = healthGainAttr.as_int(); + outfit->regeneration = true; + } + + if (auto healthTicksAttr = outfitNode.attribute("healthTicks")) { + outfit->healthTicks = healthTicksAttr.as_int(); + outfit->regeneration = true; + } + + if (auto manaGainAttr = outfitNode.attribute("manaGain")) { + outfit->manaGain = manaGainAttr.as_int(); + outfit->regeneration = true; + } + + if (auto manaTicksAttr = outfitNode.attribute("manaTicks")) { + outfit->manaTicks = manaTicksAttr.as_int(); + outfit->regeneration = true; + } + + if (auto skillsNode = outfitNode.child("skills")) { + for (auto skillNode : skillsNode.children()) { + std::string skillName = skillNode.name(); + int32_t skillValue = skillNode.attribute("value").as_int(); + + if (skillName == "fist") { + outfit->skills[SKILL_FIST] += skillValue; + } else if (skillName == "club") { + outfit->skills[SKILL_CLUB] += skillValue; + } else if (skillName == "axe") { + outfit->skills[SKILL_AXE] += skillValue; + } else if (skillName == "sword") { + outfit->skills[SKILL_SWORD] += skillValue; + } else if (skillName == "distance" || skillName == "dist") { + outfit->skills[SKILL_DISTANCE] += skillValue; + } else if (skillName == "shielding" || skillName == "shield") { + outfit->skills[SKILL_SHIELD] = skillValue; + } else if (skillName == "fishing" || skillName == "fish") { + outfit->skills[SKILL_FISHING] += skillValue; + } else if (skillName == "melee") { + outfit->skills[SKILL_FIST] += skillValue; + outfit->skills[SKILL_CLUB] += skillValue; + outfit->skills[SKILL_SWORD] += skillValue; + outfit->skills[SKILL_AXE] += skillValue; + } else if (skillName == "weapon" || skillName == "weapons") { + outfit->skills[SKILL_CLUB] += skillValue; + outfit->skills[SKILL_SWORD] += skillValue; + outfit->skills[SKILL_AXE] += skillValue; + outfit->skills[SKILL_DISTANCE] += skillValue; + } + } + + if (auto statsNode = outfitNode.child("stats")) { + for (auto statNode : statsNode.children()) { + std::string statName = statNode.name(); + int32_t statValue = statNode.attribute("value").as_int(); + + if (statName == "maxHealth") { + outfit->stats[STAT_MAXHITPOINTS] += statValue; + } else if (statName == "maxMana") { + outfit->stats[STAT_MAXMANAPOINTS] += statValue; + } else if (statName == "soul" || statName == "soulpoints") { + outfit->stats[STAT_SOULPOINTS] += statValue; + } else if (statName == "cap" || statName == "capacity") { + outfit->stats[STAT_CAPACITY] += statValue * 100; + } else if (statName == "magLevel" || statName == "magicLevel" || statName == "ml") { + outfit->stats[STAT_MAGICPOINTS] += statValue; + } + } + } + } + + outfits[type].emplace_back(outfit); } + for (uint8_t sex = PLAYERSEX_FEMALE; sex <= PLAYERSEX_LAST; ++sex) { outfits[sex].shrink_to_fit(); } + return true; } @@ -120,7 +202,7 @@ const std::vector> &Outfits::getOutfits(PlayerSex_t sex) return outfits[sex]; } -std::shared_ptr Outfits::getOutfitByName(PlayerSex_t sex, const std::string &name) const { +std::shared_ptr Outfits::getOutfitByName(PlayerSex_t sex, const std::string_view &name) const { for (const auto &outfit : outfits[sex]) { if (outfit->name == name) { return outfit; @@ -129,3 +211,138 @@ std::shared_ptr Outfits::getOutfitByName(PlayerSex_t sex, const std::str return nullptr; } + +uint32_t Outfits::getOutfitId(PlayerSex_t sex, uint16_t lookType) const { + for (const auto &outfit : outfits[sex]) { + if (outfit->lookType == lookType) { + return outfit->lookType; + } + } + + return 0; +} + +bool Outfits::addAttributes(uint32_t playerId, uint32_t outfitId, uint16_t sex, uint16_t addons) { + const auto &player = g_game().getPlayerByID(playerId); + if (!player) { + return false; + } + + auto &outfitsList = outfits[sex]; + auto it = std::ranges::find_if(outfitsList, [&outfitId](const auto &outfit) { + return outfit->lookType == outfitId; + }); + + if (it == outfitsList.end()) { + return false; + } + + const auto &outfit = *it; + + // Apply Conditions + if (outfit->manaShield) { + const auto &condition = Condition::createCondition(CONDITIONID_OUTFIT, CONDITION_MANASHIELD, -1, 0); + player->addCondition(condition); + } + + if (outfit->invisible) { + const auto &condition = Condition::createCondition(CONDITIONID_OUTFIT, CONDITION_INVISIBLE, -1, 0); + player->addCondition(condition); + } + + if (outfit->speed) { + g_game().changeSpeed(player, outfit->speed); + } + + if (outfit->regeneration) { + const auto &condition = Condition::createCondition(CONDITIONID_OUTFIT, CONDITION_REGENERATION, -1, 0); + if (outfit->healthGain) { + condition->setParam(CONDITION_PARAM_HEALTHGAIN, outfit->healthGain); + } + + if (outfit->healthTicks) { + condition->setParam(CONDITION_PARAM_HEALTHTICKS, outfit->healthTicks); + } + + if (outfit->manaGain) { + condition->setParam(CONDITION_PARAM_MANAGAIN, outfit->manaGain); + } + + if (outfit->manaTicks) { + condition->setParam(CONDITION_PARAM_MANATICKS, outfit->manaTicks); + } + + player->addCondition(condition); + } + + // Apply skills + for (uint32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (outfit->skills[i]) { + player->setVarSkill(static_cast(i), outfit->skills[i]); + } + } + + // Apply stats + for (uint32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (outfit->stats[s]) { + player->setVarStats(static_cast(s), outfit->stats[s]); + } + } + + player->sendStats(); + player->sendSkills(); + return true; +} + +bool Outfits::removeAttributes(uint32_t playerId, uint32_t outfitId, uint16_t sex) { + const auto &player = g_game().getPlayerByID(playerId); + if (!player) { + return false; + } + + auto &outfitsList = outfits[sex]; + auto it = std::ranges::find_if(outfitsList, [&outfitId](const auto &outfit) { + return outfit->lookType == outfitId; + }); + + if (it == outfitsList.end()) { + return false; + } + + const auto &outfit = *it; + + // Remove conditions + if (outfit->manaShield) { + player->removeCondition(CONDITION_MANASHIELD, CONDITIONID_OUTFIT); + } + + if (outfit->invisible) { + player->removeCondition(CONDITION_INVISIBLE, CONDITIONID_OUTFIT); + } + + if (outfit->speed) { + g_game().changeSpeed(player, -outfit->speed); + } + + if (outfit->regeneration) { + player->removeCondition(CONDITION_REGENERATION, CONDITIONID_OUTFIT); + } + + // Remove skills + for (uint32_t i = SKILL_FIRST; i <= SKILL_LAST; ++i) { + if (outfit->skills[i]) { + player->setVarSkill(static_cast(i), -outfit->skills[i]); + } + } + + // Remove stats + for (uint32_t s = STAT_FIRST; s <= STAT_LAST; ++s) { + if (outfit->stats[s]) { + player->setVarStats(static_cast(s), -outfit->stats[s]); + } + } + + player->sendStats(); + player->sendSkills(); + return true; +} diff --git a/src/creatures/appearance/outfit/outfit.hpp b/src/creatures/appearance/outfit/outfit.hpp index 167aa0b5..bdc82298 100644 --- a/src/creatures/appearance/outfit/outfit.hpp +++ b/src/creatures/appearance/outfit/outfit.hpp @@ -16,6 +16,7 @@ //////////////////////////////////////////////////////////////////////// #pragma once +#include "creatures/creatures_definitions.hpp" enum PlayerSex_t : uint8_t; class Player; @@ -29,14 +30,32 @@ struct OutfitEntry { }; struct Outfit { - Outfit(std::string initName, uint16_t initLookType, bool initPremium, bool initUnlocked, std::string initFrom) : - name(std::move(initName)), lookType(initLookType), premium(initPremium), unlocked(initUnlocked), from(std::move(initFrom)) { } + Outfit(std::string initName, std::string initFrom, bool initPremium, bool initUnlocked, uint16_t initLookType) : + name(std::move(initName)), from(std::move(initFrom)), premium(initPremium), unlocked(initUnlocked), lookType(initLookType) { + std::memset(skills, 0, sizeof(skills)); + std::memset(stats, 0, sizeof(stats)); + } - std::string name; - uint16_t lookType; - bool premium; - bool unlocked; - std::string from; + std::string name = ""; + std::string from = ""; + + bool premium = false; + bool unlocked = false; + bool manaShield = false; + bool invisible = false; + bool regeneration = false; + + uint16_t lookType = 0; + + int32_t speed = 0; + int32_t attackSpeed = 0; + int32_t healthGain = 0; + int32_t healthTicks = 0; + int32_t manaGain = 0; + int32_t manaTicks = 0; + + int32_t skills[SKILL_LAST + 1] = { 0 }; + int32_t stats[STAT_LAST + 1] = { 0 }; }; struct ProtocolOutfit { @@ -58,5 +77,9 @@ class Outfits { [[nodiscard]] std::shared_ptr getOutfitByLookType(const std::shared_ptr &player, uint16_t lookType, bool isOppositeOutfit = false) const; [[nodiscard]] const std::vector> &getOutfits(PlayerSex_t sex) const; - std::shared_ptr getOutfitByName(PlayerSex_t sex, const std::string &name) const; + std::shared_ptr getOutfitByName(PlayerSex_t sex, const std::string_view &name) const; + uint32_t getOutfitId(PlayerSex_t sex, uint16_t lookType) const; + + bool addAttributes(uint32_t playerId, uint32_t outfitId, uint16_t sex, uint16_t addons); + bool removeAttributes(uint32_t playerId, uint32_t outfitId, uint16_t sex); }; diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index 346fc27e..85c2a112 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -1017,6 +1017,16 @@ bool ConditionAttributes::setParam(ConditionParam_t param, int32_t value) { return true; } + case CONDITION_PARAM_STAT_SOULPOINTS: { + stats[STAT_SOULPOINTS] = value; + return true; + } + + case CONDITION_PARAM_STAT_SOULPOINTSPERCENT: { + statsPercent[STAT_SOULPOINTS] = std::max(0, value); + return true; + } + case CONDITION_PARAM_STAT_MAGICPOINTSPERCENT: { statsPercent[STAT_MAGICPOINTS] = std::max(0, value); return true; diff --git a/src/creatures/creatures_definitions.hpp b/src/creatures/creatures_definitions.hpp index cdaaac68..08cd9d5c 100644 --- a/src/creatures/creatures_definitions.hpp +++ b/src/creatures/creatures_definitions.hpp @@ -176,11 +176,11 @@ enum ConditionParam_t { CONDITION_PARAM_SKILL_FISHING = 26, CONDITION_PARAM_STAT_MAXHITPOINTS = 27, CONDITION_PARAM_STAT_MAXMANAPOINTS = 28, - // CONDITION_PARAM_STAT_SOULPOINTS = 29, + CONDITION_PARAM_STAT_SOULPOINTS = 29, CONDITION_PARAM_STAT_MAGICPOINTS = 30, CONDITION_PARAM_STAT_MAXHITPOINTSPERCENT = 31, CONDITION_PARAM_STAT_MAXMANAPOINTSPERCENT = 32, - // CONDITION_PARAM_STAT_SOULPOINTSPERCENT = 33, + CONDITION_PARAM_STAT_SOULPOINTSPERCENT = 33, CONDITION_PARAM_STAT_MAGICPOINTSPERCENT = 34, CONDITION_PARAM_PERIODICDAMAGE = 35, CONDITION_PARAM_SKILL_MELEEPERCENT = 36, @@ -235,7 +235,7 @@ enum ConditionParam_t { enum stats_t { STAT_MAXHITPOINTS, STAT_MAXMANAPOINTS, - STAT_SOULPOINTS, // unused + STAT_SOULPOINTS, STAT_MAGICPOINTS, STAT_CAPACITY, @@ -392,6 +392,7 @@ enum ConditionId_t : int8_t { CONDITIONID_FEET, CONDITIONID_RING, CONDITIONID_AMMO, + CONDITIONID_OUTFIT }; enum PlayerSex_t : uint8_t { diff --git a/src/creatures/players/cyclopedia/player_title.cpp b/src/creatures/players/cyclopedia/player_title.cpp index d849c08a..d59ad349 100644 --- a/src/creatures/players/cyclopedia/player_title.cpp +++ b/src/creatures/players/cyclopedia/player_title.cpp @@ -287,17 +287,17 @@ bool PlayerTitle::checkOther(const std::string &name) const { // Win Ancient Aucar Outfits complete so fight with Atab and be teleported to the arena. } else if (name == "Admirer of the Crown") { // Complete the Royal Costume Outfits. - return m_player.canWear(1457, 3) && m_player.canWear(1456, 3); + return m_player.canWearOutfit(1457, 3) && m_player.canWearOutfit(1456, 3); } else if (name == "Big Spender") { // Unlocked the full Golden Outfit. - return m_player.canWear(1211, 3) && m_player.canWear(1210, 3); + return m_player.canWearOutfit(1211, 3) && m_player.canWearOutfit(1210, 3); } else if (name == "Challenger of the Iks") { // Defeat Ahau while equipping a Broken Iks Headpiece, a Broken Iks Cuirass, some Broken Iks Faulds and Broken Iks Sandals return m_player.getBestiaryKillCount(2346) >= 1; } else if (name == "Royal Bounacean Advisor") { // Complete the Galthen and the Lost Queen quest line // Win Royal Bounacean Outfit - return m_player.canWear(1437, 3) && m_player.canWear(1436, 3); + return m_player.canWearOutfit(1437, 3) && m_player.canWearOutfit(1436, 3); } else if (name == "Aeternal") { // Unlocked by 10-year-old characters. } else if (name == "Robinson Crusoe") { diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 84e1e947..46eecbbf 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -946,11 +946,15 @@ void Player::setVarStats(stats_t stat, int32_t modifier) { int32_t Player::getDefaultStats(stats_t stat) const { switch (stat) { case STAT_MAXHITPOINTS: - return healthMax; + return getMaxHealth() - getVarStats(STAT_MAXHITPOINTS); case STAT_MAXMANAPOINTS: - return manaMax; + return getMaxMana() - getVarStats(STAT_MAXMANAPOINTS); case STAT_MAGICPOINTS: - return getBaseMagicLevel(); + return getBaseMagicLevel() - getVarStats(STAT_MAGICPOINTS); + case STAT_SOULPOINTS: + return getSoul() - getVarStats(STAT_SOULPOINTS); + case STAT_CAPACITY: + return getBaseCapacity() - getVarStats(STAT_CAPACITY); default: return 0; } @@ -6168,9 +6172,26 @@ void Player::changeSoul(int32_t soulChange) { sendStats(); } -bool Player::canWear(uint16_t lookType, uint8_t addons) const { +bool Player::changeOutfit(Outfit_t outfit, bool checkList) { + auto outfitId = Outfits::getInstance().getOutfitId(getSex(), outfit.lookType); + if (checkList && (!canWearOutfit(outfitId, outfit.lookAddons) || !requestedOutfit)) { + return false; + } + + requestedOutfit = false; + if (outfitAttributes) { + auto oldId = Outfits::getInstance().getOutfitId(getSex(), defaultOutfit.lookType); + outfitAttributes = !Outfits::getInstance().removeAttributes(getID(), oldId, getSex()); + } + + defaultOutfit = outfit; + outfitAttributes = Outfits::getInstance().addAttributes(getID(), outfitId, getSex(), defaultOutfit.lookAddons); + return true; +} + +bool Player::canWearOutfit(uint16_t lookType, uint8_t addons) const { if (g_configManager().getBoolean(WARN_UNSAFE_SCRIPTS) && lookType != 0 && !g_game().isLookTypeRegistered(lookType)) { - g_logger().warn("[Player::canWear] An unregistered creature looktype type with id '{}' was blocked to prevent client crash.", lookType); + g_logger().warn("[Player::canWearOutfit] An unregistered creature looktype type with id '{}' was blocked to prevent client crash.", lookType); return false; } @@ -6631,6 +6652,19 @@ uint32_t Player::getAttackSpeed() const { } } + if (outfitAttributes) { + const auto &outfit = Outfits::getInstance().getOutfitByLookType(getPlayer(), defaultOutfit.lookType); + if (outfit) { + if (outfit->attackSpeed > 0) { + if (outfit->attackSpeed >= vocation->getAttackSpeed()) { + modifiers = 0; + } else { + modifiers += outfit->attackSpeed; + } + } + } + } + if (onFistAttackSpeed) { uint32_t baseAttackSpeed = vocation->getAttackSpeed(); uint32_t skillLevel = getSkillLevel(SKILL_FIST); @@ -6764,6 +6798,17 @@ bool Player::hasExtraSwing() { return lastAttack > 0 && !checkLastAttackWithin(getAttackSpeed()); } +int32_t Player::getSkill(skills_t skilltype, SkillsId_t skillinfo) const { + const Skill &skill = skills[skilltype]; + int32_t ret = 0; + + if (skillinfo == SKILLVALUE_LEVEL) { + ret = skill.level + varSkills[skilltype]; + } + + return std::max(0, ret); +} + uint16_t Player::getSkillLevel(skills_t skill) const { auto skillLevel = getLoyaltySkill(skill); skillLevel = std::max(0, skillLevel + varSkills[skill]); @@ -10095,6 +10140,11 @@ void Player::onCreatureAppear(const std::shared_ptr &creature, bool is if (isLogin && creature == getPlayer()) { onEquipInventory(); + const auto &outfit = Outfits::getInstance().getOutfitByLookType(getPlayer(), defaultOutfit.lookType); + if (outfit) { + outfitAttributes = Outfits::getInstance().addAttributes(getID(), defaultOutfit.lookType, sex, defaultOutfit.lookAddons); + } + // Refresh bosstiary tracker onLogin refreshCyclopediaMonsterTracker(true); // Refresh bestiary tracker onLogin diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index b8c39b4d..be8370d7 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -465,6 +465,9 @@ class Player final : public Creature, public Cylinder, public Bankable { uint8_t getSoul() const { return soul; } + uint8_t getFullSoul() const { + return getSoul() + getVarStats(STAT_SOULPOINTS); + } bool isAccessPlayer() const; bool isPlayerGroup() const; bool isPremium() const; @@ -557,6 +560,9 @@ class Player final : public Creature, public Cylinder, public Bankable { void setVarStats(stats_t stat, int32_t modifier); int32_t getDefaultStats(stats_t stat) const; + int32_t getVarStats(stats_t stat) const { + return varStats[stat]; + } void addConditionSuppressions(const std::array &addCondition); void removeConditionSuppressions(); @@ -681,6 +687,7 @@ class Player final : public Creature, public Cylinder, public Bankable { void drainMana(const std::shared_ptr &attacker, int32_t manaLoss) override; void addManaSpent(uint64_t amount); void addSkillAdvance(skills_t skill, uint64_t count); + int32_t getSkill(skills_t skilltype, SkillsId_t skillinfo) const; int32_t getArmor() const override; int32_t getDefense() const override; @@ -729,12 +736,17 @@ class Player final : public Creature, public Cylinder, public Bankable { void sendCreatureSkull(const std::shared_ptr &creature) const; void checkSkullTicks(int64_t ticks); - bool canWear(uint16_t lookType, uint8_t addons) const; + bool canWearOutfit(uint16_t lookType, uint8_t addons) const; void addOutfit(uint16_t lookType, uint8_t addons); bool removeOutfit(uint16_t lookType); bool removeOutfitAddon(uint16_t lookType, uint8_t addons); bool getOutfitAddons(const std::shared_ptr &outfit, uint8_t &addons) const; + bool changeOutfit(Outfit_t outfit, bool checkList); + void hasRequestedOutfit(bool v) { + requestedOutfit = v; + } + bool canFamiliar(uint16_t lookType) const; void addFamiliar(uint16_t lookType); bool removeFamiliar(uint16_t lookType); @@ -1595,6 +1607,8 @@ class Player final : public Creature, public Cylinder, public Bankable { bool moved = false; bool m_isDead = false; bool imbuementTrackerWindowOpen = false; + bool requestedOutfit = false; + bool outfitAttributes = false; // Hazard system int64_t lastHazardSystemCriticalHit = 0; diff --git a/src/game/game.cpp b/src/game/game.cpp index 8100e1ba..5722c847 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -4396,7 +4396,7 @@ void Game::playerSetShowOffSocket(uint32_t playerId, Outfit_t &outfit, const Pos item->setCustomAttribute("PastLookMount", static_cast(outfit.lookMount)); } - if (!player->canWear(outfit.lookType, outfit.lookAddons)) { + if (!player->canWearOutfit(outfit.lookType, outfit.lookAddons)) { outfit.lookType = 0; outfit.lookAddons = 0; } @@ -6124,6 +6124,10 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, uint8_t isMoun return; } + if (!player->changeOutfit(outfit, true)) { + return; + } + if (player->isWearingSupportOutfit()) { outfit.lookMount = 0; isMountRandomized = 0; @@ -6174,7 +6178,7 @@ void Game::playerChangeOutfit(uint32_t playerId, Outfit_t outfit, uint8_t isMoun player->dismount(); } - if (player->canWear(outfit.lookType, outfit.lookAddons)) { + if (player->canWearOutfit(outfit.lookType, outfit.lookAddons)) { player->defaultOutfit = outfit; if (player->hasCondition(CONDITION_OUTFIT)) { diff --git a/src/lua/functions/core/game/lua_enums.cpp b/src/lua/functions/core/game/lua_enums.cpp index 56383f32..19212df5 100644 --- a/src/lua/functions/core/game/lua_enums.cpp +++ b/src/lua/functions/core/game/lua_enums.cpp @@ -358,6 +358,7 @@ void LuaEnums::initConditionIdEnums(lua_State* L) { registerEnum(L, CONDITIONID_FEET); registerEnum(L, CONDITIONID_RING); registerEnum(L, CONDITIONID_AMMO); + registerEnum(L, CONDITIONID_OUTFIT); } void LuaEnums::initConditionParamEnums(lua_State* L) { diff --git a/src/lua/functions/creatures/player/player_functions.cpp b/src/lua/functions/creatures/player/player_functions.cpp index 1268d53c..9cff42fa 100644 --- a/src/lua/functions/creatures/player/player_functions.cpp +++ b/src/lua/functions/creatures/player/player_functions.cpp @@ -2733,7 +2733,7 @@ int PlayerFunctions::luaPlayerHasOutfit(lua_State* L) { if (player) { const uint16_t lookType = Lua::getNumber(L, 2); const auto addon = Lua::getNumber(L, 3, 0); - Lua::pushBoolean(L, player->canWear(lookType, addon)); + Lua::pushBoolean(L, player->canWearOutfit(lookType, addon)); } else { lua_pushnil(L); } diff --git a/src/server/network/protocol/protocolgame.cpp b/src/server/network/protocol/protocolgame.cpp index f0e3b50a..c2081a22 100644 --- a/src/server/network/protocol/protocolgame.cpp +++ b/src/server/network/protocol/protocolgame.cpp @@ -3502,7 +3502,7 @@ void ProtocolGame::sendCyclopediaCharacterGeneralStats() { msg.add(std::min(player->getMaxHealth(), std::numeric_limits::max())); msg.add(std::min(player->getMana(), std::numeric_limits::max())); msg.add(std::min(player->getMaxMana(), std::numeric_limits::max())); - msg.addByte(player->getSoul()); + msg.addByte(player->getFullSoul()); msg.add(player->getStaminaMinutes()); std::shared_ptr condition = player->getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT); @@ -7240,7 +7240,7 @@ void ProtocolGame::sendOutfitWindow() { msg.addByte(0x00); ++outfitSize; } else if (outfit->lookType == 1210 || outfit->lookType == 1211) { // golden outfit - if (player->canWear(1210, 0) || player->canWear(1211, 0)) { + if (player->canWearOutfit(1210, 0) || player->canWearOutfit(1211, 0)) { msg.add(outfit->lookType); msg.addString(outfit->name); msg.addByte(3); @@ -7248,7 +7248,7 @@ void ProtocolGame::sendOutfitWindow() { ++outfitSize; } } else if (outfit->lookType == 1456 || outfit->lookType == 1457) { // Royal Costume - if (player->canWear(1456, 0) || player->canWear(1457, 0)) { + if (player->canWearOutfit(1456, 0) || player->canWearOutfit(1457, 0)) { msg.add(outfit->lookType); msg.addString(outfit->name); msg.addByte(3); @@ -7335,6 +7335,7 @@ void ProtocolGame::sendOutfitWindow() { // Version 12.81 - Random mount 'bool' msg.addByte(isSupportOutfit ? 0x00 : (player->isRandomMounted() ? 0x01 : 0x00)); + player->hasRequestedOutfit(true); writeToOutputBuffer(msg); } @@ -7878,7 +7879,7 @@ void ProtocolGame::AddPlayerStats(NetworkMessage &msg) { msg.addByte(std::min(static_cast(player->getMagicLevelPercent()), 100)); } - msg.addByte(player->getSoul()); + msg.addByte(player->getFullSoul()); msg.add(player->getStaminaMinutes());