diff --git a/config.lua.dist b/config.lua.dist index 266a70e44f6..4b0e4885e66 100644 --- a/config.lua.dist +++ b/config.lua.dist @@ -401,6 +401,8 @@ coinPacketSize = 25 coinImagesURL = "http://127.0.0.1/images/store/" classicAttackSpeed = false showScriptsLogInConsole = false +-- time to suppress negative conditions after being affected by them (ms) +minDelayBetweenConditions = 0 -- configure maximum value of critical imbuement criticalChance = 10 inventoryGlowOnFiveBless = false diff --git a/src/config/config_definitions.hpp b/src/config/config_definitions.hpp index b3f1e51a739..250c4a612b0 100644 --- a/src/config/config_definitions.hpp +++ b/src/config/config_definitions.hpp @@ -147,6 +147,7 @@ enum ConfigKey_t : uint16_t { METRICS_ENABLE_PROMETHEUS, METRICS_OSTREAM_INTERVAL, METRICS_PROMETHEUS_ADDRESS, + MIN_DELAY_BETWEEN_CONDITIONS, MIN_ELEMENTAL_RESISTANCE, MOMENTUM_CHANCE_FORMULA_A, MOMENTUM_CHANCE_FORMULA_B, diff --git a/src/config/configmanager.cpp b/src/config/configmanager.cpp index 2287a3ed0a4..560858fc88b 100644 --- a/src/config/configmanager.cpp +++ b/src/config/configmanager.cpp @@ -117,6 +117,7 @@ bool ConfigManager::load() { loadBoolConfig(L, CLEAN_PROTECTION_ZONES, "cleanProtectionZones", false); loadBoolConfig(L, GLOBAL_SERVER_SAVE_SHUTDOWN, "globalServerSaveShutdown", true); loadBoolConfig(L, PUSH_WHEN_ATTACKING, "pushWhenAttacking", false); + loadIntConfig(L, MIN_DELAY_BETWEEN_CONDITIONS, "minDelayBetweenConditions", 0); loadBoolConfig(L, WEATHER_RAIN, "weatherRain", false); loadBoolConfig(L, WEATHER_THUNDER, "thunderEffect", false); diff --git a/src/creatures/combat/combat.cpp b/src/creatures/combat/combat.cpp index 3e0af9ea444..7f8545d3164 100644 --- a/src/creatures/combat/combat.cpp +++ b/src/creatures/combat/combat.cpp @@ -744,7 +744,7 @@ void Combat::CombatConditionFunc(std::shared_ptr caster, std::shared_p // TODO: infight condition until all aggressive conditions has ended if (target) { - target->addCombatCondition(conditionCopy); + target->addCombatCondition(conditionCopy, caster && caster->getPlayer() != nullptr); } } } diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index f1e2ec6e6d1..9549abd4014 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -1644,7 +1644,9 @@ bool ConditionDamage::getNextDamage(int32_t &damage) { } bool ConditionDamage::doDamage(std::shared_ptr creature, int32_t healthChange) { - if (creature->isSuppress(getType())) { + auto attacker = g_game().getPlayerByGUID(owner) ? g_game().getPlayerByGUID(owner)->getCreature() : g_game().getCreatureByID(owner); + bool isPlayer = attacker && attacker->getPlayer(); + if (creature->isSuppress(getType(), isPlayer)) { return true; } @@ -1653,7 +1655,6 @@ bool ConditionDamage::doDamage(std::shared_ptr creature, int32_t healt damage.primary.value = healthChange; damage.primary.type = Combat::ConditionToDamageType(conditionType); - std::shared_ptr attacker = g_game().getCreatureByID(owner); if (field && creature->getPlayer() && attacker && attacker->getPlayer()) { damage.primary.value = static_cast(std::round(damage.primary.value / 2.)); } diff --git a/src/creatures/creature.cpp b/src/creatures/creature.cpp index efbf3ea5cf7..b917a23d640 100644 --- a/src/creatures/creature.cpp +++ b/src/creatures/creature.cpp @@ -1334,12 +1334,14 @@ bool Creature::setMaster(std::shared_ptr newMaster, bool reloadCreatur return true; } -bool Creature::addCondition(std::shared_ptr condition) { +bool Creature::addCondition(std::shared_ptr condition, bool attackerPlayer /* = false*/) { metrics::method_latency measure(__METHOD_NAME__); if (condition == nullptr) { return false; } - + if (isSuppress(condition->getType(), attackerPlayer)) { + return false; + } std::shared_ptr prevCond = getCondition(condition->getType(), condition->getId(), condition->getSubId()); if (prevCond) { prevCond->addCondition(getCreature(), condition); @@ -1355,11 +1357,11 @@ bool Creature::addCondition(std::shared_ptr condition) { return false; } -bool Creature::addCombatCondition(std::shared_ptr condition) { +bool Creature::addCombatCondition(std::shared_ptr condition, bool attackerPlayer /* = false*/) { // Caution: condition variable could be deleted after the call to addCondition ConditionType_t type = condition->getType(); - if (!addCondition(condition)) { + if (!addCondition(condition, attackerPlayer)) { return false; } @@ -1490,7 +1492,7 @@ void Creature::executeConditions(uint32_t interval) { bool Creature::hasCondition(ConditionType_t type, uint32_t subId /* = 0*/) const { metrics::method_latency measure(__METHOD_NAME__); - if (isSuppress(type)) { + if (isSuppress(type, false)) { return false; } diff --git a/src/creatures/creature.hpp b/src/creatures/creature.hpp index cbbdc94e3ac..a21403edcef 100644 --- a/src/creatures/creature.hpp +++ b/src/creatures/creature.hpp @@ -388,8 +388,8 @@ class Creature : virtual public Thing, public SharedObject { return SPEECHBUBBLE_NONE; } - bool addCondition(std::shared_ptr condition); - bool addCombatCondition(std::shared_ptr condition); + bool addCondition(std::shared_ptr condition, bool attackerPlayer = false); + bool addCombatCondition(std::shared_ptr condition, bool attackerPlayer = false); void removeCondition(ConditionType_t conditionType, ConditionId_t conditionId, bool force = false); void removeCondition(ConditionType_t type); void removeCondition(std::shared_ptr condition); @@ -406,7 +406,7 @@ class Creature : virtual public Thing, public SharedObject { virtual bool isImmune(ConditionType_t type) const { return false; } - virtual bool isSuppress(ConditionType_t type) const { + virtual bool isSuppress(ConditionType_t type, bool attackerPlayer) const { return false; }; diff --git a/src/creatures/creatures_definitions.hpp b/src/creatures/creatures_definitions.hpp index ee1f390b9a2..71c607b64c3 100644 --- a/src/creatures/creatures_definitions.hpp +++ b/src/creatures/creatures_definitions.hpp @@ -112,6 +112,28 @@ enum ConditionType_t : uint8_t { CONDITION_COUNT = 39 }; +// constexpr definiting suppressible conditions +constexpr bool IsConditionSuppressible(ConditionType_t condition) { + constexpr ConditionType_t suppressibleConditions[] = { + CONDITION_POISON, + CONDITION_FIRE, + CONDITION_ENERGY, + CONDITION_BLEEDING, + CONDITION_PARALYZE, + CONDITION_DROWN, + CONDITION_FREEZING, + CONDITION_CURSED, + }; + + for (const auto &suppressibleCondition : suppressibleConditions) { + if (condition == suppressibleCondition) { + return true; + } + } + + return false; +} + enum ConditionParam_t { CONDITION_PARAM_OWNER = 1, CONDITION_PARAM_TICKS = 2, diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 5a42dc398f5..576957ec553 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -215,7 +215,12 @@ std::shared_ptr Player::getInventoryItem(Slots_t slot) const { return inventory[slot]; } -bool Player::isSuppress(ConditionType_t conditionType) const { +bool Player::isSuppress(ConditionType_t conditionType, bool attackerPlayer) const { + auto minDelay = g_configManager().getNumber(MIN_DELAY_BETWEEN_CONDITIONS, __FUNCTION__); + if (IsConditionSuppressible(conditionType) && checkLastConditionTimeWithin(conditionType, minDelay)) { + return true; + } + return m_conditionSuppressions[static_cast(conditionType)]; } @@ -450,7 +455,7 @@ float Player::getDefenseFactor() const { uint32_t Player::getClientIcons() { uint32_t icons = 0; for (const auto &condition : conditions) { - if (!isSuppress(condition->getType())) { + if (!isSuppress(condition->getType(), false)) { icons |= condition->getIcons(); } } @@ -4481,6 +4486,9 @@ void Player::onAddCondition(ConditionType_t type) { } void Player::onAddCombatCondition(ConditionType_t type) { + if (IsConditionSuppressible(type)) { + updateLastConditionTime(type); + } switch (type) { case CONDITION_POISON: sendTextMessage(MESSAGE_FAILURE, "You are poisoned."); diff --git a/src/creatures/players/player.hpp b/src/creatures/players/player.hpp index ecf489b89ec..192eb53ff6e 100644 --- a/src/creatures/players/player.hpp +++ b/src/creatures/players/player.hpp @@ -896,6 +896,25 @@ class Player final : public Creature, public Cylinder, public Bankable { return lastAttackBlockType; } + uint64_t getLastConditionTime(ConditionType_t type) const { + if (!lastConditionTime.contains(static_cast(type))) { + return 0; + } + return lastConditionTime.at(static_cast(type)); + } + + void updateLastConditionTime(ConditionType_t type) { + lastConditionTime[static_cast(type)] = OTSYS_TIME(); + } + + bool checkLastConditionTimeWithin(ConditionType_t type, uint32_t interval) const { + if (!lastConditionTime.contains(static_cast(type))) { + return false; + } + auto last = lastConditionTime.at(static_cast(type)); + return last > 0 && ((OTSYS_TIME() - last) < interval); + } + uint64_t getLastAttack() const { return lastAttack; } @@ -924,13 +943,6 @@ class Player final : public Creature, public Cylinder, public Bankable { lastAggressiveAction = OTSYS_TIME(); } - uint64_t getLastFocusLost() const { - return lastFocusLost; - } - void setLastFocusLost(uint64_t time) { - lastFocusLost = time; - } - std::unordered_set getNPCSkips(); std::shared_ptr getWeapon(Slots_t slot, bool ignoreAmmo) const; @@ -2731,8 +2743,8 @@ class Player final : public Creature, public Cylinder, public Bankable { uint64_t experience = 0; uint64_t manaSpent = 0; uint64_t lastAttack = 0; + std::unordered_map lastConditionTime; uint64_t lastAggressiveAction = 0; - uint64_t lastFocusLost = 0; uint64_t bankBalance = 0; uint64_t lastQuestlogUpdate = 0; uint64_t preyCards = 0; @@ -2953,7 +2965,7 @@ class Player final : public Creature, public Cylinder, public Bankable { return skillLoss ? static_cast(experience * getLostPercent()) : 0; } - bool isSuppress(ConditionType_t conditionType) const override; + bool isSuppress(ConditionType_t conditionType, bool attackerPlayer) const override; void addConditionSuppression(const std::array &addConditions); uint16_t getLookCorpse() const override;