mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-05-25 19:17:45 +00:00
Implement dodge, block, parry.
This commit is contained in:
parent
0903f0d2f1
commit
9a9ce6d02e
8 changed files with 206 additions and 22 deletions
|
@ -1044,6 +1044,7 @@ namespace Sapphire::Common
|
|||
MPRestore = 11,
|
||||
Haste = 12,
|
||||
InstantCast = 13,
|
||||
BlockParryRateBonus = 14,
|
||||
};
|
||||
|
||||
enum class ActionTypeFilter : int32_t
|
||||
|
|
|
@ -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.
|
||||
{
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue