1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-26 14:37:44 +00:00
sapphire/src/world/Action/Action.cpp

376 lines
9.4 KiB
C++
Raw Normal View History

2017-08-08 13:53:47 +02:00
#include "Action.h"
#include <Exd/ExdDataGenerated.h>
2018-03-06 22:22:19 +01:00
#include <Util/Util.h>
#include "Framework.h"
#include "Script/ScriptMgr.h"
2017-08-08 13:53:47 +02:00
#include "Actor/Player.h"
2017-08-08 13:53:47 +02:00
#include <Network/CommonActorControl.h>
#include "Network/PacketWrappers/ActorControlPacket142.h"
#include "Network/PacketWrappers/ActorControlPacket143.h"
#include "Network/PacketWrappers/ActorControlPacket144.h"
using namespace Sapphire::Common;
using namespace Sapphire::Network;
using namespace Sapphire::Network::Packets;
using namespace Sapphire::Network::Packets::Server;
using namespace Sapphire::Network::ActorControl;
Sapphire::Action::Action::Action() = default;
Sapphire::Action::Action::~Action() = default;
Sapphire::Action::Action::Action( Entity::CharaPtr caster, uint32_t actionId,
Data::ActionPtr action, FrameworkPtr fw ) :
m_pSource( std::move( caster ) ),
m_pFw( std::move( fw ) ),
m_id( actionId ),
m_startTime( 0 ),
m_interruptType( Common::ActionInterruptType::None )
2017-08-08 13:53:47 +02:00
{
m_castTime = static_cast< uint32_t >( action->cast100ms * 100 );
m_cooldownTime = static_cast< uint16_t >( action->recast100ms * 100 );
m_cooldownGroup = action->cooldownGroup;
m_actionCost.fill( { Common::ActionCostType::None, 0 } );
m_actionCost[ 0 ] = {
static_cast< Common::ActionCostType >( action->costType ),
action->cost
};
calculateActionCost();
}
2017-08-08 13:53:47 +02:00
uint32_t Sapphire::Action::Action::getId() const
{
return m_id;
}
void Sapphire::Action::Action::setPos( Sapphire::Common::FFXIVARR_POSITION3 pos )
{
m_pos = pos;
}
Sapphire::Common::FFXIVARR_POSITION3 Sapphire::Action::Action::getPos() const
{
return m_pos;
}
void Sapphire::Action::Action::setTargetChara( Sapphire::Entity::CharaPtr chara )
{
assert( chara );
m_pTarget = chara;
m_targetId = chara->getId();
2017-08-08 13:53:47 +02:00
}
Sapphire::Entity::CharaPtr Sapphire::Action::Action::getTargetChara() const
2017-08-08 13:53:47 +02:00
{
return m_pTarget;
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Action::Action::isInterrupted() const
2017-08-08 13:53:47 +02:00
{
return m_interruptType != Common::ActionInterruptType::None;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Action::Action::setInterrupted( Common::ActionInterruptType type )
2017-08-08 13:53:47 +02:00
{
m_interruptType = type;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Action::Action::start()
2017-08-08 13:53:47 +02:00
{
m_startTime = Util::getTimeMs();
2017-08-08 13:53:47 +02:00
onStart();
2017-08-08 13:53:47 +02:00
}
uint32_t Sapphire::Action::Action::getCastTime() const
2017-08-08 13:53:47 +02:00
{
return m_castTime;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Action::Action::setCastTime( uint32_t castTime )
2017-08-08 13:53:47 +02:00
{
m_castTime = castTime;
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Action::Action::isCastedAction() const
{
return m_castTime > 0;
}
Sapphire::Entity::CharaPtr Sapphire::Action::Action::getActionSource() const
2017-08-08 13:53:47 +02:00
{
return m_pSource;
2017-08-08 13:53:47 +02:00
}
bool Sapphire::Action::Action::update()
2017-08-08 13:53:47 +02:00
{
// action has not been started yet
if( m_startTime == 0 )
return false;
if( isInterrupted() )
{
onInterrupt();
return true;
}
uint64_t currTime = Util::getTimeMs();
if( !isCastedAction() || std::difftime( currTime, m_startTime ) > m_castTime )
{
onFinish();
return true;
}
return false;
2017-08-08 13:53:47 +02:00
}
void Sapphire::Action::Action::onStart()
{
assert( m_pSource );
if( isCastedAction() )
{
auto castPacket = makeZonePacket< Server::FFXIVIpcActorCast >( getId() );
castPacket->data().action_id = static_cast< uint16_t >( m_id );
castPacket->data().skillType = Common::SkillType::Normal;
castPacket->data().unknown_1 = m_id;
// This is used for the cast bar above the target bar of the caster.
castPacket->data().cast_time = m_castTime / 1000.f;
castPacket->data().target_id = static_cast< uint32_t >( m_targetId );
m_pSource->sendToInRangeSet( castPacket, true );
if( auto player = m_pSource->getAsPlayer() )
{
player->setStateFlag( PlayerStateFlag::Casting );
player->sendDebug( "onStart()" );
}
}
}
void Sapphire::Action::Action::onInterrupt()
{
assert( m_pSource );
// things that aren't players don't care about cooldowns and state flags
if( m_pSource->isPlayer() )
{
auto player = m_pSource->getAsPlayer();
auto resetCooldownPkt = makeActorControl143( m_pSource->getId(), ActorControlType::SetActionCooldown, 1, getId(), 0 );
player->queuePacket( resetCooldownPkt );
// todo: reset cooldown for actual player
// reset state flag
//player->unsetStateFlag( PlayerStateFlag::Occupied1 );
player->unsetStateFlag( PlayerStateFlag::Casting );
player->sendDebug( "onInterrupt()" );
}
if( isCastedAction() )
{
uint8_t interruptEffect = 0;
if( m_interruptType == ActionInterruptType::DamageInterrupt )
interruptEffect = 1;
// Note: When cast interrupt from taking too much damage, set the last value to 1.
// This enables the cast interrupt effect.
auto control = makeActorControl142( m_pSource->getId(), ActorControlType::CastInterrupt,
0x219, 1, m_id, interruptEffect );
m_pSource->sendToInRangeSet( control, true );
}
}
void Sapphire::Action::Action::onFinish()
{
assert( m_pSource );
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
auto pPlayer = m_pSource->getAsPlayer();
pPlayer->sendDebug( "onFinish()" );
if( isCastedAction() )
{
/*auto control = ActorControlPacket143( m_pTarget->getId(), ActorControlType::Unk7,
0x219, m_id, m_id, m_id, m_id );
m_pSource->sendToInRangeSet( control, true );*/
pScriptMgr->onCastFinish( *pPlayer, m_pTarget, m_id );
}
}
2019-02-09 17:36:44 +11:00
void Sapphire::Action::Action::buildEffectPacket()
{
for( int i = 0; i < EffectPacketIdentity::MAX_ACTION_EFFECT_PACKET_IDENT; ++i )
{
auto& packetData = m_effects[ static_cast< EffectPacketIdentity >( i ) ];
// todo: this
}
}
void Sapphire::Action::Action::damageTarget( uint32_t amount, Entity::Chara& chara,
Common::ActionAspect aspect )
{
Common::EffectEntry entry{};
// todo: handle cases where the action misses/is blocked?
entry.effectType = Common::ActionEffectType::Damage;
// todo: handle crits
entry.hitSeverity = Common::ActionHitSeverityType::NormalDamage;
// todo: handle > 65535 damage values, not sure if this is right?
if( amount > 65535 )
{
entry.value = static_cast< int16_t >( amount / 10 );
// todo: rename this? need to confirm how it works again
entry.bonusPercent = 1;
}
else
entry.value = static_cast< int16_t >( amount );
// todo: aspected damage?
chara.takeDamage( amount );
m_effects[ EffectPacketIdentity::DamageEffect ].m_entries.emplace_back( entry );
// todo: make sure that we don't add the same actor more than once
m_effects[ EffectPacketIdentity::DamageEffect ].m_hitActors.emplace_back( chara.getId() );
}
void Sapphire::Action::Action::healTarget( uint32_t amount, Entity::Chara& chara )
{
Common::EffectEntry entry{};
entry.effectType = Common::ActionEffectType::Heal;
// todo: handle crits
entry.hitSeverity = Common::ActionHitSeverityType::NormalHeal;
// todo: handle > 65535 healing values, not sure if this is right?
if( amount > 65535 )
{
entry.value = static_cast< int16_t >( amount / 10 );
// todo: rename this? need to confirm how it works again
entry.bonusPercent = 1;
}
else
entry.value = static_cast< int16_t >( amount );
chara.heal( amount );
m_effects[ EffectPacketIdentity::HealingEffect ].m_entries.emplace_back( entry );
// todo: make sure that we don't add the same actor more than once
m_effects[ EffectPacketIdentity::HealingEffect ].m_hitActors.emplace_back( chara.getId() );
}
const Sapphire::Action::Action::ActionCostArray& Sapphire::Action::Action::getCostArray() const
{
return m_actionCost;
}
void Sapphire::Action::Action::calculateActionCost()
{
for( uint8_t i = 0; i < m_actionCost.size(); ++i )
{
auto& entry = m_actionCost[ i ];
if( entry.m_costType == ActionCostType::MagicPoints )
calculateMPCost( i );
}
}
void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
{
auto level = m_pSource->getLevel();
// each level range is 1-10, 11-20, 21-30, ... therefore:
// level 50 should be in the 4th group, not the 5th
// dividing by 10 on the border will break this unless we subtract 1
auto levelGroup = std::max< uint8_t >( level - 1, 1 ) / 10;
auto& costEntry = m_actionCost[ costArrayIndex ];
float cost = costEntry.m_cost;
// thanks to andrew for helping me figure this shit out, should be pretty accurate
switch( levelGroup )
{
// level 1-10
case 0:
{
// r^2 = 0.9999
cost = 0.0952f * level + 0.9467f;
break;
}
// level 11-20
case 1:
{
// r^2 = 1
cost = 0.19f * level;
break;
}
// level 21-30
case 2:
{
// r^2 = 1
cost = 0.38f * level - 3.8f;
break;
}
// level 31-40
case 3:
{
// r^2 = 1
cost = 0.6652f * level - 12.358f;
break;
}
// level 41-50
case 4:
{
// r^2 = 1
cost = 1.2352f * level - 35.159f;
break;
}
// level 51-60
case 5:
{
// r^2 = 1
cost = 0.0654f * std::exp( 0.1201f * level );
break;
}
// level 61-70
case 6:
{
// r^2 = 0.9998
cost = 0.2313f * ( level * level ) - 26.98f * level + 875.21f;
break;
}
default:
return;
}
// m_cost is the base cost, cost is the multiplier for the current player level
costEntry.m_cost = static_cast< uint16_t >( std::round( cost * costEntry.m_cost ) );
}