1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-05-25 19:17:45 +00:00

Generic action handler update with status support.

This commit is contained in:
collett 2020-01-10 21:24:35 +09:00
parent 38392be3bf
commit 896ce73175
20 changed files with 3088 additions and 1711 deletions

View file

@ -160,7 +160,7 @@ namespace Sapphire::Network::Packets
PlayerStateFlags = 0x02C6, // updated 5.18
PlayerClassInfo = 0x01B0, // updated 5.18
ModelEquip = 0x0170, // updated 5.11
ModelEquip = 0x02E6, // updated 5.18
Examine = 0x0366, // updated 5.18
CharaNameReq = 0x0116, // updated 5.18

View file

@ -1,27 +0,0 @@
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
class ActionSprint3 :
public Sapphire::ScriptAPI::ActionScript
{
public:
ActionSprint3() :
Sapphire::ScriptAPI::ActionScript( 3 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto sourceChara = action.getSourceChara();
if( !sourceChara->isPlayer() )
return;
action.getEffectbuilder()->applyStatusEffect( sourceChara, 50, 30 );
sourceChara->getAsPlayer()->addStatusEffectByIdIfNotExist( 50, 20000, *sourceChara, 30 );
}
};
EXPOSE_SCRIPT( ActionSprint3 );

View file

@ -406,49 +406,12 @@ void Action::Action::execute()
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcDamage( uint32_t potency )
{
// todo: what do for npcs?
auto wepDmg = 1.f;
if( auto player = m_pSource->getAsPlayer() )
{
auto item = player->getEquippedWeapon();
assert( item );
auto role = player->getRole();
if( role == Common::Role::RangedMagical || role == Common::Role::Healer )
{
wepDmg = item->getMagicalDmg();
}
else
{
wepDmg = item->getPhysicalDmg();
}
}
return Math::CalcStats::calcActionDamage( *m_pSource, potency, wepDmg );
return Math::CalcStats::calcActionDamage( *m_pSource, *this, potency, Math::CalcStats::getWeaponDamage( *m_pSource ) );
}
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcHealing( uint32_t potency )
{
auto wepDmg = 1.f;
if( auto player = m_pSource->getAsPlayer() )
{
auto item = player->getEquippedWeapon();
assert( item );
auto role = player->getRole();
if( role == Common::Role::RangedMagical || role == Common::Role::Healer )
{
wepDmg = item->getMagicalDmg();
}
else
{
wepDmg = item->getPhysicalDmg();
}
}
return Math::CalcStats::calcActionHealing( *m_pSource, potency, wepDmg );
return Math::CalcStats::calcActionHealing( *m_pSource, *this, potency, Math::CalcStats::getWeaponDamage( *m_pSource ) );
}
void Action::Action::buildEffects()
@ -468,19 +431,22 @@ void Action::Action::buildEffects()
return;
}
if( !hasLutEntry || m_hitActors.empty() )
if( !hasLutEntry ) // this is just "if ( weCanNotUseGenericActionHandler )" in case we start to expand it.
{
// send any effect packet added by script or an empty one just to play animation for other players
m_effectBuilder->buildAndSendPackets();
return;
}
// no script exists but we have a valid lut entry
// we have a valid lut entry
if( auto player = getSourceChara()->getAsPlayer() )
{
player->sendDebug( "Hit target: pot: {} (c: {}, f: {}, r: {}), heal pot: {}, mpp: {}",
m_lutEntry.potency, m_lutEntry.comboPotency, m_lutEntry.flankPotency, m_lutEntry.rearPotency,
m_lutEntry.curePotency, m_lutEntry.restoreMPPercentage );
player->sendDebug( "type: {}, dpot: {} (dcpot: {}, ddpot: {}), hpot: {}, shpot: {}, ss: {}, ts: {}, gmp: {}",
m_actionData->attackType,
m_lutEntry.damagePotency, m_lutEntry.damageComboPotency, m_lutEntry.damageDirectionalPotency,
m_lutEntry.healPotency, m_lutEntry.selfHealPotency,
m_lutEntry.selfStatus, m_lutEntry.targetStatus,
m_lutEntry.gainMPPercentage );
}
// when aoe, these effects are in the target whatever is hit first
@ -489,9 +455,10 @@ void Action::Action::buildEffects()
for( auto& actor : m_hitActors )
{
if( m_lutEntry.potency > 0 )
if( m_lutEntry.damagePotency > 0 )
{
auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.comboPotency : m_lutEntry.potency );
auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.damageComboPotency : m_lutEntry.damagePotency );
dmg.first = Math::CalcStats::applyDamageReceiveMultiplier( *actor, dmg.first, m_actionData->attackType );
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second );
if( dmg.first > 0 )
@ -505,15 +472,16 @@ void Action::Action::buildEffects()
if( !isComboAction() || isCorrectCombo() )
{
if( m_lutEntry.curePotency > 0 ) // actions with self heal
if( m_lutEntry.selfHealPotency > 0 ) // actions with self heal
{
auto heal = calcHealing( m_lutEntry.curePotency );
auto heal = calcHealing( m_lutEntry.selfHealPotency );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *m_pSource, heal.first, 0 );
m_effectBuilder->heal( actor, m_pSource, heal.first, heal.second, Common::ActionEffectResultFlag::EffectOnSource );
}
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
if( m_lutEntry.gainMPPercentage > 0 && shouldRestoreMP )
{
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.gainMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
shouldRestoreMP = false;
}
@ -523,22 +491,33 @@ void Action::Action::buildEffects()
}
}
}
else if( m_lutEntry.curePotency > 0 )
else if( m_lutEntry.healPotency > 0 )
{
auto heal = calcHealing( m_lutEntry.curePotency );
auto heal = calcHealing( m_lutEntry.healPotency );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *actor, heal.first, 0 );
m_effectBuilder->heal( actor, actor, heal.first, heal.second );
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
if( m_lutEntry.gainMPPercentage > 0 && shouldRestoreMP )
{
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.gainMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
shouldRestoreMP = false;
}
}
else if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
else if( m_lutEntry.gainMPPercentage > 0 && shouldRestoreMP )
{
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.gainMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
shouldRestoreMP = false;
}
if( m_lutEntry.targetStatus != 0 )
{
m_effectBuilder->applyStatusEffect( actor, m_pSource, m_lutEntry.targetStatus, m_lutEntry.targetStatusDuration, m_lutEntry.targetStatusParam );
}
}
if( m_lutEntry.selfStatus != 0 )
{
m_effectBuilder->applyStatusEffect( m_pSource, m_pSource, m_lutEntry.selfStatus, m_lutEntry.selfStatusDuration, m_lutEntry.selfStatusParam );
}
m_effectBuilder->buildAndSendPackets();
@ -565,7 +544,7 @@ bool Action::Action::playerPreCheck( Entity::Player& player )
return false;
// npc actions/non player actions
if( m_actionData->classJob == -1 )
if( m_actionData->classJob == -1 && !m_actionData->isRoleAction )
return false;
if( player.getLevel() < m_actionData->classJobLevel )
@ -574,7 +553,7 @@ bool Action::Action::playerPreCheck( Entity::Player& player )
auto currentClass = player.getClass();
auto actionClass = static_cast< Common::ClassJob >( m_actionData->classJob );
if( actionClass != Common::ClassJob::Adventurer && currentClass != actionClass )
if( actionClass != Common::ClassJob::Adventurer && currentClass != actionClass && !m_actionData->isRoleAction )
{
// check if not a base class action
auto exdData = m_pFw->get< Data::ExdDataGenerated >();
@ -777,16 +756,16 @@ bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
return false;
if( !m_canTargetSelf && chara->getId() == m_pSource->getId() )
if( m_lutEntry.damagePotency > 0 && chara->getId() == m_pSource->getId() ) // !m_canTargetSelf
return false;
if( ( m_lutEntry.potency > 0 || m_lutEntry.curePotency > 0 ) && !chara->isAlive() ) // !m_canTargetDead not working for aoe
if( ( m_lutEntry.damagePotency > 0 || m_lutEntry.healPotency > 0 ) && !chara->isAlive() ) // !m_canTargetDead not working for aoe
return false;
if( m_lutEntry.potency > 0 && m_pSource->getObjKind() == chara->getObjKind() ) // !m_canTargetFriendly not working for aoe
if( m_lutEntry.damagePotency > 0 && m_pSource->getObjKind() == chara->getObjKind() ) // !m_canTargetFriendly not working for aoe
return false;
if( ( m_lutEntry.potency == 0 && m_lutEntry.curePotency > 0 ) && m_pSource->getObjKind() != chara->getObjKind() ) // !m_canTargetHostile not working for aoe
if( ( m_lutEntry.damagePotency == 0 && m_lutEntry.healPotency > 0 ) && m_pSource->getObjKind() != chara->getObjKind() ) // !m_canTargetHostile not working for aoe
return false;
return true;
@ -809,11 +788,40 @@ Sapphire::Entity::CharaPtr Action::Action::getHitChara()
bool Action::Action::hasValidLutEntry() const
{
return m_lutEntry.potency != 0 || m_lutEntry.comboPotency != 0 || m_lutEntry.flankPotency != 0 || m_lutEntry.frontPotency != 0 ||
m_lutEntry.rearPotency != 0 || m_lutEntry.curePotency != 0 || m_lutEntry.restoreMPPercentage != 0;
return m_lutEntry.damagePotency != 0 || m_lutEntry.healPotency != 0 || m_lutEntry.selfHealPotency != 0 || m_lutEntry.selfStatus != 0 ||
m_lutEntry.targetStatus != 0 || m_lutEntry.gainMPPercentage != 0;
}
Action::EffectBuilderPtr Action::Action::getEffectbuilder()
{
return m_effectBuilder;
}
Data::ActionPtr Action::Action::getActionData() const
{
return m_actionData;
}
bool Action::Action::isPhysical() const
{
return isAttackTypePhysical( m_actionData->attackType );
}
bool Action::Action::isMagical() const
{
return isAttackTypeMagical( m_actionData->attackType );
}
bool Action::Action::isAttackTypePhysical( int8_t attackType )
{
return attackType == -1 ||
attackType == 1 ||
attackType == 2 ||
attackType == 3 ||
attackType == 4;
}
bool Action::Action::isAttackTypeMagical( int8_t attackType )
{
return attackType == 5;
}

View file

@ -118,6 +118,14 @@ namespace Sapphire::World::Action
*/
Entity::CharaPtr getHitChara();
Data::ActionPtr getActionData() const;
bool isPhysical() const;
bool isMagical() const;
static bool isAttackTypePhysical( int8_t attackType );
static bool isAttackTypeMagical( int8_t attackType );
/*!
* @brief Starts the cast. Finishes it immediately if there is no cast time (weaponskills).
*/

View file

@ -12,9 +12,9 @@ bool ActionLut::validEntryExists( uint16_t actionId )
const auto& entry = it->second;
// if all of the fields are 0, it's not 'valid' due to parse error or no useful data in the tooltip
return entry.potency != 0 || entry.comboPotency != 0 || entry.flankPotency != 0 || entry.frontPotency != 0 ||
entry.rearPotency != 0 || entry.curePotency != 0;
// if all of these fields are 0, it's not 'valid' due to parse error or no useful data
return entry.damagePotency != 0 || entry.healPotency != 0 || entry.selfHealPotency != 0 || entry.selfStatus != 0 ||
entry.targetStatus != 0 || entry.gainMPPercentage != 0;
}
const ActionEntry& ActionLut::getEntry( uint16_t actionId )
@ -25,3 +25,24 @@ const ActionEntry& ActionLut::getEntry( uint16_t actionId )
return it->second;
}
bool ActionLut::validStatusEffectExists( uint16_t statusId )
{
auto it = m_statusEffectTable.find( statusId );
if( it == m_statusEffectTable.end() )
return false;
const auto& entry = it->second;
return entry.effectType != 0;
}
const StatusEffectEntry& ActionLut::getStatusEffectEntry( uint16_t statusId )
{
auto it = m_statusEffectTable.find( statusId );
assert( it != m_statusEffectTable.end() );
return it->second;
}

View file

@ -7,24 +7,60 @@ namespace Sapphire::World::Action
{
struct ActionEntry
{
uint16_t potency;
uint16_t comboPotency;
uint16_t flankPotency;
uint16_t frontPotency;
uint16_t rearPotency;
uint16_t curePotency;
uint16_t restoreMPPercentage;
uint16_t damagePotency;
uint16_t damageComboPotency;
uint16_t damageDirectionalPotency;
uint16_t healPotency;
uint16_t selfHealPotency;
uint16_t selfStatus;
uint32_t selfStatusDuration;
uint16_t selfStatusParam;
uint16_t targetStatus;
uint32_t targetStatusDuration;
uint16_t targetStatusParam;
uint16_t gainMPPercentage;
};
const uint32_t EffectTypeInvalid = 0;
const uint32_t EffectTypeDamageMultiplier = 1;
const uint32_t EffectTypeDamageReceiveMultiplier = 2;
const uint32_t EffectTypeHot = 3;
const uint32_t EffectTypeDot = 4;
const uint32_t EffectTypeHealReceiveMultiplier = 5;
const uint32_t EffectTypeHealCastMultiplier = 6;
const uint32_t EffectTypeCritDHRateBonus = 7;
const uint8_t EffectActionTypeFilterPhysical = 1;
const uint8_t EffectActionTypeFilterMagical = 2;
const uint8_t EffectActionTypeFilterAll = 255;
const uint8_t EffectCritDHBonusFilterDamage = 1;
const uint8_t EffectCritDHBonusFilterHeal = 2;
const uint8_t EffectCritDHBonusFilterAll = 255;
struct StatusEffectEntry
{
uint32_t effectType;
int32_t effectValue1;
int32_t effectValue2;
int32_t effectValue3;
int32_t effectValue4;
};
class ActionLut
{
public:
using Lut = std::unordered_map< uint16_t, ActionEntry >;
using StatusEffectTable = std::unordered_map< uint16_t, StatusEffectEntry >;
static bool validEntryExists( uint16_t actionId );
static const ActionEntry& getEntry( uint16_t actionId );
static bool validStatusEffectExists( uint16_t statusId );
static const StatusEffectEntry& getStatusEffectEntry( uint16_t statusId );
static Lut m_actionLut;
static StatusEffectTable m_statusEffectTable;
};
}

File diff suppressed because it is too large Load diff

View file

@ -51,43 +51,43 @@ void EffectBuilder::moveToResultList( Entity::CharaPtr& chara, EffectResultPtr r
void EffectBuilder::heal( Entity::CharaPtr& effectTarget, Entity::CharaPtr& healingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
{
EffectResultPtr nextResult = make_EffectResult( healingTarget, getResultDelayMs() );
EffectResultPtr nextResult = make_EffectResult( healingTarget, nullptr, getResultDelayMs() );
nextResult->heal( amount, severity, flag );
moveToResultList( effectTarget, nextResult );
}
void EffectBuilder::restoreMP( Entity::CharaPtr& target, Entity::CharaPtr& restoringTarget, uint32_t amount, Common::ActionEffectResultFlag flag )
{
EffectResultPtr nextResult = make_EffectResult( restoringTarget, getResultDelayMs() ); // restore mp source actor
EffectResultPtr nextResult = make_EffectResult( restoringTarget, nullptr, getResultDelayMs() ); // restore mp source actor
nextResult->restoreMP( amount, flag );
moveToResultList( target, nextResult );
}
void EffectBuilder::damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
{
EffectResultPtr nextResult = make_EffectResult( damagingTarget, getResultDelayMs() );
EffectResultPtr nextResult = make_EffectResult( damagingTarget, nullptr, getResultDelayMs() );
nextResult->damage( amount, severity, flag );
moveToResultList( effectTarget, nextResult );
}
void EffectBuilder::startCombo( Entity::CharaPtr& target, uint16_t actionId )
{
EffectResultPtr nextResult = make_EffectResult( target, 0 );
EffectResultPtr nextResult = make_EffectResult( target, nullptr, 0 );
nextResult->startCombo( actionId );
moveToResultList( target, nextResult );
}
void EffectBuilder::comboSucceed( Entity::CharaPtr& target )
{
EffectResultPtr nextResult = make_EffectResult( target, 0 );
EffectResultPtr nextResult = make_EffectResult( target, nullptr, 0 );
nextResult->comboSucceed();
moveToResultList( target, nextResult );
}
void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint8_t param )
void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint8_t param )
{
EffectResultPtr nextResult = make_EffectResult( target, 0 );
nextResult->applyStatusEffect( statusId, param );
EffectResultPtr nextResult = make_EffectResult( target, source, 0 );
nextResult->applyStatusEffect( statusId, duration, param );
moveToResultList( target, nextResult );
}

View file

@ -26,7 +26,7 @@ namespace Sapphire::World::Action
void comboSucceed( Entity::CharaPtr& target );
void applyStatusEffect( Entity::CharaPtr& target, uint16_t statusId, uint8_t param );
void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint8_t param );
void buildAndSendPackets();

View file

@ -8,19 +8,26 @@ using namespace Sapphire;
using namespace Sapphire::World::Action;
EffectResult::EffectResult( Entity::CharaPtr target, uint64_t runAfter ) :
EffectResult::EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, uint64_t runAfter ) :
m_target( std::move( target ) ),
m_source( std::move( source ) ),
m_delayMs( runAfter ),
m_type( Common::ActionEffectType::Nothing ),
m_value( 0 ),
m_value2( 0 ),
m_param0( 0 ),
m_param1( 0 ),
m_type( Common::ActionEffectType::Nothing ),
m_param2( 0 ),
m_flag( Common::ActionEffectResultFlag::None )
{
}
Entity::CharaPtr EffectResult::getSource() const
{
return m_source;
}
Entity::CharaPtr EffectResult::getTarget() const
{
return m_target;
@ -76,9 +83,10 @@ void EffectResult::comboSucceed()
m_type = Common::ActionEffectType::ComboSucceed;
}
void EffectResult::applyStatusEffect( uint16_t statusId, uint8_t param )
void EffectResult::applyStatusEffect( uint16_t statusId, uint32_t duration, uint8_t param )
{
m_value = statusId;
m_value2 = duration;
m_param2 = param;
m_type = Common::ActionEffectType::ApplyStatusEffect;
@ -121,6 +129,12 @@ void EffectResult::execute()
break;
}
case Common::ActionEffectType::ApplyStatusEffect:
{
m_target->addStatusEffectById( m_value, m_value2, *m_source, m_param2 );
break;
}
default:
break;
}

View file

@ -13,15 +13,16 @@ namespace Sapphire::World::Action
class EffectResult
{
public:
explicit EffectResult( Entity::CharaPtr target, uint64_t delayMs );
explicit EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, uint64_t delayMs );
void damage( uint32_t amount, Common::ActionHitSeverityType severity, 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 );
void comboSucceed();
void applyStatusEffect( uint16_t statusId, uint8_t param );
void applyStatusEffect( uint16_t statusId, uint32_t duration, uint8_t param );
Entity::CharaPtr getSource() const;
Entity::CharaPtr getTarget() const;
uint32_t getValue() const;
@ -35,6 +36,7 @@ namespace Sapphire::World::Action
private:
uint64_t m_delayMs;
Entity::CharaPtr m_source;
Entity::CharaPtr m_target;
Common::ActionEffectType m_type;
@ -44,6 +46,7 @@ namespace Sapphire::World::Action
uint8_t m_param2;
uint32_t m_value;
uint32_t m_value2;
Common::ActionEffectResultFlag m_flag;
};
}

View file

@ -693,7 +693,7 @@ void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
auto pRNGMgr = m_pFw->get< World::Manager::RNGMgr >();
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
damage.first = Math::CalcStats::applyDamageReceiveMultiplier( *pTarget, damage.first, -1 );
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
Common::EffectEntry effectEntry{};

View file

@ -354,6 +354,8 @@ bool Sapphire::Entity::Chara::checkAction()
void Sapphire::Entity::Chara::update( uint64_t tickCount )
{
updateStatusEffects();
if( std::difftime( static_cast< time_t >( tickCount ), m_lastTickTime ) > 3000 )
{
onTick();
@ -388,8 +390,6 @@ uint8_t Sapphire::Entity::Chara::getLevel() const
Let an actor take damage and perform necessary steps
according to resulting hp, propagates new hp value to players
in range
TODO: eventually this needs to distinguish between physical and
magical dmg and take status effects into account
\param amount of damage to be taken
*/
@ -502,6 +502,7 @@ void Sapphire::Entity::Chara::autoAttack( CharaPtr pTarget )
srand( static_cast< uint32_t >( tick ) );
auto damage = static_cast< uint16_t >( 10 + rand() % 12 );
damage = Math::CalcStats::applyDamageReceiveMultiplier( *pTarget, damage, -1 );
auto effectPacket = std::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 7 );
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
@ -557,6 +558,9 @@ void Sapphire::Entity::Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEf
/*! \param StatusEffectPtr to be applied to the actor */
void Sapphire::Entity::Chara::addStatusEffectById( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param )
{
if( hasStatusEffect( id ) ) // todo: check if we want to refresh it or discard and keep the old one
removeSingleStatusEffectById( id, false );
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000, m_pFw );
effect->setParam( param );
addStatusEffect( effect );
@ -593,19 +597,19 @@ void Sapphire::Entity::Chara::statusEffectFreeSlot( uint8_t slotId )
m_statusEffectFreeSlotQueue.push( slotId );
}
void Sapphire::Entity::Chara::removeSingleStatusEffectById( uint32_t id )
void Sapphire::Entity::Chara::removeSingleStatusEffectById( uint32_t id, bool sendPacket )
{
for( auto effectIt : m_statusEffectMap )
{
if( effectIt.second->getId() == id )
{
removeStatusEffect( effectIt.first );
removeStatusEffect( effectIt.first, sendPacket );
break;
}
}
}
void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId )
void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId, bool sendPacket )
{
auto pEffectIt = m_statusEffectMap.find( effectSlotId );
if( pEffectIt == m_statusEffectMap.end() )
@ -616,7 +620,8 @@ void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId )
auto pEffect = pEffectIt->second;
pEffect->removeStatus();
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), isPlayer() );
if( sendPacket )
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), isPlayer() );
m_statusEffectMap.erase( effectSlotId );
@ -694,10 +699,10 @@ void Sapphire::Entity::Chara::updateStatusEffects()
uint32_t duration = effect->getDuration();
uint32_t tickRate = effect->getTickRate();
if( ( currentTimeMs - startTime ) > duration )
if( duration > 0 && ( currentTimeMs - startTime ) > duration )
{
// remove status effect
removeStatusEffect( effectIndex );
removeStatusEffect( effectIndex, true );
// break because removing invalidates iterators
break;
}
@ -733,20 +738,28 @@ void Sapphire::Entity::Chara::updateStatusEffects()
{
takeDamage( thisTickDmg );
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,
static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ) );
static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ), true );
}
if( thisTickHeal != 0 )
{
heal( thisTickDmg );
heal( thisTickHeal );
sendToInRangeSet( makeActorControl( getId(), HPFloatingText, 0,
static_cast< uint8_t >( ActionEffectType::Heal ), thisTickHeal ) );
static_cast< uint8_t >( ActionEffectType::Heal ), thisTickHeal ), true );
}
}
bool Sapphire::Entity::Chara::hasStatusEffect( uint32_t id )
{
return m_statusEffectMap.find( id ) != m_statusEffectMap.end();
//return m_statusEffectMap.find( id ) != m_statusEffectMap.end();
for( auto effectIt : m_statusEffectMap )
{
if( effectIt.second->getId() == id )
{
return true;
}
}
return false;
}
int64_t Sapphire::Entity::Chara::getLastUpdateTime() const

View file

@ -146,9 +146,9 @@ namespace Sapphire::Entity
/// Status effect functions
void addStatusEffect( StatusEffect::StatusEffectPtr pEffect );
void removeStatusEffect( uint8_t effectSlotId );
void removeStatusEffect( uint8_t effectSlotId, bool sendPacket );
void removeSingleStatusEffectById( uint32_t id );
void removeSingleStatusEffectById( uint32_t id, bool sendPacket );
void updateStatusEffects();

View file

@ -1100,8 +1100,6 @@ void Sapphire::Entity::Player::update( uint64_t tickCount )
if( !isAlive() )
return;
updateStatusEffects();
m_lastUpdate = tickCount;
if( !checkAction() )
@ -1578,6 +1576,7 @@ void Sapphire::Entity::Player::autoAttack( CharaPtr pTarget )
auto variation = static_cast< uint32_t >( pRNGMgr->getRandGenerator< float >( 0, 3 ).next() );
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
damage.first = Math::CalcStats::applyDamageReceiveMultiplier( *pTarget, damage.first, -1 );
if( getClass() == ClassJob::Machinist || getClass() == ClassJob::Bard || getClass() == ClassJob::Archer )
{

View file

@ -9,6 +9,10 @@
#include "Inventory/Item.h"
#include "StatusEffect/StatusEffect.h"
#include "Action/Action.h"
#include "CalcStats.h"
#include "Framework.h"
@ -190,7 +194,7 @@ float CalcStats::blockProbability( const Chara& chara )
return std::floor( ( 30 * blockRate ) / levelVal + 10 );
}
float CalcStats::directHitProbability( const Chara& chara )
float CalcStats::directHitProbability( const Chara& chara, uint8_t filterType )
{
const auto& baseStats = chara.getStats();
auto level = chara.getLevel();
@ -200,10 +204,23 @@ float CalcStats::directHitProbability( const Chara& chara )
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
return std::floor( 550.f * ( dhRate - subVal ) / divVal ) / 10.f;
auto result = std::floor( 550.f * ( dhRate - subVal ) / divVal ) / 10.f;
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeCritDHRateBonus )
continue;
if( effectEntry.effectValue1 & filterType )
{
result += effectEntry.effectValue3;
}
}
return result;
}
float CalcStats::criticalHitProbability( const Chara& chara )
float CalcStats::criticalHitProbability( const Chara& chara, uint8_t filterType )
{
const auto& baseStats = chara.getStats();
auto level = chara.getLevel();
@ -213,7 +230,20 @@ float CalcStats::criticalHitProbability( const Chara& chara )
auto divVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::DIV ] );
auto subVal = static_cast< float >( levelTable[ level ][ Common::LevelTableEntry::SUB ] );
return std::floor( 200.f * ( chRate - subVal ) / divVal + 50.f ) / 10.f;
auto result = std::floor( 200.f * ( chRate - subVal ) / divVal + 50.f ) / 10.f;
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeCritDHRateBonus )
continue;
if( effectEntry.effectValue1 & filterType )
{
result += effectEntry.effectValue2;
}
}
return result;
}
@ -328,6 +358,29 @@ float CalcStats::healingMagicPower( const Sapphire::Entity::Chara& chara )
return calcAttackPower( chara, chara.getStatValue( Common::BaseParam::HealingMagicPotency ) );
}
float CalcStats::getWeaponDamage( Sapphire::Entity::Chara& chara )
{
auto wepDmg = chara.getLevel();
if( auto player = chara.getAsPlayer() )
{
auto item = player->getEquippedWeapon();
assert( item );
auto role = player->getRole();
if( role == Common::Role::RangedMagical || role == Common::Role::Healer )
{
wepDmg = item->getMagicalDmg();
}
else
{
wepDmg = item->getPhysicalDmg();
}
}
return wepDmg;
}
float CalcStats::determination( const Sapphire::Entity::Chara& chara )
{
auto level = chara.getLevel();
@ -464,13 +517,13 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
factor = std::floor( factor * speed( chara ) );
if( criticalHitProbability( chara ) > range100( rng ) )
if( criticalHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterDamage ) > range100( rng ) )
{
factor *= criticalHitBonus( chara );
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
}
if( directHitProbability( chara ) > range100( rng ) )
if( directHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterDamage ) > range100( rng ) )
{
factor *= 1.25f;
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
@ -480,7 +533,17 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
// todo: buffs
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeDamageMultiplier )
continue;
if( effectEntry.effectValue1 & Sapphire::World::Action::EffectActionTypeFilterPhysical )
{
factor *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
}
}
constexpr auto format = "auto attack: pot: {} aa: {} ap: {} det: {} ten: {} = {}";
@ -496,7 +559,7 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcAutoA
return std::pair( factor, hitType );
}
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionDamage( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
float CalcStats::calcDamageBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
{
// D = ⌊ f(pot) × f(wd) × f(ap) × f(det) × f(tnc) × traits ⌋
// × f(chr) ⌋ × f(dhr) ⌋ × rand[ 0.95, 1.05 ] ⌋ buff_1 ⌋ × buff_1 ⌋ × buff... ⌋
@ -511,15 +574,49 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
ten = tenacity( chara );
auto factor = std::floor( pot * wd * ap * det * ten );
constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} ten: {} = {}";
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
{
player->sendDebug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor );
}
return factor;
}
float CalcStats::calcHealBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
{
// reused damage formula just for testing
auto pot = potency( static_cast< uint16_t >( ptc ) );
auto wd = weaponDamage( chara, wepDmg );
auto ap = getPrimaryAttackPower( chara );
auto det = determination( chara );
auto factor = std::floor( pot * wd * ap * det );
constexpr auto format = "heal: pot: {} ({}) wd: {} ({}) ap: {} det: {} = {}";
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
{
player->sendDebug( format, pot, ptc, wd, wepDmg, ap, det, factor );
}
return factor;
}
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionDamage( const Sapphire::Entity::Chara& chara, const Sapphire::World::Action::Action& action, uint32_t ptc, float wepDmg )
{
auto factor =calcDamageBaseOnPotency( chara, ptc, wepDmg );
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalDamage;
if( criticalHitProbability( chara ) > range100( rng ) )
if( criticalHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterDamage ) > range100( rng ) )
{
factor *= criticalHitBonus( chara );
hitType = Sapphire::Common::ActionHitSeverityType::CritDamage;
}
if( directHitProbability( chara ) > range100( rng ) )
if( directHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterDamage ) > range100( rng ) )
{
factor *= 1.25f;
hitType = hitType == Sapphire::Common::ActionHitSeverityType::CritDamage ?
@ -529,29 +626,65 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
// todo: buffs
constexpr auto format = "dmg: pot: {} ({}) wd: {} ({}) ap: {} det: {} ten: {} = {}";
if( auto player = const_cast< Entity::Chara& >( chara ).getAsPlayer() )
for( auto const& entry : chara.getStatusEffectMap() )
{
player->sendDebug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor );
}
else
{
Logger::debug( format, pot, ptc, wd, wepDmg, ap, det, ten, factor );
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeDamageMultiplier )
continue;
uint8_t actionType = action.isPhysical() ? Sapphire::World::Action::EffectActionTypeFilterPhysical :
( action.isMagical() ? Sapphire::World::Action::EffectActionTypeFilterMagical : 0 );
if( effectEntry.effectValue1 & actionType )
{
factor *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
}
}
return std::pair( factor, hitType );
}
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionHealing( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg )
float CalcStats::applyDamageReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalDamage, int8_t attackType )
{
// lol just for testing
auto factor = std::floor( ptc * ( wepDmg / 10.0f ) + ptc );
float damage = originalDamage;
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeDamageReceiveMultiplier )
continue;
uint8_t actionType = 0;
if( World::Action::Action::isAttackTypePhysical( attackType ) )
actionType = Sapphire::World::Action::EffectActionTypeFilterPhysical;
else if( World::Action::Action::isAttackTypeMagical( attackType ) )
actionType = Sapphire::World::Action::EffectActionTypeFilterMagical;
if( effectEntry.effectValue1 & actionType )
{
damage *= ( 1.0f + ( effectEntry.effectValue2 / 100.0f ) );
}
}
return damage;
}
float CalcStats::applyHealingReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalHeal, int8_t healType )
{
float heal = originalHeal;
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeHealReceiveMultiplier )
continue;
heal *= ( 1.0f + ( effectEntry.effectValue2 / 100.0f ) );
}
return heal;
}
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActionHealing( const Sapphire::Entity::Chara& chara, const Sapphire::World::Action::Action& action, uint32_t ptc, float wepDmg )
{
auto factor = calcHealBaseOnPotency( chara, ptc, wepDmg );
Sapphire::Common::ActionHitSeverityType hitType = Sapphire::Common::ActionHitSeverityType::NormalHeal;
if( criticalHitProbability( chara ) > range100( rng ) )
if( criticalHitProbability( chara, Sapphire::World::Action::EffectCritDHBonusFilterHeal ) > range100( rng ) )
{
factor *= criticalHitBonus( chara );
hitType = Sapphire::Common::ActionHitSeverityType::CritHeal;
@ -559,6 +692,19 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcActio
factor *= 1.0f + ( ( range100( rng ) - 50.0f ) / 1000.0f );
for( auto const& entry : chara.getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeHealCastMultiplier )
continue;
if( static_cast< Common::ActionCategory >( action.getActionData()->actionCategory ) == Common::ActionCategory::Spell ) // must be a "cast"
{
factor *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
}
}
return std::pair( factor, hitType );
}

View file

@ -26,12 +26,12 @@ namespace Sapphire::Math
/*!
* @brief Calculates the probability of a direct hit happening
*/
static float directHitProbability( const Sapphire::Entity::Chara& chara );
static float directHitProbability( const Sapphire::Entity::Chara& chara, uint8_t filterType );
/*!
* @brief Calculates the probability of a critical hit happening
*/
static float criticalHitProbability( const Sapphire::Entity::Chara& chara );
static float criticalHitProbability( const Sapphire::Entity::Chara& chara, uint8_t filterType );
/*!
* @brief Calculates the contribution of potency to damage output.
@ -64,6 +64,8 @@ namespace Sapphire::Math
static float healingMagicPower( const Sapphire::Entity::Chara& chara );
static float getWeaponDamage( Sapphire::Entity::Chara& chara );
/*!
* @brief Calculates determinations contribution to damage and healing output.
*
@ -129,13 +131,25 @@ namespace Sapphire::Math
////////////////////////////////////////////
static float calcDamageBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
static float calcHealBaseOnPotency( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
static std::pair< float, Common::ActionHitSeverityType > calcAutoAttackDamage( const Sapphire::Entity::Chara& chara );
static std::pair< float, Common::ActionHitSeverityType > calcActionDamage( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
static std::pair< float, Common::ActionHitSeverityType > calcActionDamage( const Sapphire::Entity::Chara& chara, const Sapphire::World::Action::Action& action, uint32_t ptc, float wepDmg );
static std::pair< float, Common::ActionHitSeverityType > calcActionHealing( const Sapphire::Entity::Chara& chara, uint32_t ptc, float wepDmg );
static float applyDamageReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalDamage, int8_t attackType );
static float applyHealingReceiveMultiplier( const Sapphire::Entity::Chara& chara, float originalHeal, int8_t healType );
static std::pair< float, Common::ActionHitSeverityType > calcActionHealing( const Sapphire::Entity::Chara& chara, const Sapphire::World::Action::Action& action, uint32_t ptc, float wepDmg );
static uint32_t primaryStatValue( const Sapphire::Entity::Chara& chara );
static std::random_device dev;
static std::mt19937 rng;
static std::uniform_int_distribution< std::mt19937::result_type > range100;
private:
/*!
@ -144,10 +158,6 @@ namespace Sapphire::Math
* @param attackPower The magic/physical attack power value.
*/
static float calcAttackPower( const Sapphire::Entity::Chara& chara, uint32_t attackPower );
static std::random_device dev;
static std::mt19937 rng;
static std::uniform_int_distribution< std::mt19937::result_type > range100;
};
}

View file

@ -134,7 +134,7 @@ void Sapphire::Network::GameConnection::clientTriggerHandler( FrameworkPtr pFw,
case ClientTriggerType::RemoveStatusEffect: // Remove status (clicking it off)
{
// todo: check if status can be removed by client from exd
player.removeSingleStatusEffectById( static_cast< uint32_t >( param1 ) );
player.removeSingleStatusEffectById( static_cast< uint32_t >( param1 ), true );
break;
}
case ClientTriggerType::CastCancel: // Cancel cast

View file

@ -10,6 +10,8 @@
#include "Script/ScriptMgr.h"
#include "Math/CalcStats.h"
#include "StatusEffect.h"
#include "Framework.h"
@ -26,7 +28,10 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
m_startTime( 0 ),
m_tickRate( tickRate ),
m_lastTick( 0 ),
m_pFw( pFw )
m_pFw( pFw ),
m_cachedHotOrDotValue( 0 ),
m_cachedSourceCrit( 0 ),
m_cachedSourceCritBonus( 0 )
{
auto pExdData = m_pFw->get< Data::ExdDataGenerated >();
auto entry = pExdData->get< Sapphire::Data::Status >( id );
@ -41,6 +46,11 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
Util::eraseAll( m_name, '-' );
Util::eraseAll( m_name, '(' );
Util::eraseAll( m_name, ')' );
if( Sapphire::World::Action::ActionLut::validStatusEffectExists( id ) )
m_effectEntry = Sapphire::World::Action::ActionLut::getStatusEffectEntry( id );
else
m_effectEntry.effectType = Sapphire::World::Action::EffectTypeInvalid;
}
@ -56,7 +66,30 @@ void Sapphire::StatusEffect::StatusEffect::registerTickEffect( uint8_t type, uin
std::pair< uint8_t, uint32_t > Sapphire::StatusEffect::StatusEffect::getTickEffect()
{
auto thisTick = m_currTickEffect;
m_currTickEffect = std::make_pair( 0, 0 );
if( m_effectEntry.effectType == Sapphire::World::Action::EffectTypeDot )
{
auto value = m_cachedHotOrDotValue;
if( m_cachedSourceCrit > Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) )
{
value *= m_cachedSourceCritBonus;
}
value *= 1.0f + ( ( Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) - 50.0f ) / 1000.0f );
m_currTickEffect = std::make_pair( 1, value );
}
else if( m_effectEntry.effectType == Sapphire::World::Action::EffectTypeHot )
{
auto value = m_cachedHotOrDotValue;
if( m_cachedSourceCrit > Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) )
{
value *= m_cachedSourceCritBonus;
}
value *= 1.0f + ( ( Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) - 50.0f ) / 1000.0f );
m_currTickEffect = std::make_pair( 2, value );
}
else
{
m_currTickEffect = std::make_pair( 0, 0 );
}
return thisTick;
}
@ -87,23 +120,49 @@ void Sapphire::StatusEffect::StatusEffect::applyStatus()
m_startTime = Util::getTimeMs();
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
// this is only right when an action is being used by the player
// else you probably need to use an actorcontrol
if( m_effectEntry.effectType == Sapphire::World::Action::EffectTypeDot )
{
auto wepDmg = Sapphire::Math::CalcStats::getWeaponDamage( *m_sourceActor );
auto damage = Sapphire::Math::CalcStats::calcDamageBaseOnPotency( *m_sourceActor, m_effectEntry.effectValue2, wepDmg );
//GamePacketNew< FFXIVIpcEffect > effectPacket( m_sourceActor->getId() );
//effectPacket.data().targetId = m_sourceActor->getId();
//effectPacket.data().actionAnimationId = 3;
//effectPacket.data().unknown_3 = 1;
//effectPacket.data().actionTextId = 3;
//effectPacket.data().unknown_5 = 1;
//effectPacket.data().unknown_6 = 321;
//effectPacket.data().rotation = ( uint16_t ) ( 0x8000 * ( ( m_sourceActor->getPos().getR() + 3.1415926 ) ) / 3.1415926 );
//effectPacket.data().effectTargetId = m_sourceActor->getId();
//effectPacket.data().effects[4].unknown_1 = 17;
//effectPacket.data().effects[4].bonusPercent = 30;
//effectPacket.data().effects[4].param1 = m_id;
//effectPacket.data().effects[4].unknown_5 = 0x80;
//m_sourceActor->sendToInRangeSet( effectPacket, true );
for( auto const& entry : m_sourceActor->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeDamageMultiplier )
continue;
if( effectEntry.effectValue1 & m_effectEntry.effectValue1 )
{
damage *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
}
}
m_cachedHotOrDotValue = Sapphire::Math::CalcStats::applyDamageReceiveMultiplier( *m_targetActor, damage,
m_effectEntry.effectValue1 == Sapphire::World::Action::EffectActionTypeFilterPhysical ? -1 :
( m_effectEntry.effectValue1 == Sapphire::World::Action::EffectActionTypeFilterMagical ? 5 : -128 ) );
m_cachedSourceCrit = Sapphire::Math::CalcStats::criticalHitProbability( *m_sourceActor, Sapphire::World::Action::EffectCritDHBonusFilterDamage );
m_cachedSourceCritBonus = Sapphire::Math::CalcStats::criticalHitBonus( *m_sourceActor );
}
else if( m_effectEntry.effectType == Sapphire::World::Action::EffectTypeHot )
{
auto wepDmg = Sapphire::Math::CalcStats::getWeaponDamage( *m_sourceActor );
auto heal = Sapphire::Math::CalcStats::calcHealBaseOnPotency( *m_sourceActor, m_effectEntry.effectValue2, wepDmg );
if( m_effectEntry.effectValue1 == 0 ) // this value is always 0 atm, if statement here just in case there is a hot that isn't a "cast"
{
for( auto const& entry : m_sourceActor->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( effectEntry.effectType != Sapphire::World::Action::EffectTypeHealCastMultiplier )
continue;
heal *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
}
}
m_cachedHotOrDotValue = Sapphire::Math::CalcStats::applyHealingReceiveMultiplier( *m_targetActor, heal, m_effectEntry.effectValue1 );
m_cachedSourceCrit = Sapphire::Math::CalcStats::criticalHitProbability( *m_sourceActor, Sapphire::World::Action::EffectCritDHBonusFilterHeal );
m_cachedSourceCritBonus = Sapphire::Math::CalcStats::criticalHitBonus( *m_sourceActor );
}
pScriptMgr->onStatusReceive( m_targetActor, m_id );
}
@ -153,3 +212,8 @@ const std::string& Sapphire::StatusEffect::StatusEffect::getName() const
{
return m_name;
}
const Sapphire::World::Action::StatusEffectEntry& Sapphire::StatusEffect::StatusEffect::getEffectEntry() const
{
return m_effectEntry;
}

View file

@ -3,6 +3,8 @@
#include "Forwards.h"
#include "Action/ActionLut.h"
namespace Sapphire {
namespace StatusEffect {
@ -47,6 +49,8 @@ public:
const std::string& getName() const;
const Sapphire::World::Action::StatusEffectEntry& getEffectEntry() const;
private:
uint32_t m_id;
Entity::CharaPtr m_sourceActor;
@ -59,7 +63,10 @@ private:
std::string m_name;
std::pair< uint8_t, uint32_t > m_currTickEffect;
FrameworkPtr m_pFw;
Sapphire::World::Action::StatusEffectEntry m_effectEntry;
uint32_t m_cachedHotOrDotValue;
float m_cachedSourceCrit;
float m_cachedSourceCritBonus;
};
}