From 9a9ce6d02ebd1a49b521d0f75d5632d50372f95c Mon Sep 17 00:00:00 2001 From: collett Date: Wed, 5 Feb 2020 18:42:30 +0900 Subject: [PATCH] Implement dodge, block, parry. --- src/common/Common.h | 1 + src/world/Action/Action.cpp | 83 +++++++++++++++++++++++------- src/world/Action/EffectBuilder.cpp | 21 ++++++++ src/world/Action/EffectBuilder.h | 8 +++ src/world/Action/EffectResult.cpp | 25 +++++++++ src/world/Action/EffectResult.h | 3 ++ src/world/Math/CalcStats.cpp | 73 +++++++++++++++++++++++++- src/world/Math/CalcStats.h | 14 ++++- 8 files changed, 206 insertions(+), 22 deletions(-) diff --git a/src/common/Common.h b/src/common/Common.h index f78a7a67..0cae99dc 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -1044,6 +1044,7 @@ namespace Sapphire::Common MPRestore = 11, Haste = 12, InstantCast = 13, + BlockParryRateBonus = 14, }; enum class ActionTypeFilter : int32_t diff --git a/src/world/Action/Action.cpp b/src/world/Action/Action.cpp index eb03c670..bfaa322b 100644 --- a/src/world/Action/Action.cpp +++ b/src/world/Action/Action.cpp @@ -475,14 +475,52 @@ void Action::Action::buildEffects() if( m_lutEntry.damagePotency > 0 ) { Common::AttackType attackType = static_cast< Common::AttackType >( m_actionData->attackType ); - + actor->onActionHostile( m_pSource ); + auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.damageComboPotency : m_lutEntry.damagePotency ); dmg.first = Math::CalcStats::applyDamageReceiveMultiplier( *actor, dmg.first, attackType ); + + float originalDamage = dmg.first; + bool dodged = false; + float blocked = 0; + float parried = 0; + + if( dmg.first > 0 ) + { + dodged = Math::CalcStats::calcDodge( *actor ); + + if( !dodged && dmg.second == Common::ActionHitSeverityType::NormalDamage && actor->isPlayer() ) + { + blocked = Math::CalcStats::calcBlock( *actor, dmg.first ); + } + + if( !dodged && blocked == 0 && dmg.second == Common::ActionHitSeverityType::NormalDamage && actor->isPlayer() ) + { + if( isPhysical() ) + { + parried = Math::CalcStats::calcParry( *actor, dmg.first ); + } + } + } + + if( dodged ) + dmg.first = 0; + else + { + dmg.first -= blocked; + dmg.first -= parried; + } + if( dmg.first > 0 ) { - actor->onActionHostile( m_pSource ); dmg.first = actor->applyShieldProtection( dmg.first ); - m_effectBuilder->damage( actor, actor, dmg.first, dmg.second, dmg.first == 0 ? Common::ActionEffectResultFlag::Absorbed : Common::ActionEffectResultFlag::None ); + if( blocked > 0 ) + m_effectBuilder->blockedDamage( actor, actor, dmg.first, static_cast< uint16_t >( blocked / originalDamage * 100 ) , dmg.first == 0 ? Common::ActionEffectResultFlag::Absorbed : Common::ActionEffectResultFlag::None ); + else if (parried > 0 ) + m_effectBuilder->parriedDamage( actor, actor, dmg.first, static_cast< uint16_t >( parried / originalDamage * 100 ), dmg.first == 0 ? Common::ActionEffectResultFlag::Absorbed : Common::ActionEffectResultFlag::None ); + else + m_effectBuilder->damage( actor, actor, dmg.first, dmg.second, dmg.first == 0 ? Common::ActionEffectResultFlag::Absorbed : Common::ActionEffectResultFlag::None ); + if( m_isAutoAttack && m_pSource->isPlayer() ) { @@ -494,31 +532,38 @@ void Action::Action::buildEffects() } } } - } - auto reflectDmg = Math::CalcStats::calcDamageReflect( m_pSource, actor, dmg.first, - attackType == Common::AttackType::Physical ? Common::ActionTypeFilter::Physical : - ( attackType == Common::AttackType::Magical ? Common::ActionTypeFilter::Magical : Common::ActionTypeFilter::Unknown ) ); - if( reflectDmg.first > 0 ) + auto reflectDmg = Math::CalcStats::calcDamageReflect( m_pSource, actor, dmg.first, + attackType == Common::AttackType::Physical ? Common::ActionTypeFilter::Physical : + ( attackType == Common::AttackType::Magical ? Common::ActionTypeFilter::Magical : Common::ActionTypeFilter::Unknown ) ); + if( reflectDmg.first > 0 ) + { + m_effectBuilder->damage( actor, m_pSource, reflectDmg.first, reflectDmg.second, Common::ActionEffectResultFlag::Reflected ); + } + + auto absorb = Math::CalcStats::calcAbsorbHP( m_pSource, dmg.first ); + if( absorb > 0 ) + { + if( absorb > actor->getHp() ) + absorb = actor->getHp(); + m_effectBuilder->heal( actor, m_pSource, absorb, Common::ActionHitSeverityType::NormalHeal, Common::ActionEffectResultFlag::EffectOnSource ); + } + } + else { - m_effectBuilder->damage( actor, m_pSource, reflectDmg.first, reflectDmg.second, Common::ActionEffectResultFlag::Reflected ); + if( dodged ) + { + m_effectBuilder->dodge( actor, actor ); + } } - auto absorb = Math::CalcStats::calcAbsorbHP( m_pSource, dmg.first, Common::ActionTypeFilter::All ); - if( absorb > 0 ) - { - if( absorb > actor->getHp() ) - absorb = actor->getHp(); - m_effectBuilder->heal( actor, m_pSource, absorb, Common::ActionHitSeverityType::NormalHeal, Common::ActionEffectResultFlag::EffectOnSource ); - } - - if( isCorrectCombo() && shouldApplyComboSucceedEffect ) + if( dmg.first > 0 && isCorrectCombo() && shouldApplyComboSucceedEffect ) { m_effectBuilder->comboSucceed( actor ); shouldApplyComboSucceedEffect = false; } - if( !isComboAction() || isCorrectCombo() ) + if( dmg.first > 0 && ( !isComboAction() || isCorrectCombo() ) ) { if ( !m_actionData->preservesCombo ) // this matches retail packet, on all standalone actions even casts. { diff --git a/src/world/Action/EffectBuilder.cpp b/src/world/Action/EffectBuilder.cpp index 2717c0ff..63c84c5c 100644 --- a/src/world/Action/EffectBuilder.cpp +++ b/src/world/Action/EffectBuilder.cpp @@ -57,6 +57,13 @@ void EffectBuilder::restoreMP( Entity::CharaPtr& target, Entity::CharaPtr& resto moveToResultList( target, nextResult ); } +void EffectBuilder::dodge( Entity::CharaPtr& effectTarget, Entity::CharaPtr& dodgingTarget, Common::ActionEffectResultFlag flag ) +{ + EffectResultPtr nextResult = make_EffectResult( dodgingTarget, 0 ); + nextResult->dodge( flag ); + moveToResultList( effectTarget, nextResult ); +} + void EffectBuilder::damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag, uint64_t resultDelayMs ) { EffectResultPtr nextResult = make_EffectResult( damagingTarget, Common::Util::getTimeMs() + resultDelayMs ); @@ -64,6 +71,20 @@ void EffectBuilder::damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& da moveToResultList( effectTarget, nextResult ); } +void EffectBuilder::blockedDamage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag, uint64_t resultDelayMs ) +{ + EffectResultPtr nextResult = make_EffectResult( damagingTarget, Common::Util::getTimeMs() + resultDelayMs ); + nextResult->blockedDamage( amount, rate, flag ); + moveToResultList( effectTarget, nextResult ); +} + +void EffectBuilder::parriedDamage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag, uint64_t resultDelayMs ) +{ + EffectResultPtr nextResult = make_EffectResult( damagingTarget, Common::Util::getTimeMs() + resultDelayMs ); + nextResult->parriedDamage( amount, rate, flag ); + moveToResultList( effectTarget, nextResult ); +} + void EffectBuilder::startCombo( Entity::CharaPtr& target, uint16_t actionId ) { EffectResultPtr nextResult = make_EffectResult( target, 0 ); diff --git a/src/world/Action/EffectBuilder.h b/src/world/Action/EffectBuilder.h index 97a6484c..799d87e4 100644 --- a/src/world/Action/EffectBuilder.h +++ b/src/world/Action/EffectBuilder.h @@ -20,9 +20,17 @@ namespace Sapphire::World::Action void restoreMP( Entity::CharaPtr& effectTarget, Entity::CharaPtr& restoringTarget, uint32_t amount, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 ); + void dodge( Entity::CharaPtr& effectTarget, Entity::CharaPtr& dodgingTarget, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); + void damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalDamage, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 ); + + void blockedDamage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, uint16_t rate, + Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 ); + + void parriedDamage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, uint16_t rate, + Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None, uint64_t resultDelayMs = 600 ); void startCombo( Entity::CharaPtr& target, uint16_t actionId ); diff --git a/src/world/Action/EffectResult.cpp b/src/world/Action/EffectResult.cpp index 3e279692..7e38db2d 100644 --- a/src/world/Action/EffectResult.cpp +++ b/src/world/Action/EffectResult.cpp @@ -50,6 +50,13 @@ uint64_t EffectResult::getDelay() return m_delayMs; } +void EffectResult::dodge( Common::ActionEffectResultFlag flag ) +{ + m_flag = flag; + + m_type = Common::ActionEffectType::Miss; +} + void EffectResult::damage( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag ) { m_param0 = static_cast< uint8_t >( severity ); @@ -59,6 +66,24 @@ void EffectResult::damage( uint32_t amount, Common::ActionHitSeverityType severi m_type = Common::ActionEffectType::Damage; } +void EffectResult::blockedDamage( uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag ) +{ + m_value = amount; + m_flag = flag; + m_param2 = rate; + + m_type = Common::ActionEffectType::BlockedDamage; +} + +void EffectResult::parriedDamage( uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag ) +{ + m_value = amount; + m_flag = flag; + m_param2 = rate; + + m_type = Common::ActionEffectType::ParriedDamage; +} + void EffectResult::heal( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag ) { m_param1 = static_cast< uint8_t >( severity ); diff --git a/src/world/Action/EffectResult.h b/src/world/Action/EffectResult.h index 02ec2d78..39a184bc 100644 --- a/src/world/Action/EffectResult.h +++ b/src/world/Action/EffectResult.h @@ -18,7 +18,10 @@ namespace Sapphire::World::Action explicit EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, uint64_t delayMs ); explicit EffectResult( Entity::CharaPtr target, uint64_t delayMs ); + void dodge( Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); void damage( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); + void blockedDamage( uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); + void parriedDamage( uint32_t amount, uint16_t rate, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); void heal( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); void restoreMP( uint32_t amount, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None ); void startCombo( uint16_t actionId ); diff --git a/src/world/Math/CalcStats.cpp b/src/world/Math/CalcStats.cpp index 90190678..57768845 100644 --- a/src/world/Math/CalcStats.cpp +++ b/src/world/Math/CalcStats.cpp @@ -185,13 +185,50 @@ uint32_t CalcStats::calculateMaxHp( PlayerPtr pPlayer, Sapphire::FrameworkPtr pF return result; } +float CalcStats::dodgeProbability( const Sapphire::Entity::Chara& chara ) +{ + // fake value: 5% for players. + return chara.isPlayer() ? 5 : 0; +} + float CalcStats::blockProbability( const Chara& chara ) { + // fake value: 20% for pld. + float result = chara.getClass() == Common::ClassJob::Paladin ? 20 : 0; + /* auto level = chara.getLevel(); auto blockRate = static_cast< float >( chara.getStatValue( Common::BaseParam::BlockRate ) ); auto levelVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] ); - return std::floor( ( 30 * blockRate ) / levelVal + 10 ); + float result = std::floor( ( 30 * blockRate ) / levelVal + 10 ); + */ + for( auto const& entry : chara.getStatusEffectMap() ) + { + auto status = entry.second; + auto effectEntry = status->getEffectEntry(); + if( static_cast< Common::StatusEffectType >( effectEntry.effectType ) != Common::StatusEffectType::BlockParryRateBonus ) + continue; + result += effectEntry.effectValue2; + } + + return result; +} + +float CalcStats::parryProbability( const Sapphire::Entity::Chara& chara ) +{ + // fake value: 10% for players. + float result = chara.isPlayer() ? 10 : 0; + + for( auto const& entry : chara.getStatusEffectMap() ) + { + auto status = entry.second; + auto effectEntry = status->getEffectEntry(); + if( static_cast< Common::StatusEffectType >( effectEntry.effectType ) != Common::StatusEffectType::BlockParryRateBonus ) + continue; + result += effectEntry.effectValue3; + } + + return result; } float CalcStats::directHitProbability( const Chara& chara, Sapphire::Common::CritDHBonusFilter filter ) @@ -455,6 +492,11 @@ float CalcStats::blockStrength( const Sapphire::Entity::Chara& chara ) return std::floor( ( 30 * blockStrength ) / levelVal + 10 ) / 100.f; } +float CalcStats::parryStrength( const Sapphire::Entity::Chara& chara ) +{ + return 0.15f; +} + float CalcStats::autoAttack( const Sapphire::Entity::Chara& chara ) { // todo: default values for NPCs, not sure what we should have here @@ -780,7 +822,7 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcDamag return std::pair< float, Sapphire::Common::ActionHitSeverityType >( 0, Sapphire::Common::ActionHitSeverityType::NormalDamage ); } -float CalcStats::calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage, Sapphire::Common::ActionTypeFilter filter ) +float CalcStats::calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage ) { float result = 0; for( auto const& entry : pChara->getStatusEffectMap() ) @@ -799,6 +841,33 @@ float CalcStats::calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage, return result; } +bool CalcStats::calcDodge( const Sapphire::Entity::Chara& chara ) +{ + if( dodgeProbability( chara ) > getRandomNumber0To99() ) + { + return true; + } + return false; +} + +float CalcStats::calcBlock( const Sapphire::Entity::Chara& chara, float damage ) +{ + if( blockProbability( chara ) > getRandomNumber0To99() ) + { + return damage * blockStrength( chara ); + } + return 0; +} + +float CalcStats::calcParry( const Sapphire::Entity::Chara& chara, float damage ) +{ + if( parryProbability( chara ) > getRandomNumber0To99() ) + { + return damage * parryStrength( chara ); + } + return 0; +} + uint32_t CalcStats::getRandomNumber0To99() { return range100( rng ); diff --git a/src/world/Math/CalcStats.h b/src/world/Math/CalcStats.h index 1ba22d3f..1a3a2eee 100644 --- a/src/world/Math/CalcStats.h +++ b/src/world/Math/CalcStats.h @@ -15,11 +15,15 @@ namespace Sapphire::Math static uint32_t calculateMaxHp( Sapphire::Entity::PlayerPtr pPlayer, FrameworkPtr pFw ); + static float dodgeProbability( const Sapphire::Entity::Chara& chara ); + /*! * @brief Calculates the probability of a block happening */ static float blockProbability( const Sapphire::Entity::Chara& chara ); + static float parryProbability( const Sapphire::Entity::Chara& chara ); + /*! * @brief Calculates the probability of a direct hit happening */ @@ -115,6 +119,8 @@ namespace Sapphire::Math */ static float blockStrength( const Sapphire::Entity::Chara& chara ); + static float parryStrength( const Sapphire::Entity::Chara& chara ); + static float autoAttack( const Sapphire::Entity::Chara& chara ); /*! @@ -146,7 +152,13 @@ namespace Sapphire::Math static std::pair< float, Sapphire::Common::ActionHitSeverityType > calcDamageReflect( Sapphire::Entity::CharaPtr pCharaAttacker, Sapphire::Entity::CharaPtr pCharaVictim, float damage, Sapphire::Common::ActionTypeFilter filter ); - static float calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage, Sapphire::Common::ActionTypeFilter filter ); + static float calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage ); + + static bool calcDodge( const Sapphire::Entity::Chara& chara ); + + static float calcBlock( const Sapphire::Entity::Chara& chara, float damage ); + + static float calcParry( const Sapphire::Entity::Chara& chara, float damage ); static uint32_t getRandomNumber0To99(); private: