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

Implement shield buff, and script for adloquium as a demo.

This commit is contained in:
collett 2020-01-13 03:05:54 +09:00
parent 659786590c
commit 0573138e1b
16 changed files with 337 additions and 69 deletions

View file

@ -624,6 +624,7 @@ namespace Sapphire::Common
TpGain = 13,
GpGain = 14,
ApplyStatusEffect = 15,
StatusNoEffect = 21,
/*!
* @brief Tells the client that it should show combo indicators on actions.
*
@ -650,6 +651,7 @@ namespace Sapphire::Common
enum class ActionEffectResultFlag : uint8_t
{
None = 0,
Absorbed = 4,
EffectOnSource = 0x80,
Reflected = 0xA0,
};
@ -1034,6 +1036,7 @@ namespace Sapphire::Common
CritDHRateBonus = 7,
DamageReceiveTrigger = 8,
DamageDealtTrigger = 9,
Shield = 10,
};
enum class ActionTypeFilter : int32_t

View file

@ -422,7 +422,8 @@ namespace Sapphire::Network::Packets::Server
uint16_t current_mp;
uint16_t max_mp;
uint16_t currentTp;
uint16_t unknown1;
uint8_t shieldPercentage;
uint8_t unknown1;
Common::StatusEffect effect[30];
uint32_t padding;
};
@ -441,25 +442,16 @@ namespace Sapphire::Network::Packets::Server
{
uint32_t unknown;
uint32_t actor_id;
//uint8_t unknown1;
//uint8_t unknown2;
//uint16_t padding1;
//uint32_t current_hp;
//uint16_t current_mp;
//uint16_t current_tp;
//uint32_t max_hp;
//uint16_t max_mp;
//uint16_t max_something;
uint32_t current_hp;
uint32_t max_hp;
uint16_t current_mp;
uint16_t unknown1;
uint16_t current_tp;
uint16_t max_mp;
uint8_t unknown2;
uint8_t unknown1;
uint8_t classId;
uint8_t unknown4;
uint8_t unkFlag;
uint16_t unknown6;
uint8_t shieldPercentage;
uint8_t entryCount;
uint16_t unknown2;
struct StatusEntry
{
@ -467,12 +459,12 @@ namespace Sapphire::Network::Packets::Server
uint8_t unknown3;
uint16_t id;
uint16_t param;
uint16_t unknown5; // Sort this out (old right half of power/param property)
uint16_t unknown4; // Sort this out (old right half of power/param property)
float duration;
uint32_t sourceActorId;
} statusEntries[4];
uint32_t unknown7;
uint32_t unknown5;
};
/**

View file

@ -0,0 +1,83 @@
#include <Script/NativeScriptApi.h>
#include <ScriptObject.h>
#include <Actor/Player.h>
#include <Action/Action.h>
#include <Math/CalcStats.h>
#include "StatusEffect/StatusEffect.h"
using namespace Sapphire;
using namespace Sapphire::StatusEffect;
const uint16_t STATUS_ID_GALVANIZE = 297;
const uint16_t STATUS_ID_CATALYZE = 1918;
class ActionAdloquium185 :
public ScriptAPI::ActionScript
{
public:
ActionAdloquium185() :
ScriptAPI::ActionScript( 185 )
{
}
void onExecute( Sapphire::World::Action::Action& action ) override
{
auto pTarget = action.getHitChara();
if( pTarget )
{
// still pull out the lut entry to get potency values etc.
auto lutEntry = action.getActionEntry();
// do healing part
auto heal = action.calcHealing( lutEntry.healPotency );
heal.first = Math::CalcStats::applyHealingReceiveMultiplier( *pTarget, heal.first );
action.getEffectbuilder()->heal( pTarget, pTarget, heal.first, heal.second );
float shieldValue = heal.first * 1.25f;
// apply new galvanize when not existing or larger than existing one
auto oldEffect = pTarget->getStatusEffectById( STATUS_ID_GALVANIZE );
if( !oldEffect.second || oldEffect.second->getEffectEntry().effectValue1 <= shieldValue )
{
World::Action::StatusEffectEntry effectEntry;
effectEntry.effectType = static_cast< uint32_t >( Common::StatusEffectType::Shield );
effectEntry.effectValue1 = static_cast< int32_t >( shieldValue );
auto pNewEffect = action.createStatusEffect( STATUS_ID_GALVANIZE, action.getSourceChara(), pTarget, 30000, 3000 );
pNewEffect->replaceEffectEntry( effectEntry );
action.getEffectbuilder()->applyStatusEffect( pTarget, action.getSourceChara(), pNewEffect );
}
else
action.getEffectbuilder()->statusNoEffect( pTarget, STATUS_ID_GALVANIZE );
if( heal.second == Common::ActionHitSeverityType::CritHeal )
{
// apply catalyze when crit, same rule as galvanize
oldEffect = pTarget->getStatusEffectById( STATUS_ID_CATALYZE );
if( !oldEffect.second || oldEffect.second->getEffectEntry().effectValue1 <= shieldValue )
{
World::Action::StatusEffectEntry effectEntry;
effectEntry.effectType = static_cast< uint32_t >( Common::StatusEffectType::Shield );
effectEntry.effectValue1 = static_cast< int32_t >( shieldValue ); // same shield value
auto pNewEffect = action.createStatusEffect( STATUS_ID_CATALYZE, action.getSourceChara(), pTarget, 30000, 3000 );
pNewEffect->replaceEffectEntry( effectEntry );
action.getEffectbuilder()->applyStatusEffect( pTarget, action.getSourceChara(), pNewEffect );
}
else
action.getEffectbuilder()->statusNoEffect( pTarget, STATUS_ID_CATALYZE );
}
}
}
void onStart( Sapphire::World::Action::Action& action ) override
{
/*
don't run generic action handler for this action.
for simpler actions like cure 1 we only want to apply the freecure proc in script,
and let the generic handler do the heal so we don't have to copy heal code into scripts,
unless in cases like adlo, the healing result matters so we have to.
*/
action.disableGenericHandler();
}
};
EXPOSE_SCRIPT( ActionAdloquium185 );

View file

@ -51,7 +51,8 @@ Action::Action::Action( Entity::CharaPtr caster, uint32_t actionId, uint16_t seq
m_startTime( 0 ),
m_interruptType( Common::ActionInterruptType::None ),
m_sequence( sequence ),
m_isAutoAttack( false )
m_isAutoAttack( false ),
m_disableGenericHandler( false )
{
}
@ -295,7 +296,7 @@ void Action::Action::start()
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
// check the lut too and see if we have something usable, otherwise cancel the cast
if( !pScriptMgr->onStart( *this ) && !ActionLut::validEntryExists( static_cast< uint16_t >( getId() ) ) )
if( !pScriptMgr->onStart( *this ) && !hasValidLutEntry() )
{
// script not implemented and insufficient lut data (no potencies)
interrupt();
@ -424,9 +425,8 @@ void Action::Action::buildEffects()
snapshotAffectedActors( m_hitActors );
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
auto hasLutEntry = hasValidLutEntry();
if( !pScriptMgr->onExecute( *this ) && !hasLutEntry )
if( !pScriptMgr->onExecute( *this ) && !hasValidLutEntry() )
{
if( auto player = m_pSource->getAsPlayer() )
{
@ -436,7 +436,7 @@ void Action::Action::buildEffects()
return;
}
if( !hasLutEntry ) // this is just "if ( weCanNotUseGenericActionHandler )" in case we start to expand it.
if( m_disableGenericHandler || !hasValidLutEntry() )
{
// send any effect packet added by script or an empty one just to play animation for other players
m_effectBuilder->buildAndSendPackets();
@ -469,7 +469,8 @@ void Action::Action::buildEffects()
if( dmg.first > 0 )
{
actor->onActionHostile( m_pSource );
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second );
dmg.first = Math::CalcStats::applyShieldProtection( actor, dmg.first );
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second, dmg.first == 0 ? Common::ActionEffectResultFlag::Absorbed : Common::ActionEffectResultFlag::None );
}
auto reflectDmg = Math::CalcStats::calcDamageReflect( m_pSource, actor, dmg.first,
@ -824,11 +825,21 @@ Data::ActionPtr Action::Action::getActionData() const
return m_actionData;
}
Action::ActionEntry Action::Action::getActionEntry() const
{
return m_lutEntry;
}
void Action::Action::setAutoAttack()
{
m_isAutoAttack = true;
}
void Action::Action::disableGenericHandler()
{
m_disableGenericHandler = true;
}
bool Action::Action::isPhysical() const
{
return isAttackTypePhysical( static_cast< Common::AttackType >( m_actionData->attackType ) );
@ -847,4 +858,10 @@ bool Action::Action::isAttackTypePhysical( Common::AttackType attackType )
bool Action::Action::isAttackTypeMagical( Common::AttackType attackType )
{
return attackType == Common::AttackType::Magical;
}
Sapphire::StatusEffect::StatusEffectPtr Action::Action::createStatusEffect( uint32_t id, Entity::CharaPtr sourceActor, Entity::CharaPtr targetActor, uint32_t duration, uint32_t tickRate )
{
// workaround to framework access issue in action script
return StatusEffect::make_StatusEffect( id, sourceActor, targetActor, duration, tickRate, m_pFw );
}

View file

@ -6,6 +6,7 @@
#include "Util/ActorFilter.h"
#include "ForwardsZone.h"
#include "EffectBuilder.h"
#include "StatusEffect/StatusEffect.h"
namespace Sapphire::Data
{
@ -54,6 +55,10 @@ namespace Sapphire::World::Action
void setAutoAttack();
void disableGenericHandler();
StatusEffect::StatusEffectPtr createStatusEffect( uint32_t id, Entity::CharaPtr sourceActor, Entity::CharaPtr targetActor, uint32_t duration, uint32_t tickRate );
/*!
* @brief Checks if a chara has enough resources available to cast the action (tp/mp/etc)
* @return true if they have the required resources
@ -122,6 +127,8 @@ namespace Sapphire::World::Action
Data::ActionPtr getActionData() const;
ActionEntry getActionEntry() const;
bool isPhysical() const;
bool isMagical() const;
@ -191,6 +198,7 @@ namespace Sapphire::World::Action
bool m_canTargetHostile;
bool m_canTargetDead;
bool m_isAutoAttack;
bool m_disableGenericHandler;
Common::ActionInterruptType m_interruptType;

View file

@ -91,6 +91,20 @@ void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPt
moveToResultList( target, nextResult );
}
void EffectBuilder::applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, StatusEffect::StatusEffectPtr pStatusEffect )
{
EffectResultPtr nextResult = make_EffectResult( target, source, getResultDelayMs() );
nextResult->applyStatusEffect( pStatusEffect );
moveToResultList( target, nextResult );
}
void EffectBuilder::statusNoEffect( Entity::CharaPtr& target, uint16_t statusId )
{
EffectResultPtr nextResult = make_EffectResult( target, nullptr, 0 );
nextResult->statusNoEffect( statusId );
moveToResultList( target, nextResult );
}
void EffectBuilder::buildAndSendPackets()
{
auto targetCount = m_resolvedEffects.size();

View file

@ -27,10 +27,12 @@ namespace Sapphire::World::Action
void comboSucceed( Entity::CharaPtr& target );
void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, uint16_t statusId, uint32_t duration, uint8_t param );
void applyStatusEffect( Entity::CharaPtr& target, Entity::CharaPtr& source, StatusEffect::StatusEffectPtr pStatusEffect );
void statusNoEffect( Entity::CharaPtr& target, uint16_t statusId );
void buildAndSendPackets();
private:
void moveToResultList( Entity::CharaPtr& chara, EffectResultPtr result );

View file

@ -18,7 +18,8 @@ EffectResult::EffectResult( Entity::CharaPtr target, Entity::CharaPtr source, ui
m_param0( 0 ),
m_param1( 0 ),
m_param2( 0 ),
m_flag( Common::ActionEffectResultFlag::None )
m_flag( Common::ActionEffectResultFlag::None ),
m_pPreBuiltStatusEffect( nullptr )
{
}
@ -92,6 +93,22 @@ void EffectResult::applyStatusEffect( uint16_t statusId, uint32_t duration, uint
m_type = Common::ActionEffectType::ApplyStatusEffect;
}
void EffectResult::applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect )
{
m_value = pStatusEffect->getId();
m_param2 = pStatusEffect->getParam();
m_pPreBuiltStatusEffect = std::move( pStatusEffect );
m_type = Common::ActionEffectType::ApplyStatusEffect;
}
void EffectResult::statusNoEffect( uint16_t statusId )
{
m_value = statusId;
m_type = Common::ActionEffectType::StatusNoEffect;
}
Common::EffectEntry EffectResult::buildEffectEntry() const
{
Common::EffectEntry entry{};
@ -131,7 +148,22 @@ void EffectResult::execute()
case Common::ActionEffectType::ApplyStatusEffect:
{
m_target->addStatusEffectById( m_value, m_value2, *m_source, m_param2 );
//remove same effect from the same source (refreshing old buff)
for( auto const& entry : m_target->getStatusEffectMap() )
{
auto statusEffect = entry.second;
if( statusEffect->getId() == m_value && statusEffect->getSrcActorId() == m_source->getId() )
{
// refreshing does not show "-status" flying text, and we don't send status list now because we are adding a new one
m_target->removeStatusEffect( entry.first, false, false );
break;
}
}
if( m_pPreBuiltStatusEffect )
m_target->addStatusEffect( m_pPreBuiltStatusEffect );
else
m_target->addStatusEffectById( m_value, m_value2, *m_source, m_param2 );
break;
}

View file

@ -4,6 +4,8 @@
#include <ForwardsZone.h>
#include <Common.h>
#include "StatusEffect/StatusEffect.h"
namespace Sapphire::World::Action
{
/*!
@ -21,6 +23,8 @@ namespace Sapphire::World::Action
void startCombo( uint16_t actionId );
void comboSucceed();
void applyStatusEffect( uint16_t statusId, uint32_t duration, uint8_t param );
void applyStatusEffect( StatusEffect::StatusEffectPtr pStatusEffect );
void statusNoEffect( uint16_t statusId );
Entity::CharaPtr getSource() const;
Entity::CharaPtr getTarget() const;
@ -48,6 +52,8 @@ namespace Sapphire::World::Action
uint32_t m_value;
uint32_t m_value2;
Common::ActionEffectResultFlag m_flag;
StatusEffect::StatusEffectPtr m_pPreBuiltStatusEffect;
};
}

View file

@ -501,13 +501,11 @@ void Sapphire::Entity::Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEf
statusEffectAdd->data().actor_id = pEffect->getTargetActorId();
statusEffectAdd->data().current_hp = getHp();
statusEffectAdd->data().current_mp = static_cast< uint16_t >( getMp() );
//statusEffectAdd->data().current_tp = getTp();
statusEffectAdd->data().current_tp = getTp();
statusEffectAdd->data().max_hp = getMaxHp();
statusEffectAdd->data().max_mp = static_cast< uint16_t >( getMaxMp() );
//statusEffectAdd->data().max_something = 1;
//statusEffectAdd->data().unknown2 = 28;
statusEffectAdd->data().classId = static_cast< uint8_t >(getClass());
statusEffectAdd->data().unkFlag = 1;
statusEffectAdd->data().classId = static_cast< uint8_t >( getClass() );
statusEffectAdd->data().entryCount = 1; // todo: add multiple status but send only one result
auto& status = statusEffectAdd->data().statusEntries[0];
@ -517,31 +515,47 @@ void Sapphire::Entity::Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEf
status.index = static_cast< uint8_t >( nextSlot );
status.param = pEffect->getParam();
sendToInRangeSet( statusEffectAdd, isPlayer() );
float totalShieldValue = 0;
for( auto effectIt : m_statusEffectMap )
{
auto statusEffect = effectIt.second;
if( static_cast< Common::StatusEffectType >( statusEffect->getEffectEntry().effectType ) == Common::StatusEffectType::Shield )
{
totalShieldValue += statusEffect->getEffectEntry().effectValue1;
}
}
if( totalShieldValue > 0 )
{
totalShieldValue /= getMaxHp();
totalShieldValue *= 100;
statusEffectAdd->data().shieldPercentage = totalShieldValue >= 255 ? 255 : static_cast< uint8_t >( totalShieldValue );
}
sendToInRangeSet( statusEffectAdd, true );
sendStatusEffectUpdate(); // although client buff displays correctly without this but retail sends it so we do it as well
}
/*! \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 oldEffect = getStatusEffectById( id );
if( oldEffect.second )
removeStatusEffect( oldEffect.first, false, false );
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000, m_pFw );
effect->setParam( param );
addStatusEffect( effect );
}
/*! \param StatusEffectPtr to be applied to the actor */
void Sapphire::Entity::Chara::addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source,
uint16_t param )
{
if( hasStatusEffect( id ) )
if( getStatusEffectById( id ).second )
return;
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000, m_pFw );
effect->setParam( param );
addStatusEffect( effect );
}
int8_t Sapphire::Entity::Chara::getStatusEffectFreeSlot()
@ -562,19 +576,19 @@ void Sapphire::Entity::Chara::statusEffectFreeSlot( uint8_t slotId )
m_statusEffectFreeSlotQueue.push( slotId );
}
void Sapphire::Entity::Chara::removeSingleStatusEffectById( uint32_t id, bool sendPacket )
void Sapphire::Entity::Chara::removeSingleStatusEffectById( uint32_t id, bool sendActorControl, bool sendStatusList )
{
for( auto effectIt : m_statusEffectMap )
{
if( effectIt.second->getId() == id )
{
removeStatusEffect( effectIt.first, sendPacket );
removeStatusEffect( effectIt.first, sendActorControl, sendStatusList );
break;
}
}
}
void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId, bool sendPacket )
void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId, bool sendActorControl, bool sendStatusList )
{
auto pEffectIt = m_statusEffectMap.find( effectSlotId );
if( pEffectIt == m_statusEffectMap.end() )
@ -585,12 +599,13 @@ void Sapphire::Entity::Chara::removeStatusEffect( uint8_t effectSlotId, bool sen
auto pEffect = pEffectIt->second;
pEffect->removeStatus();
if( sendPacket )
if( sendActorControl )
sendToInRangeSet( makeActorControl( getId(), StatusEffectLose, pEffect->getId() ), isPlayer() );
m_statusEffectMap.erase( effectSlotId );
sendStatusEffectUpdate();
if( sendStatusList )
sendStatusEffectUpdate();
}
std::map< uint8_t, Sapphire::StatusEffect::StatusEffectPtr > Sapphire::Entity::Chara::getStatusEffectMap() const
@ -622,7 +637,6 @@ void Sapphire::Entity::Chara::sendStatusEffectUpdate()
{
uint64_t currentTimeMs = Util::getTimeMs();
auto statusEffectList = makeZonePacket< FFXIVIpcStatusEffectList >( getId() );
statusEffectList->data().classId = static_cast< uint8_t >( getClass() );
statusEffectList->data().level = getLevel();
@ -633,18 +647,63 @@ void Sapphire::Entity::Chara::sendStatusEffectUpdate()
statusEffectList->data().max_hp = getMaxHp();
statusEffectList->data().max_mp = getMaxMp();
uint8_t slot = 0;
float totalShieldValue = 0;
for( auto effectIt : m_statusEffectMap )
{
float timeLeft = static_cast< float >( effectIt.second->getDuration() -
( currentTimeMs - effectIt.second->getStartTimeMs() ) ) / 1000;
auto statusEffect = effectIt.second;
if( static_cast< Common::StatusEffectType >( statusEffect->getEffectEntry().effectType ) == Common::StatusEffectType::Shield )
{
totalShieldValue += statusEffect->getEffectEntry().effectValue1;
}
float timeLeft = static_cast< float >( statusEffect->getDuration() -
( currentTimeMs - statusEffect->getStartTimeMs() ) ) / 1000;
statusEffectList->data().effect[ slot ].duration = timeLeft;
statusEffectList->data().effect[ slot ].effect_id = effectIt.second->getId();
statusEffectList->data().effect[ slot ].sourceActorId = effectIt.second->getSrcActorId();
statusEffectList->data().effect[ slot ].effect_id = statusEffect->getId();
statusEffectList->data().effect[ slot ].sourceActorId = statusEffect->getSrcActorId();
slot++;
}
sendToInRangeSet( statusEffectList, isPlayer() );
if( totalShieldValue > 0 )
{
totalShieldValue /= getMaxHp();
totalShieldValue *= 100;
statusEffectList->data().shieldPercentage = totalShieldValue >= 255 ? 255 : static_cast< uint8_t >( totalShieldValue );
}
sendToInRangeSet( statusEffectList, true );
}
void Sapphire::Entity::Chara::sendEffectResultToUpdateShieldValue()
{
auto pPacket = makeZonePacket< FFXIVIpcEffectResult >( getId() );
pPacket->data().actor_id = getId();
pPacket->data().current_hp = getHp();
pPacket->data().current_mp = static_cast< uint16_t >( getMp() );
pPacket->data().current_tp = getTp();
pPacket->data().max_hp = getMaxHp();
pPacket->data().max_mp = static_cast< uint16_t >( getMaxMp() );
pPacket->data().classId = static_cast< uint8_t >( getClass() );
float totalShieldValue = 0;
for( auto effectIt : m_statusEffectMap )
{
auto statusEffect = effectIt.second;
if( static_cast< Common::StatusEffectType >( statusEffect->getEffectEntry().effectType ) == Common::StatusEffectType::Shield )
{
totalShieldValue += statusEffect->getEffectEntry().effectValue1;
}
}
if( totalShieldValue > 0 )
{
totalShieldValue /= getMaxHp();
totalShieldValue *= 100;
pPacket->data().shieldPercentage = totalShieldValue >= 255 ? 255 : static_cast< uint8_t >( totalShieldValue );
}
sendToInRangeSet( pPacket, true );
}
void Sapphire::Entity::Chara::updateStatusEffects()
@ -667,7 +726,7 @@ void Sapphire::Entity::Chara::updateStatusEffects()
if( duration > 0 && ( currentTimeMs - startTime ) > duration )
{
// remove status effect
removeStatusEffect( effectIndex, true );
removeStatusEffect( effectIndex, true, true );
// break because removing invalidates iterators
break;
}
@ -714,17 +773,16 @@ void Sapphire::Entity::Chara::updateStatusEffects()
}
}
bool Sapphire::Entity::Chara::hasStatusEffect( uint32_t id )
std::pair< uint8_t, Sapphire::StatusEffect::StatusEffectPtr > Sapphire::Entity::Chara::getStatusEffectById( uint32_t id )
{
//return m_statusEffectMap.find( id ) != m_statusEffectMap.end();
for( auto effectIt : m_statusEffectMap )
{
if( effectIt.second->getId() == id )
{
return true;
return std::make_pair( effectIt.first, effectIt.second );
}
}
return false;
return std::make_pair( 0, nullptr );
}
int64_t Sapphire::Entity::Chara::getLastUpdateTime() const

View file

@ -146,13 +146,13 @@ namespace Sapphire::Entity
/// Status effect functions
void addStatusEffect( StatusEffect::StatusEffectPtr pEffect );
void removeStatusEffect( uint8_t effectSlotId, bool sendPacket );
void removeStatusEffect( uint8_t effectSlotId, bool sendActorControl, bool sendStatusList );
void removeSingleStatusEffectById( uint32_t id, bool sendPacket );
void removeSingleStatusEffectById( uint32_t id, bool sendActorControl, bool sendStatusList );
void updateStatusEffects();
bool hasStatusEffect( uint32_t id );
std::pair< uint8_t, StatusEffect::StatusEffectPtr > getStatusEffectById( uint32_t id );
int8_t getStatusEffectFreeSlot();
@ -166,6 +166,8 @@ namespace Sapphire::Entity
void sendStatusEffectUpdate();
void sendEffectResultToUpdateShieldValue();
/*! return a const pointer to the look array */
const uint8_t* getLookArray() const;
@ -177,8 +179,6 @@ namespace Sapphire::Entity
// add a status effect by id if it doesn't exist
void addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param = 0 );
// remove a status effect by id
void removeSingleStatusEffectFromId( uint32_t id );
/// End Status Effect Functions
std::string getName() const;

View file

@ -716,7 +716,7 @@ uint32_t CalcStats::primaryStatValue( const Sapphire::Entity::Chara& chara )
std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcDamageReflect( Sapphire::Entity::CharaPtr pCharaAttacker, Sapphire::Entity::CharaPtr pCharaVictim, float damage, Sapphire::Common::ActionTypeFilter filter )
{
for( auto entry : pCharaVictim->getStatusEffectMap() )
for( auto const& entry : pCharaVictim->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
@ -740,7 +740,7 @@ std::pair< float, Sapphire::Common::ActionHitSeverityType > CalcStats::calcDamag
float CalcStats::calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage, Sapphire::Common::ActionTypeFilter filter )
{
float result = 0;
for( auto entry : pChara->getStatusEffectMap() )
for( auto const& entry : pChara->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
@ -754,4 +754,49 @@ float CalcStats::calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage,
}
}
return result;
}
float CalcStats::applyShieldProtection( Sapphire::Entity::CharaPtr pChara, float damage )
{
float remainingDamage = damage;
bool shieldChanged = false;
std::vector< uint8_t > destroyedShieldSlotList;
for( auto const& entry : pChara->getStatusEffectMap() )
{
auto status = entry.second;
auto effectEntry = status->getEffectEntry();
if( static_cast< Common::StatusEffectType >( effectEntry.effectType ) == Common::StatusEffectType::Shield )
{
shieldChanged = true;
if( remainingDamage < effectEntry.effectValue1 )
{
effectEntry.effectValue1 -= static_cast< int32_t >( remainingDamage );
status->replaceEffectEntry( effectEntry );
remainingDamage = 0;
break;
}
else
{
remainingDamage -= effectEntry.effectValue1;
destroyedShieldSlotList.push_back( entry.first );
}
}
}
if( shieldChanged )
{
if( !destroyedShieldSlotList.empty() )
{
for( auto const& slotId : destroyedShieldSlotList )
{
pChara->removeStatusEffect( slotId, true, false );
}
pChara->sendStatusEffectUpdate();
}
else
pChara->sendEffectResultToUpdateShieldValue(); // yes this is the packet to update shield value
}
return remainingDamage;
}

View file

@ -148,6 +148,8 @@ namespace Sapphire::Math
static float calcAbsorbHP( Sapphire::Entity::CharaPtr pChara, float damage, Sapphire::Common::ActionTypeFilter filter );
static float applyShieldProtection( Sapphire::Entity::CharaPtr pChara, float damage );
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 ), true );
player.removeSingleStatusEffectById( static_cast< uint32_t >( param1 ), true, true );
break;
}
case ClientTriggerType::CastCancel: // Cancel cast

View file

@ -29,7 +29,7 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
m_tickRate( tickRate ),
m_lastTick( 0 ),
m_pFw( pFw ),
m_cachedHotOrDotValue( 0 ),
m_value( 0 ),
m_cachedSourceCrit( 0 ),
m_cachedSourceCritBonus( 0 )
{
@ -53,7 +53,6 @@ Sapphire::StatusEffect::StatusEffect::StatusEffect( uint32_t id, Entity::CharaPt
m_effectEntry.effectType = static_cast< uint32_t >( Common::StatusEffectType::Invalid );
}
Sapphire::StatusEffect::StatusEffect::~StatusEffect()
{
}
@ -69,7 +68,7 @@ std::pair< uint8_t, uint32_t > Sapphire::StatusEffect::StatusEffect::getTickEffe
auto statusEffectType = static_cast< Common::StatusEffectType >( m_effectEntry.effectType );
if( statusEffectType == Common::StatusEffectType::Dot )
{
auto value = m_cachedHotOrDotValue;
auto value = m_value;
if( m_cachedSourceCrit > Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) )
{
value *= m_cachedSourceCritBonus;
@ -79,7 +78,7 @@ std::pair< uint8_t, uint32_t > Sapphire::StatusEffect::StatusEffect::getTickEffe
}
else if( statusEffectType == Common::StatusEffectType::Hot )
{
auto value = m_cachedHotOrDotValue;
auto value = m_value;
if( m_cachedSourceCrit > Sapphire::Math::CalcStats::range100( Sapphire::Math::CalcStats::rng ) )
{
value *= m_cachedSourceCritBonus;
@ -138,7 +137,7 @@ void Sapphire::StatusEffect::StatusEffect::applyStatus()
}
}
m_cachedHotOrDotValue = Sapphire::Math::CalcStats::applyDamageReceiveMultiplier( *m_targetActor, damage,
m_value = Sapphire::Math::CalcStats::applyDamageReceiveMultiplier( *m_targetActor, damage,
m_effectEntry.effectValue1 == static_cast< int32_t >( Common::ActionTypeFilter::Physical ) ? Common::AttackType::Physical :
( m_effectEntry.effectValue1 == static_cast< int32_t >( Common::ActionTypeFilter::Magical ) ? Common::AttackType::Magical : Common::AttackType::Unknown_0 ) );
m_cachedSourceCrit = Sapphire::Math::CalcStats::criticalHitProbability( *m_sourceActor, Common::CritDHBonusFilter::Damage );
@ -160,7 +159,7 @@ void Sapphire::StatusEffect::StatusEffect::applyStatus()
heal *= 1.0f + ( effectEntry.effectValue2 / 100.0f );
}
}
m_cachedHotOrDotValue = Sapphire::Math::CalcStats::applyHealingReceiveMultiplier( *m_targetActor, heal );
m_value = Sapphire::Math::CalcStats::applyHealingReceiveMultiplier( *m_targetActor, heal );
m_cachedSourceCrit = Sapphire::Math::CalcStats::criticalHitProbability( *m_sourceActor, Common::CritDHBonusFilter::Heal );
m_cachedSourceCritBonus = Sapphire::Math::CalcStats::criticalHitBonus( *m_sourceActor );
}
@ -218,3 +217,8 @@ const Sapphire::World::Action::StatusEffectEntry& Sapphire::StatusEffect::Status
{
return m_effectEntry;
}
void Sapphire::StatusEffect::StatusEffect::replaceEffectEntry( Sapphire::World::Action::StatusEffectEntry entryOverride )
{
m_effectEntry = entryOverride;
}

View file

@ -51,6 +51,8 @@ public:
const Sapphire::World::Action::StatusEffectEntry& getEffectEntry() const;
void replaceEffectEntry( Sapphire::World::Action::StatusEffectEntry entryOverride );
private:
uint32_t m_id;
Entity::CharaPtr m_sourceActor;
@ -64,7 +66,7 @@ private:
std::pair< uint8_t, uint32_t > m_currTickEffect;
FrameworkPtr m_pFw;
Sapphire::World::Action::StatusEffectEntry m_effectEntry;
uint32_t m_cachedHotOrDotValue;
uint32_t m_value;
float m_cachedSourceCrit;
float m_cachedSourceCritBonus;
};