diff --git a/src/common/Common.h b/src/common/Common.h index c54db478..adcba688 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -940,6 +940,16 @@ namespace Sapphire::Common RemoveOnSuccessfulHit = 4096 }; + enum class StatusRefreshPolicy : uint8_t + { + Stack = 0, + ReplaceOrApply = 1, + Extend = 2, + ExtendOrApply = 3, + Reject = 4, + Custom = 255 + }; + enum struct ActionAspect : uint8_t { None = 0, // Doesn't imply unaspected 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..533d23a3 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,86 @@ 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: + { + 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 +704,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 +720,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" ) )