diff --git a/data/actions/player.json b/data/actions/player.json index c266e805..ebd0494f 100644 --- a/data/actions/player.json +++ b/data/actions/player.json @@ -504,6 +504,8 @@ { "id": 85, "duration": 24000, + "maxDuration": 60000, + "statusRefreshPolicy": 48, "modifiers": [ { "modifier": "DamageDealtPercent", @@ -2039,7 +2041,20 @@ "nextCombo": [], "statuses": { "caster": [], - "target": [] + "target": [ + { + "id": 150, + "duration": 30000, + "statusRefreshPolicy": 17, + "flag": 8192, + "modifiers": [ + { + "modifier": "TickHeal", + "value": 50 + } + ] + } + ] } }, "134": { @@ -2099,7 +2114,20 @@ "nextCombo": [], "statuses": { "caster": [], - "target": [] + "target": [ + { + "id": 158, + "duration": 21000, + "statusRefreshPolicy": 17, + "flag": 8192, + "modifiers": [ + { + "modifier": "TickHeal", + "value": 150 + } + ] + } + ] } }, "138": { diff --git a/src/common/Common.h b/src/common/Common.h index c54db478..dad9e8eb 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -937,7 +937,18 @@ namespace Sapphire::Common Invisibilty = 512, CanStatusOff = 1024, FcBuff = 2048, - RemoveOnSuccessfulHit = 4096 + RemoveOnSuccessfulHit = 4096, + ReplaceSameCaster = 8192 + }; + + enum class StatusRefreshPolicy : uint8_t + { + Stack = 0, + ReplaceOrApply = 1, + Extend = 2, + ExtendOrApply = 3, + Reject = 4, + Custom = 255 }; enum struct ActionAspect : uint8_t diff --git a/src/tools/action_parse/actions/player.json b/src/tools/action_parse/actions/player.json index e6c9cb3e..b3517933 100644 --- a/src/tools/action_parse/actions/player.json +++ b/src/tools/action_parse/actions/player.json @@ -398,7 +398,18 @@ "restorePercentage": 0, "nextCombo": [], "statuses": { - "caster": [], + "caster": [ + { + "id": 83, + "duration": 20000, + "modifiers": [ + { + "modifier": "DefensePercent", + "value": 20 + } + ] + } + ], "target": [] } }, @@ -414,7 +425,18 @@ "nextCombo": [], "statuses": { "caster": [], - "target": [] + "target": [ + { + "id": 244, + "duration": 30000, + "modifiers": [ + { + "modifier": "TickDamage", + "value": 20 + } + ] + } + ] } }, "34": { @@ -478,7 +500,18 @@ 45 ], "statuses": { - "caster": [], + "caster": [ + { + "id": 85, + "duration": 24000, + "modifiers": [ + { + "modifier": "DamageDealtPercent", + "value": 20 + } + ] + } + ], "target": [] } }, @@ -493,7 +526,18 @@ "restorePercentage": 0, "nextCombo": [], "statuses": { - "caster": [], + "caster": [ + { + "id": 86, + "duration": 20000, + "modifiers": [ + { + "modifier": "AttackPowerPercent", + "value": 50 + } + ] + } + ], "target": [] } }, @@ -523,7 +567,18 @@ "restorePercentage": 0, "nextCombo": [], "statuses": { - "caster": [], + "caster": [ + { + "id": 87, + "duration": 20000, + "modifiers": [ + { + "modifier": "HPPercent", + "value": 20 + } + ] + } + ], "target": [] } }, @@ -583,7 +638,18 @@ "restorePercentage": 0, "nextCombo": [], "statuses": { - "caster": [], + "caster": [ + { + "id": 89, + "duration": 20000, + "modifiers": [ + { + "modifier": "ReflectPhysical", + "value": 50 + } + ] + } + ], "target": [] } }, @@ -643,7 +709,34 @@ "restorePercentage": 0, "nextCombo": [], "statuses": { - "caster": [], + "caster": [ + { + "id": 91, + "duration": 0, + "modifiers": [ + { + "modifier": "HPPercent", + "value": 25 + }, + { + "modifier": "DamageDealtPercent", + "value": -25 + }, + { + "modifier": "HealingMagicRecoveryPercent", + "value": 20 + }, + { + "modifier": "AccuracyPercent", + "value": 5 + }, + { + "modifier": "EnmityPercent", + "value": 20 + } + ] + } + ], "target": [] } }, @@ -1176,7 +1269,19 @@ "restorePercentage": 0, "nextCombo": [], "statuses": { - "caster": [], + "caster": [ + { + "id": 116, + "duration": 10000, + "flag": 4096, + "modifiers": [ + { + "modifier": "CriticalHitPercent", + "value": 100 + } + ] + } + ], "target": [] } }, @@ -2394,7 +2499,18 @@ "nextCombo": [], "statuses": { "caster": [], - "target": [] + "target": [ + { + "id": 179, + "duration": 18000, + "modifiers": [ + { + "modifier": "TickDamage", + "value": 40 + } + ] + } + ] } }, "165": { @@ -2454,7 +2570,38 @@ "nextCombo": [], "statuses": { "caster": [], - "target": [] + "target": [ + { + "id": 180, + "duration": 24000, + "modifiers": [ + { + "modifier": "TickDamage", + "value": 35 + } + ] + }, + { + "id": 191, + "duration": 24000, + "modifiers": [ + { + "modifier": "HealingRecoveryPercent", + "value": -20 + } + ] + }, + { + "id": 240, + "duration": 24000, + "modifiers": [ + { + "modifier": "HeavyPercent", + "value": 40 + } + ] + } + ] } }, "169": { diff --git a/src/tools/action_parse/main.cpp b/src/tools/action_parse/main.cpp index 1f07de8f..f2434461 100644 --- a/src/tools/action_parse/main.cpp +++ b/src/tools/action_parse/main.cpp @@ -41,6 +41,8 @@ struct StatusEntry { uint16_t id; uint32_t duration; + uint32_t maxDuration; + uint8_t statusRefreshPolicy; uint32_t flag; std::vector< StatusModifier > modifiers; }; @@ -79,6 +81,8 @@ void to_json( nlohmann::ordered_json& j, const StatusEntry& statusEntry ) j = nlohmann::ordered_json{ { "id", statusEntry.id }, { "duration", statusEntry.duration }, + { "maxDuration", statusEntry.maxDuration }, + { "statusRefreshPolicy", statusEntry.statusRefreshPolicy }, { "flag", statusEntry.flag }, { "modifiers", statusEntry.modifiers } }; @@ -98,7 +102,7 @@ void to_json( nlohmann::ordered_json& j, const ActionEntry& action ) { "nextCombo", action.nextCombo }, { "statuses", { { "caster", action.statuses.caster }, - { "target", action.statuses.target }, + { "target", action.statuses.target } } } }; diff --git a/src/world/Action/Action.cpp b/src/world/Action/Action.cpp index aefb889f..9ee60a2d 100644 --- a/src/world/Action/Action.cpp +++ b/src/world/Action/Action.cpp @@ -32,6 +32,8 @@ #include #include "WorldServer.h" +#include "StatusEffect/StatusEffect.h" + #include "Job/Warrior.h" using namespace Sapphire; @@ -607,6 +609,89 @@ void Action::Action::buildActionResults() // m_effectBuilder.reset(); } +void Action::Action::applyStatusEffect( bool isSelf, Entity::CharaPtr& target, Entity::CharaPtr& source, World::Action::StatusEntry& status, bool statusToSource ) +{ + auto pActionBuilder = getActionResultBuilder(); + + if( !pActionBuilder ) + return; + + auto hasSameStatus = false; + auto hasSameStatusFromSameCaster = false; + Sapphire::StatusEffect::StatusEffectPtr referenceStatus = nullptr; + + for( auto const& entry : statusToSource ? source->getStatusEffectMap() : target->getStatusEffectMap() ) + { + auto statusEffect = entry.second; + if( statusEffect->getId() == status.id ) + { + hasSameStatus = true; + + if( !referenceStatus ) + referenceStatus = statusEffect; + + if( statusEffect->getSrcActorId() == source->getId() ) + { + hasSameStatusFromSameCaster = true; + referenceStatus = statusEffect; + break;; + } + } + } + + auto policy = getStatusRefreshPolicy( status.statusRefreshPolicy, isSelf ); + switch( policy ) + { + case Common::StatusRefreshPolicy::Stack: + { + pActionBuilder->applyStatusEffect( target, status.id, status.duration, 0, std::move( status.modifiers ), status.flag, statusToSource, false ); + break; + } + case Common::StatusRefreshPolicy::ReplaceOrApply: + { + if( (status.flag & static_cast< uint32_t >( Common::StatusEffectFlag::ReplaceSameCaster ) && hasSameStatusFromSameCaster) || hasSameStatus ) + pActionBuilder->replaceStatusEffect( referenceStatus, target, status.id, status.duration, 0, std::move( status.modifiers ), status.flag, statusToSource ); + else + pActionBuilder->applyStatusEffect( target, status.id, status.duration, 0, std::move( status.modifiers ), status.flag, statusToSource, true ); + break; + } + case Common::StatusRefreshPolicy::Extend: + case Common::StatusRefreshPolicy::ExtendOrApply: + { + int64_t remainingDuration = 0; + if( hasSameStatus ) + { + remainingDuration = static_cast< int64_t >( referenceStatus->getDuration() ) - ( Common::Util::getTimeMs() - referenceStatus->getStartTimeMs() ); + if( remainingDuration < 0 ) + remainingDuration = 0; + } + + if( hasSameStatus || policy == Common::StatusRefreshPolicy::ExtendOrApply ) + { + pActionBuilder->applyStatusEffect( target, status.id, std::min( status.duration + remainingDuration, static_cast< int64_t >( status.maxDuration ) ), 0, std::move( status.modifiers ), status.flag, statusToSource, true ); + } + break; + } + case Common::StatusRefreshPolicy::Reject: + { + if( !hasSameStatus ) + { + pActionBuilder->applyStatusEffect( target, status.id, status.duration, 0, std::move( status.modifiers ), status.flag, statusToSource, true ); + } + else + { + // add function to ActionBuilder for No Effect effect + } + break; + } + case Common::StatusRefreshPolicy::Custom: + { + // script should handle it + break; + } + } +} + void Action::Action::handleStatusEffects() { auto pActionBuilder = getActionResultBuilder(); @@ -622,7 +707,12 @@ void Action::Action::handleStatusEffects() { for( auto& status : m_lutEntry.statuses.caster ) { - pActionBuilder->applyStatusEffectSelf( status.id, status.duration, 0, std::move( status.modifiers ), status.flag, true ); + /*if( m_hitActors[ 0 ] ) // might need a firstValidVictim? + applyStatusEffect( true, m_hitActors[ 0 ], m_pSource, status, true ); + // pActionBuilder->applyStatusEffectSelf( status.id, status.duration, 0, std::move( status.modifiers ), status.flag, true ); // statusToSource true + else if( m_lutEntry.potency == 0 )*/ + applyStatusEffect( true, m_pSource, m_pSource, status, true ); + // pActionBuilder->applyStatusEffectSelf( status.id, status.duration, 0, std::move( status.modifiers ), status.flag, true ); } } @@ -633,7 +723,8 @@ void Action::Action::handleStatusEffects() { for( auto& status : m_lutEntry.statuses.target ) { - pActionBuilder->applyStatusEffect( actor, status.id, status.duration, 0, std::move( status.modifiers ), status.flag, true ); + applyStatusEffect( false, actor, m_pSource, status ); + // pActionBuilder->applyStatusEffect( actor, status.id, status.duration, 0, std::move( status.modifiers ), status.flag, true ); } if( !actor->getStatusEffectMap().empty() ) diff --git a/src/world/Action/Action.h b/src/world/Action/Action.h index e6e71424..5ed1007b 100644 --- a/src/world/Action/Action.h +++ b/src/world/Action/Action.h @@ -113,6 +113,8 @@ namespace Sapphire::World::Action void buildActionResults(); + void applyStatusEffect( bool isSelf, Entity::CharaPtr& target, Entity::CharaPtr& source, World::Action::StatusEntry& status, bool statusToSource = false ); + void handleStatusEffects(); void handleJobAction(); diff --git a/src/world/Action/ActionLut.cpp b/src/world/Action/ActionLut.cpp index 69824c43..23bede2f 100644 --- a/src/world/Action/ActionLut.cpp +++ b/src/world/Action/ActionLut.cpp @@ -26,4 +26,10 @@ const ActionEntry& ActionLut::getEntry( uint16_t actionId ) assert( it != m_actionLut.end() ); return it->second; +} + +Sapphire::Common::StatusRefreshPolicy Sapphire::World::Action::getStatusRefreshPolicy( uint8_t statusRefreshPolicy, bool sameSource ) +{ + uint8_t policy = sameSource ? statusRefreshPolicy >> 4 : statusRefreshPolicy & 0x0F; + return static_cast< Sapphire::Common::StatusRefreshPolicy >( policy ); } \ No newline at end of file diff --git a/src/world/Action/ActionLut.h b/src/world/Action/ActionLut.h index 0b86013e..d1575c59 100644 --- a/src/world/Action/ActionLut.h +++ b/src/world/Action/ActionLut.h @@ -2,7 +2,6 @@ #include #include -#include #include #include "Common.h" @@ -18,6 +17,8 @@ namespace Sapphire::World::Action { uint16_t id; uint32_t duration; + uint32_t maxDuration; + uint8_t statusRefreshPolicy; uint32_t flag; std::vector< StatusModifier > modifiers; }; @@ -51,4 +52,6 @@ namespace Sapphire::World::Action static Lut m_actionLut; }; + + Sapphire::Common::StatusRefreshPolicy getStatusRefreshPolicy( uint8_t statusRefreshPolicy, bool sameSource ); } diff --git a/src/world/Action/ActionLutData.h b/src/world/Action/ActionLutData.h index 22cf62bf..9f7d39f6 100644 --- a/src/world/Action/ActionLutData.h +++ b/src/world/Action/ActionLutData.h @@ -33,6 +33,10 @@ namespace Sapphire::World::Action { j.at( "id" ).get_to( statusEntry.id ); j.at( "duration" ).get_to( statusEntry.duration ); + if( j.contains( "maxDuration" ) ) + j.at( "maxDuration" ).get_to( statusEntry.maxDuration ); + if( j.contains( "statusRefreshPolicy" ) ) + j.at( "statusRefreshPolicy" ).get_to( statusEntry.statusRefreshPolicy ); if( j.contains( "flag" ) ) j.at( "flag" ).get_to( statusEntry.flag ); if( j.contains( "modifiers" ) ) diff --git a/src/world/Action/ActionResult.cpp b/src/world/Action/ActionResult.cpp index 13b8d99e..82c8392d 100644 --- a/src/world/Action/ActionResult.cpp +++ b/src/world/Action/ActionResult.cpp @@ -9,6 +9,8 @@ #include "Actor/Player.h" #include "StatusEffect/StatusEffect.h" +#include "Network/Util/PacketUtil.h" + using namespace Sapphire; using namespace Sapphire::Common; using namespace Sapphire::World::Action; @@ -72,19 +74,21 @@ void ActionResult::applyStatusEffect( uint32_t id, int32_t duration, Entity::Cha m_result.Arg2 = param; m_result.Type = CalcResultType::TypeSetStatus; - m_bOverrideStatus = shouldOverride; + m_bShouldOverride = shouldOverride; m_pStatus = Sapphire::StatusEffect::make_StatusEffect( id, source.getAsChara(), m_target, duration, 3000 ); m_pStatus->setParam( param ); } void ActionResult::applyStatusEffect( uint32_t id, int32_t duration, Entity::Chara& source, uint8_t param, - const std::vector< StatusModifier >& modifiers, uint32_t flag, bool shouldOverride ) + const std::vector< StatusModifier >& modifiers, uint32_t flag, bool statusToSource, bool shouldOverride ) { m_result.Value = static_cast< int16_t >( id ); m_result.Arg2 = param; - m_result.Type = CalcResultType::TypeSetStatus; + m_result.Type = statusToSource ? CalcResultType::TypeSetStatusMe : CalcResultType::TypeSetStatus; + if( statusToSource ) + m_result.Flag = static_cast< uint8_t >( ActionResultFlag::EffectOnSource ); - m_bOverrideStatus = shouldOverride; + m_bShouldOverride = shouldOverride; m_pStatus = Sapphire::StatusEffect::make_StatusEffect( id, source.getAsChara(), m_target, duration, modifiers, flag, 3000 ); m_pStatus->setParam( param ); } @@ -96,7 +100,7 @@ void ActionResult::applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t m_result.Type = CalcResultType::TypeSetStatusMe; m_result.Flag = static_cast< uint8_t >( ActionResultFlag::EffectOnSource ); - m_bOverrideStatus = shouldOverride; + m_bShouldOverride = shouldOverride; m_pStatus = Sapphire::StatusEffect::make_StatusEffect( id, m_target, m_target, duration, 3000 ); m_pStatus->setParam( param ); } @@ -109,11 +113,27 @@ void ActionResult::applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t m_result.Type = CalcResultType::TypeSetStatusMe; m_result.Flag = static_cast< uint8_t >( Common::ActionResultFlag::EffectOnSource ); - m_bOverrideStatus = shouldOverride; + m_bShouldOverride = shouldOverride; m_pStatus = Sapphire::StatusEffect::make_StatusEffect( id, m_target, m_target, duration, modifiers, flag, 3000 ); m_pStatus->setParam( param ); } +void ActionResult::replaceStatusEffect( Sapphire::StatusEffect::StatusEffectPtr& pOldStatus, uint32_t id, int32_t duration, Entity::Chara& source, uint8_t param, + const std::vector< StatusModifier >& modifiers, uint32_t flag, bool statusToSource ) +{ + applyStatusEffect( id, duration, source, param, modifiers, flag, statusToSource, false ); + m_pOldStatus = std::move( pOldStatus ); + m_pStatus->setSlot( m_pOldStatus->getSlot() ); +} + +void ActionResult::replaceStatusEffectSelf( Sapphire::StatusEffect::StatusEffectPtr& pOldStatus, uint32_t id, int32_t duration, uint8_t param, + const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag ) +{ + applyStatusEffectSelf( id, duration, param, modifiers, flag, false ); + m_pOldStatus = std::move( pOldStatus ); + m_pStatus->setSlot( m_pOldStatus->getSlot() ); +} + void ActionResult::mount( uint16_t mountId ) { m_result.Value = static_cast< int16_t >( mountId ); @@ -161,7 +181,22 @@ void ActionResult::execute() case CalcResultType::TypeSetStatus: case CalcResultType::TypeSetStatusMe: { - if( !m_bOverrideStatus ) + for( auto const& entry : m_target->getStatusEffectMap() ) + { + auto statusEffect = entry.second; + if( statusEffect->getId() == m_result.Value && m_bShouldOverride && statusEffect->getSrcActorId() == m_pStatus->getSrcActorId() ) + { + statusEffect->refresh( m_pStatus->getDuration() ); + m_pStatus->setSlot( statusEffect->getSlot() ); + + Network::Util::Packet::sendHudParam( *m_target ); + return; + } + } + + if( m_pOldStatus ) + m_target->replaceSingleStatusEffect( m_pOldStatus->getSlot(), m_pStatus ); + else if( !m_bShouldOverride ) m_target->addStatusEffectByIdIfNotExist( m_pStatus ); else m_target->addStatusEffectById( m_pStatus ); @@ -178,4 +213,4 @@ void ActionResult::execute() default: break; } -} \ No newline at end of file +} diff --git a/src/world/Action/ActionResult.h b/src/world/Action/ActionResult.h index 3a10b4ab..59f213e2 100644 --- a/src/world/Action/ActionResult.h +++ b/src/world/Action/ActionResult.h @@ -22,10 +22,14 @@ namespace Sapphire::World::Action void comboSucceed(); void applyStatusEffect( uint32_t id, int32_t duration, Entity::Chara& source, uint8_t param, bool shouldOverride ); void applyStatusEffect( uint32_t id, int32_t duration, Entity::Chara& source, uint8_t param, - const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag, bool shouldOverride ); + const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag, bool statusToSource, bool shouldOverride ); void applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t param, bool shouldOverride ); void applyStatusEffectSelf( uint32_t id, int32_t duration, uint8_t param, const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag, bool shouldOverride ); + void replaceStatusEffect( Sapphire::StatusEffect::StatusEffectPtr& pOldStatus, uint32_t id, int32_t duration, Entity::Chara& source, uint8_t param, + const std::vector< StatusModifier >& modifiers, uint32_t flag, bool statusToSource ); + void replaceStatusEffectSelf( Sapphire::StatusEffect::StatusEffectPtr& pOldStatus, uint32_t id, int32_t duration, uint8_t param, + const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag ); void mount( uint16_t mountId ); Entity::CharaPtr getTarget() const; @@ -40,8 +44,9 @@ namespace Sapphire::World::Action Common::CalcResultParam m_result; - bool m_bOverrideStatus { false }; + bool m_bShouldOverride { false }; Sapphire::StatusEffect::StatusEffectPtr m_pStatus; + Sapphire::StatusEffect::StatusEffectPtr m_pOldStatus; }; diff --git a/src/world/Action/ActionResultBuilder.cpp b/src/world/Action/ActionResultBuilder.cpp index db0da0cb..eb27c5c6 100644 --- a/src/world/Action/ActionResultBuilder.cpp +++ b/src/world/Action/ActionResultBuilder.cpp @@ -1,4 +1,4 @@ - #include "ActionResultBuilder.h" +#include "ActionResultBuilder.h" #include "ActionResult.h" #include @@ -94,10 +94,10 @@ void ActionResultBuilder::applyStatusEffect( Entity::CharaPtr& target, uint16_t } void ActionResultBuilder::applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param, - const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag, bool shouldOverride ) + const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag, bool statusToSource, bool shouldOverride ) { ActionResultPtr nextResult = make_ActionResult( target ); - nextResult->applyStatusEffect( statusId, duration, *m_sourceChara, param, modifiers, flag, shouldOverride ); + nextResult->applyStatusEffect( statusId, duration, *m_sourceChara, param, modifiers, flag, statusToSource, shouldOverride ); addResultToActor( target, nextResult ); } @@ -116,6 +116,22 @@ void ActionResultBuilder::applyStatusEffectSelf( uint16_t statusId, uint32_t dur addResultToActor( m_sourceChara, nextResult ); } +void ActionResultBuilder::replaceStatusEffect( Sapphire::StatusEffect::StatusEffectPtr& pOldStatus, Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param, + const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag, bool statusToSource ) +{ + ActionResultPtr nextResult = make_ActionResult( target ); + nextResult->replaceStatusEffect( pOldStatus, statusId, duration, *m_sourceChara, param, modifiers, flag, statusToSource ); + addResultToActor( target, nextResult ); +} + +void ActionResultBuilder::replaceStatusEffectSelf( Sapphire::StatusEffect::StatusEffectPtr& pOldStatus, uint16_t statusId, uint32_t duration, uint8_t param, + const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag ) +{ + ActionResultPtr nextResult = make_ActionResult( m_sourceChara ); + nextResult->replaceStatusEffectSelf( pOldStatus, statusId, duration, param, modifiers, flag ); + addResultToActor( m_sourceChara, nextResult ); +} + void ActionResultBuilder::mount( Entity::CharaPtr& target, uint16_t mountId ) { ActionResultPtr nextResult = make_ActionResult( target ); diff --git a/src/world/Action/ActionResultBuilder.h b/src/world/Action/ActionResultBuilder.h index e2e79747..b6009222 100644 --- a/src/world/Action/ActionResultBuilder.h +++ b/src/world/Action/ActionResultBuilder.h @@ -28,11 +28,14 @@ namespace Sapphire::World::Action void applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param, bool shouldOverride = false ); void applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param, - const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag = 0, bool shouldOverride = false ); + const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag = 0, bool statusToSource = false, bool shouldOverride = false ); void applyStatusEffectSelf( uint16_t statusId, uint32_t duration, uint8_t param, bool shouldOverride = false ); void applyStatusEffectSelf( uint16_t statusId, uint32_t duration, uint8_t param, const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag = 0, bool shouldOverride = false ); - + void replaceStatusEffect( Sapphire::StatusEffect::StatusEffectPtr& pOldStatus, Entity::CharaPtr& target, uint16_t statusId, uint32_t duration, uint8_t param, + const std::vector< StatusModifier >& modifiers, uint32_t flag = 0, bool statusToSource = false ); + void replaceStatusEffectSelf( Sapphire::StatusEffect::StatusEffectPtr& pOldStatus, uint16_t statusId, uint32_t duration, uint8_t param, + const std::vector< World::Action::StatusModifier >& modifiers, uint32_t flag = 0 ); void mount( Entity::CharaPtr& target, uint16_t mountId ); void sendActionResults( const std::vector< Entity::CharaPtr >& targetList ); diff --git a/src/world/Action/Job/Warrior.cpp b/src/world/Action/Job/Warrior.cpp index 4ee8a485..f4e1c9c2 100644 --- a/src/world/Action/Job/Warrior.cpp +++ b/src/world/Action/Job/Warrior.cpp @@ -3,6 +3,7 @@ #include #include #include +#include using namespace Sapphire; using namespace Sapphire::World::Action; @@ -29,6 +30,7 @@ void Warrior::onAction( Entity::Player& player, Action& action ) void Warrior::handleWrath( Entity::Player& player, Action& action ) { + Sapphire::StatusEffect::StatusEffectPtr oldStatus = nullptr; auto effectToApply = Wrath; auto parry = 2; auto asChara = player.getAsChara(); @@ -38,33 +40,45 @@ void Warrior::handleWrath( Entity::Player& player, Action& action ) if( !pActionBuilder ) return; - if( player.hasStatusEffect( Wrath ) ) + auto statusMap = player.getStatusEffectMap(); + + for( const auto& effectIt : statusMap ) { - player.replaceSingleStatusEffectById( Wrath ); - effectToApply = WrathII; - parry += 2; - } - else if( player.hasStatusEffect( WrathII ) ) - { - player.replaceSingleStatusEffectById( WrathII ); - effectToApply = WrathIII; - parry += 2; - } - else if( player.hasStatusEffect( WrathIII ) ) - { - player.replaceSingleStatusEffectById( WrathIII ); - effectToApply = WrathIV; - parry += 2; - } - else if( player.hasStatusEffect( WrathIV ) ) - { - player.replaceSingleStatusEffectById( WrathIV ); - effectToApply = Infuriated; - parry += 2; + if( effectIt.second->getId() == Wrath ) + { + oldStatus = effectIt.second; + effectToApply = WrathII; + parry += 2; + break; + } + else if( effectIt.second->getId() == WrathII ) + { + oldStatus = effectIt.second; + effectToApply = WrathIII; + parry += 2; + break; + } + else if( effectIt.second->getId() == WrathIII ) + { + oldStatus = effectIt.second; + effectToApply = WrathIV; + parry += 2; + break; + } + else if( effectIt.second->getId() == WrathIV ) + { + oldStatus = effectIt.second; + effectToApply = Infuriated; + parry += 2; + break; + } } if( !player.hasStatusEffect( Infuriated ) ) - { - pActionBuilder->applyStatusEffectSelf( effectToApply, 30000, 0, { StatusModifier{ Common::ParamModifier::ParryPercent, parry } }, 0, false ); + { + if( oldStatus ) + pActionBuilder->replaceStatusEffectSelf( oldStatus, effectToApply, 30000, 0, { StatusModifier{ Common::ParamModifier::ParryPercent, parry } } ); + else + pActionBuilder->applyStatusEffectSelf( effectToApply, 30000, 0, { StatusModifier{ Common::ParamModifier::ParryPercent, parry } } ); } } \ No newline at end of file diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index 9593b045..7a068c90 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -49,12 +49,6 @@ Chara::Chara( ObjKind type ) : m_lastAttack = Common::Util::getTimeMs(); m_bonusStats.fill( 0 ); - - // initialize the free slot queue - for( uint8_t i = 0; i < MAX_STATUS_EFFECTS; i++ ) - { - m_statusEffectFreeSlotQueue.push( i ); - } } Chara::~Chara() = default; @@ -521,18 +515,31 @@ int8_t Chara::getStatusEffectFreeSlot() { int8_t freeEffectSlot = -1; - if( m_statusEffectFreeSlotQueue.empty() ) + if( m_statusEffectSlots.size() >= MAX_STATUS_EFFECTS ) return freeEffectSlot; - freeEffectSlot = static_cast< int8_t >( m_statusEffectFreeSlotQueue.front() ); - m_statusEffectFreeSlotQueue.pop(); + if( m_statusEffectSlots.empty() ) + freeEffectSlot = 0; + else + freeEffectSlot = static_cast< int8_t >( *m_statusEffectSlots.rbegin() + 1 ); + + m_statusEffectSlots.insert( freeEffectSlot ); + + Logger::warn( "Slot id being added: {}", freeEffectSlot ); return freeEffectSlot; } void Chara::statusEffectFreeSlot( uint8_t slotId ) { - m_statusEffectFreeSlotQueue.push( slotId ); + m_statusEffectSlots.erase( slotId ); +} + +void Chara::replaceSingleStatusEffect( uint32_t slotId, StatusEffect::StatusEffectPtr pStatus ) +{ + pStatus->setSlot( slotId ); + m_statusEffectMap[ slotId ] = pStatus; + pStatus->applyStatus(); } void Chara::replaceSingleStatusEffectById( uint32_t id ) @@ -588,7 +595,7 @@ void Chara::removeStatusEffectByFlag( Common::StatusEffectFlag flag ) } } -std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr >::iterator Chara::removeStatusEffect( uint8_t effectSlotId, bool sendOrder ) +std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr >::iterator Chara::removeStatusEffect( uint8_t effectSlotId, bool updateStatus ) { auto pEffectIt = m_statusEffectMap.find( effectSlotId ); if( pEffectIt == m_statusEffectMap.end() ) @@ -599,11 +606,35 @@ std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr >::iterator Chara::re auto pEffect = pEffectIt->second; pEffect->removeStatus(); - if( sendOrder ) + if( updateStatus ) + { Network::Util::Packet::sendActorControl( getInRangePlayerIds( isPlayer() ), getId(), StatusEffectLose, pEffect->getId() ); + Network::Util::Packet::sendHudParam( *this ); + } auto it = m_statusEffectMap.erase( pEffectIt ); - Network::Util::Packet::sendHudParam( *this ); + + for( auto effectIt = it; effectIt != m_statusEffectMap.end(); ) + { + // if the status is *after* the one being removed, shift the slots down by one + auto shifted_slot = effectIt->first - 1; + + auto node_slot = m_statusEffectSlots.extract( effectIt->first ); + node_slot.value() = shifted_slot; + m_statusEffectSlots.insert( std::move( node_slot ) ); + + auto node_status = m_statusEffectMap.extract( effectIt->first ); + node_status.key() = shifted_slot; + m_statusEffectMap.insert( std::move( node_status ) ); + + effectIt->second->setSlot( effectIt->second->getSlot() - 1 ); + + Logger::warn( "Shifted slot {} to slot: {}", effectSlotId, shifted_slot ); + ++effectIt; + } + + Logger::warn( "Slot id being freed: {}", effectSlotId ); + return it; } diff --git a/src/world/Actor/Chara.h b/src/world/Actor/Chara.h index 212ea1de..30c2fcbf 100644 --- a/src/world/Actor/Chara.h +++ b/src/world/Actor/Chara.h @@ -86,9 +86,9 @@ namespace Sapphire::Entity /*! Status effects */ const uint8_t MAX_STATUS_EFFECTS = 30; - std::queue< uint8_t > m_statusEffectFreeSlotQueue; std::vector< std::pair< uint8_t, uint32_t > > m_statusEffectList; std::map< uint8_t, StatusEffect::StatusEffectPtr > m_statusEffectMap; + std::set< uint8_t > m_statusEffectSlots; /*! Detour Crowd AgentId */ uint32_t m_agentId; @@ -108,7 +108,9 @@ namespace Sapphire::Entity /// Status effect functions void addStatusEffect( StatusEffect::StatusEffectPtr pEffect ); - std::map< uint8_t, StatusEffect::StatusEffectPtr >::iterator removeStatusEffect( uint8_t effectSlotId, bool sendOrder = true ); + std::map< uint8_t, StatusEffect::StatusEffectPtr >::iterator removeStatusEffect( uint8_t effectSlotId, bool updateStatus = true ); + + void replaceSingleStatusEffect( uint32_t slotId, StatusEffect::StatusEffectPtr pStatus ); void replaceSingleStatusEffectById( uint32_t id ); diff --git a/src/world/StatusEffect/StatusEffect.cpp b/src/world/StatusEffect/StatusEffect.cpp index 0bb160a0..98be7dbe 100644 --- a/src/world/StatusEffect/StatusEffect.cpp +++ b/src/world/StatusEffect/StatusEffect.cpp @@ -147,14 +147,17 @@ void Sapphire::StatusEffect::StatusEffect::applyStatus() m_startTime = Util::getTimeMs(); auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); - for( const auto& mod : m_statusModifiers ) - { - if( mod.modifier != Common::ParamModifier::TickDamage && mod.modifier != Common::ParamModifier::TickHeal ) - setModifier( mod.modifier, mod.value ); - else if( mod.modifier == Common::ParamModifier::TickDamage ) - registerTickEffect( mod.modifier, mod.value ); - else if( mod.modifier == Common::ParamModifier::TickHeal ) - registerTickEffect( mod.modifier, mod.value ); + if( m_modifiers.empty() ) + { + for( const auto& mod : m_statusModifiers ) + { + if( mod.modifier != Common::ParamModifier::TickDamage && mod.modifier != Common::ParamModifier::TickHeal ) + setModifier( mod.modifier, mod.value ); + else if( mod.modifier == Common::ParamModifier::TickDamage ) + registerTickEffect( mod.modifier, mod.value ); + else if( mod.modifier == Common::ParamModifier::TickHeal ) + registerTickEffect( mod.modifier, mod.value ); + } } m_targetActor->calculateStats(); @@ -237,3 +240,14 @@ void Sapphire::StatusEffect::StatusEffect::setSlot( uint8_t slot ) { m_slot = slot; } + +void Sapphire::StatusEffect::StatusEffect::refresh() +{ + applyStatus(); +} + +void Sapphire::StatusEffect::StatusEffect::refresh( uint32_t newDuration ) +{ + m_duration = newDuration; + refresh(); +} \ No newline at end of file diff --git a/src/world/StatusEffect/StatusEffect.h b/src/world/StatusEffect/StatusEffect.h index f8a24dde..b72e7058 100644 --- a/src/world/StatusEffect/StatusEffect.h +++ b/src/world/StatusEffect/StatusEffect.h @@ -67,6 +67,8 @@ public: uint8_t getSlot() const; void setSlot( uint8_t slot ); + void refresh( uint32_t newDuration ); + private: uint32_t m_id; Entity::CharaPtr m_sourceActor; @@ -83,6 +85,7 @@ private: std::unordered_map< Common::ParamModifier, int32_t > m_modifiers; uint8_t m_slot; + void refresh(); }; }