2017-08-08 13:53:47 +02:00
|
|
|
#include "Action.h"
|
2019-06-02 00:34:22 +10:00
|
|
|
#include "ActionLut.h"
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2019-02-08 22:09:48 +11:00
|
|
|
#include <Exd/ExdDataGenerated.h>
|
2018-03-06 22:22:19 +01:00
|
|
|
#include <Util/Util.h>
|
2019-02-08 22:09:48 +11:00
|
|
|
#include "Framework.h"
|
|
|
|
#include "Script/ScriptMgr.h"
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2019-03-23 11:48:49 +11:00
|
|
|
#include <Math/CalcStats.h>
|
|
|
|
|
2019-02-08 22:09:48 +11:00
|
|
|
#include "Actor/Player.h"
|
2019-02-11 10:36:17 +11:00
|
|
|
#include "Actor/BNpc.h"
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2019-02-11 11:47:04 +11:00
|
|
|
#include "Territory/Zone.h"
|
|
|
|
|
2019-02-08 22:09:48 +11:00
|
|
|
#include <Network/CommonActorControl.h>
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket142.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket143.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket144.h"
|
2019-02-11 00:11:29 +11:00
|
|
|
#include <Network/PacketWrappers/EffectPacket.h>
|
2019-04-07 16:04:36 +10:00
|
|
|
#include <Logging/Logger.h>
|
|
|
|
#include <Util/ActorFilter.h>
|
2019-02-08 22:09:48 +11:00
|
|
|
|
|
|
|
using namespace Sapphire::Common;
|
|
|
|
using namespace Sapphire::Network;
|
|
|
|
using namespace Sapphire::Network::Packets;
|
|
|
|
using namespace Sapphire::Network::Packets::Server;
|
|
|
|
using namespace Sapphire::Network::ActorControl;
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
using namespace Sapphire::World::Action;
|
2019-02-08 22:09:48 +11:00
|
|
|
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
Action::Action() = default;
|
|
|
|
Action::~Action() = default;
|
|
|
|
|
|
|
|
Action::Action( Entity::CharaPtr caster, uint32_t actionId, FrameworkPtr fw ) :
|
2019-03-07 21:58:12 +11:00
|
|
|
Action( std::move( caster ), actionId, nullptr, std::move( fw ) )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
Action::Action( Entity::CharaPtr caster, uint32_t actionId,
|
2019-03-07 20:27:45 +11:00
|
|
|
Data::ActionPtr actionData, FrameworkPtr fw ) :
|
2019-02-08 22:09:48 +11:00
|
|
|
m_pSource( std::move( caster ) ),
|
|
|
|
m_pFw( std::move( fw ) ),
|
2019-03-07 21:58:12 +11:00
|
|
|
m_actionData( std::move( actionData ) ),
|
2019-02-08 22:09:48 +11:00
|
|
|
m_id( actionId ),
|
2019-03-07 21:58:12 +11:00
|
|
|
m_targetId( 0 ),
|
2019-02-09 18:32:10 +11:00
|
|
|
m_startTime( 0 ),
|
2019-03-07 21:58:12 +11:00
|
|
|
m_interruptType( Common::ActionInterruptType::None )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
uint32_t Action::getId() const
|
2019-03-07 21:58:12 +11:00
|
|
|
{
|
|
|
|
return m_id;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::init()
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-03-07 21:58:12 +11:00
|
|
|
if( !m_actionData )
|
|
|
|
{
|
|
|
|
// need to get actionData
|
|
|
|
auto exdData = m_pFw->get< Data::ExdDataGenerated >();
|
|
|
|
assert( exdData );
|
|
|
|
|
|
|
|
auto actionData = exdData->get< Data::Action >( m_id );
|
|
|
|
assert( actionData );
|
|
|
|
|
|
|
|
m_actionData = actionData;
|
|
|
|
}
|
2019-03-07 20:27:45 +11:00
|
|
|
|
2019-03-08 00:26:41 +11:00
|
|
|
m_castTimeMs = static_cast< uint32_t >( m_actionData->cast100ms * 100 );
|
|
|
|
m_recastTimeMs = static_cast< uint32_t >( m_actionData->recast100ms * 100 );
|
2019-03-07 21:58:12 +11:00
|
|
|
m_cooldownGroup = m_actionData->cooldownGroup;
|
|
|
|
m_range = m_actionData->range;
|
|
|
|
m_effectRange = m_actionData->effectRange;
|
2019-04-07 16:04:36 +10:00
|
|
|
m_castType = static_cast< Common::CastType >( m_actionData->castType );
|
2019-03-07 21:58:12 +11:00
|
|
|
m_aspect = static_cast< Common::ActionAspect >( m_actionData->aspect );
|
2019-02-11 01:50:41 +11:00
|
|
|
|
2019-04-07 16:04:36 +10:00
|
|
|
// todo: move this to bitset
|
|
|
|
m_canTargetSelf = m_actionData->canTargetSelf;
|
|
|
|
m_canTargetParty = m_actionData->canTargetParty;
|
|
|
|
m_canTargetFriendly = m_actionData->canTargetFriendly;
|
|
|
|
m_canTargetHostile = m_actionData->canTargetHostile;
|
|
|
|
// todo: this one doesn't look right based on whats in that col, probably has shifted
|
|
|
|
m_canTargetDead = m_actionData->canTargetDead;
|
|
|
|
|
2019-02-11 01:50:41 +11:00
|
|
|
// a default range is set by the game for the class/job
|
|
|
|
if( m_range == -1 )
|
|
|
|
{
|
2019-03-07 21:58:12 +11:00
|
|
|
switch( static_cast< Common::ClassJob >( m_actionData->classJob ) )
|
2019-02-11 01:50:41 +11:00
|
|
|
{
|
|
|
|
case Common::ClassJob::Bard:
|
|
|
|
case Common::ClassJob::Archer:
|
|
|
|
m_range = 25;
|
|
|
|
|
2019-03-07 21:58:12 +11:00
|
|
|
// anything that isnt ranged
|
2019-02-11 01:50:41 +11:00
|
|
|
default:
|
|
|
|
m_range = 3;
|
|
|
|
}
|
|
|
|
}
|
2019-02-09 18:01:39 +11:00
|
|
|
|
2019-03-07 21:58:12 +11:00
|
|
|
m_primaryCostType = static_cast< Common::ActionPrimaryCostType >( m_actionData->costType );
|
|
|
|
m_primaryCost = m_actionData->cost;
|
2019-02-09 20:49:22 +11:00
|
|
|
|
2019-04-28 23:34:43 +02:00
|
|
|
/*if( !m_actionData->targetArea )
|
2019-04-07 16:31:09 +10:00
|
|
|
{
|
|
|
|
// override pos to target position
|
|
|
|
// todo: this is kinda dirty
|
|
|
|
for( auto& actor : m_pSource->getInRangeActors() )
|
|
|
|
{
|
|
|
|
if( actor->getId() == m_targetId )
|
|
|
|
{
|
|
|
|
m_pos = actor->getPos();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-04-28 23:34:43 +02:00
|
|
|
}*/
|
2019-04-07 16:31:09 +10:00
|
|
|
|
2019-03-07 20:27:45 +11:00
|
|
|
// todo: add missing rows for secondaryCostType/secondaryCostType and rename the current rows to primaryCostX
|
2019-02-09 23:14:30 +11:00
|
|
|
|
2019-04-07 16:04:36 +10:00
|
|
|
addDefaultActorFilters();
|
|
|
|
|
2019-03-07 21:58:12 +11:00
|
|
|
return true;
|
2019-02-09 15:39:05 +11:00
|
|
|
}
|
2019-02-08 22:09:48 +11:00
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::setPos( Sapphire::Common::FFXIVARR_POSITION3 pos )
|
2019-02-09 18:32:10 +11:00
|
|
|
{
|
|
|
|
m_pos = pos;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
Sapphire::Common::FFXIVARR_POSITION3 Action::getPos() const
|
2019-02-09 18:32:10 +11:00
|
|
|
{
|
|
|
|
return m_pos;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::setTargetId( uint64_t targetId )
|
2019-02-10 22:13:47 +11:00
|
|
|
{
|
|
|
|
m_targetId = targetId;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
uint64_t Action::getTargetId() const
|
2019-02-10 22:13:47 +11:00
|
|
|
{
|
2019-03-07 21:58:12 +11:00
|
|
|
return m_targetId;
|
2019-02-10 22:13:47 +11:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::hasClientsideTarget() const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-03-07 21:58:12 +11:00
|
|
|
return m_targetId > 0xFFFFFFFF;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::isInterrupted() const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-02-10 21:21:34 +11:00
|
|
|
return m_interruptType != Common::ActionInterruptType::None;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::setInterrupted( Common::ActionInterruptType type )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-02-10 21:12:22 +11:00
|
|
|
m_interruptType = type;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
uint32_t Action::getCastTime() const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-03-08 00:26:41 +11:00
|
|
|
return m_castTimeMs;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::setCastTime( uint32_t castTime )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-03-08 00:26:41 +11:00
|
|
|
m_castTimeMs = castTime;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::hasCastTime() const
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
2019-03-08 00:26:41 +11:00
|
|
|
return m_castTimeMs > 0;
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
Sapphire::Entity::CharaPtr Action::getSourceChara() const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_pSource;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::update()
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
// action has not been started yet
|
|
|
|
if( m_startTime == 0 )
|
|
|
|
return false;
|
|
|
|
|
2019-02-10 21:12:22 +11:00
|
|
|
if( isInterrupted() )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
2019-03-07 20:33:14 +11:00
|
|
|
interrupt();
|
2018-08-29 21:40:59 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-03-07 21:58:12 +11:00
|
|
|
if( !hasClientsideTarget() )
|
2019-02-10 22:13:47 +11:00
|
|
|
{
|
|
|
|
// todo: check if the target is still in range
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
uint64_t tickCount = Common::Util::getTimeMs();
|
2018-08-29 21:40:59 +02:00
|
|
|
|
2019-04-04 23:29:52 +02:00
|
|
|
if( !hasCastTime() || std::difftime( tickCount, m_startTime ) > m_castTimeMs )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
2019-03-07 20:33:14 +11:00
|
|
|
execute();
|
2018-08-29 21:40:59 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-02-10 21:01:04 +11:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return false;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
2019-02-08 22:09:48 +11:00
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::start()
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
|
|
|
assert( m_pSource );
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
m_startTime = Common::Util::getTimeMs();
|
2019-02-11 00:40:00 +11:00
|
|
|
|
2019-02-10 22:13:47 +11:00
|
|
|
auto player = m_pSource->getAsPlayer();
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
if( hasCastTime() )
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
2019-02-09 15:45:02 +11:00
|
|
|
auto castPacket = makeZonePacket< Server::FFXIVIpcActorCast >( getId() );
|
2019-02-08 22:09:48 +11:00
|
|
|
|
2019-02-10 21:18:03 +11:00
|
|
|
castPacket->data().action_id = static_cast< uint16_t >( m_id );
|
2019-02-08 22:09:48 +11:00
|
|
|
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.
|
2019-03-08 00:26:41 +11:00
|
|
|
castPacket->data().cast_time = m_castTimeMs / 1000.f;
|
2019-02-10 21:18:03 +11:00
|
|
|
castPacket->data().target_id = static_cast< uint32_t >( m_targetId );
|
2019-02-08 22:09:48 +11:00
|
|
|
|
|
|
|
m_pSource->sendToInRangeSet( castPacket, true );
|
2019-02-10 21:18:03 +11:00
|
|
|
|
2019-02-10 22:13:47 +11:00
|
|
|
if( player )
|
2019-02-10 21:18:03 +11:00
|
|
|
{
|
|
|
|
player->setStateFlag( PlayerStateFlag::Casting );
|
|
|
|
}
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
2019-02-10 23:34:05 +11:00
|
|
|
|
2019-03-08 00:26:41 +11:00
|
|
|
// todo: m_recastTimeMs needs to be adjusted for player sks/sps
|
|
|
|
auto actionStartPkt = makeActorControl143( m_pSource->getId(), ActorControlType::ActionStart, 1, getId(), m_recastTimeMs / 10 );
|
|
|
|
player->queuePacket( actionStartPkt );
|
|
|
|
|
2019-02-10 23:34:05 +11:00
|
|
|
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
2019-03-07 20:27:45 +11:00
|
|
|
if( !pScriptMgr->onStart( *this ) )
|
2019-02-11 00:40:00 +11:00
|
|
|
{
|
2019-06-02 00:34:22 +10:00
|
|
|
// check the lut initially and see if we have something usable, otherwise cancel the cast
|
|
|
|
// Sapphire::World::Action::ActionLut::validEntryExists( getId() );
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
// script not implemented
|
2019-03-07 20:33:14 +11:00
|
|
|
interrupt();
|
2019-02-11 00:40:00 +11:00
|
|
|
|
|
|
|
if( player )
|
|
|
|
{
|
2019-03-07 20:27:45 +11:00
|
|
|
player->sendUrgent( "Action not implemented, missing script for action#{0}", getId() );
|
2019-02-11 00:40:00 +11:00
|
|
|
player->setCurrentAction( nullptr );
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// instantly finish cast if there's no cast time
|
|
|
|
if( !hasCastTime() )
|
2019-03-07 20:33:14 +11:00
|
|
|
execute();
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::interrupt()
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
|
|
|
assert( m_pSource );
|
|
|
|
|
2019-02-10 21:01:04 +11:00
|
|
|
// things that aren't players don't care about cooldowns and state flags
|
|
|
|
if( m_pSource->isPlayer() )
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
2019-02-10 21:01:04 +11:00
|
|
|
auto player = m_pSource->getAsPlayer();
|
|
|
|
|
|
|
|
// todo: reset cooldown for actual player
|
|
|
|
|
|
|
|
// reset state flag
|
2019-02-10 21:18:03 +11:00
|
|
|
//player->unsetStateFlag( PlayerStateFlag::Occupied1 );
|
2019-02-10 21:01:04 +11:00
|
|
|
player->unsetStateFlag( PlayerStateFlag::Casting );
|
|
|
|
}
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
if( hasCastTime() )
|
2019-02-10 21:01:04 +11:00
|
|
|
{
|
2019-02-10 21:12:22 +11:00
|
|
|
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 );
|
2019-02-08 22:09:48 +11:00
|
|
|
|
|
|
|
m_pSource->sendToInRangeSet( control, true );
|
|
|
|
}
|
2019-02-10 23:34:05 +11:00
|
|
|
|
|
|
|
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
2019-03-07 20:27:45 +11:00
|
|
|
pScriptMgr->onInterrupt( *this );
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::execute()
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
|
|
|
assert( m_pSource );
|
|
|
|
|
2019-04-04 21:56:59 +11:00
|
|
|
// subtract costs first, if somehow the caster stops meeting those requirements cancel the cast
|
2019-04-04 22:16:00 +11:00
|
|
|
if( !consumeResources() )
|
2019-04-04 21:56:59 +11:00
|
|
|
{
|
|
|
|
interrupt();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 22:09:48 +11:00
|
|
|
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
if( hasCastTime() )
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
2019-03-07 20:27:45 +11:00
|
|
|
// todo: what's this?
|
2019-02-08 22:09:48 +11:00
|
|
|
/*auto control = ActorControlPacket143( m_pTarget->getId(), ActorControlType::Unk7,
|
|
|
|
0x219, m_id, m_id, m_id, m_id );
|
|
|
|
m_pSource->sendToInRangeSet( control, true );*/
|
|
|
|
|
2019-03-07 22:41:05 +11:00
|
|
|
if( auto player = m_pSource->getAsPlayer() )
|
|
|
|
{
|
|
|
|
player->unsetStateFlag( PlayerStateFlag::Casting );
|
|
|
|
}
|
2019-02-10 22:13:47 +11:00
|
|
|
}
|
|
|
|
|
2019-03-23 20:59:41 +11:00
|
|
|
if( isComboAction() )
|
|
|
|
{
|
|
|
|
auto player = m_pSource->getAsPlayer();
|
|
|
|
|
|
|
|
player->sendDebug( "action combo success from action#{0}", player->getLastComboActionId() );
|
|
|
|
}
|
|
|
|
|
2019-03-07 21:58:12 +11:00
|
|
|
if( !hasClientsideTarget() )
|
2019-02-10 22:13:47 +11:00
|
|
|
{
|
2019-04-07 16:04:36 +10:00
|
|
|
snapshotAffectedActors( m_hitActors );
|
|
|
|
|
|
|
|
if( !m_hitActors.empty() )
|
|
|
|
{
|
|
|
|
// only call script if actors are hit
|
|
|
|
pScriptMgr->onExecute( *this );
|
|
|
|
}
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
2019-02-11 14:51:56 +11:00
|
|
|
else if( auto player = m_pSource->getAsPlayer() )
|
2019-02-10 22:13:47 +11:00
|
|
|
{
|
2019-03-19 00:01:34 +11:00
|
|
|
pScriptMgr->onEObjHit( *player, m_targetId, getId() );
|
2019-02-10 23:28:15 +11:00
|
|
|
return;
|
2019-02-10 22:13:47 +11:00
|
|
|
}
|
2019-03-23 20:59:41 +11:00
|
|
|
|
|
|
|
// set currently casted action as the combo action if it interrupts a combo
|
|
|
|
// ignore it otherwise (ogcds, etc.)
|
|
|
|
if( !m_actionData->preservesCombo )
|
|
|
|
{
|
|
|
|
m_pSource->setLastComboActionId( getId() );
|
|
|
|
}
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
2019-02-09 17:36:44 +11:00
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::precheck()
|
2019-03-07 20:27:45 +11:00
|
|
|
{
|
|
|
|
if( auto player = m_pSource->getAsPlayer() )
|
|
|
|
{
|
|
|
|
if( !playerPrecheck( *player ) )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::playerPrecheck( Entity::Player& player )
|
2019-03-07 20:27:45 +11:00
|
|
|
{
|
|
|
|
// lol
|
|
|
|
if( !player.isAlive() )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// npc actions/non player actions
|
|
|
|
if( m_actionData->classJob == -1 )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( player.getLevel() < m_actionData->classJobLevel )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto currentClass = player.getClass();
|
|
|
|
auto actionClass = static_cast< Common::ClassJob >( m_actionData->classJob );
|
|
|
|
|
|
|
|
if( actionClass != Common::ClassJob::Adventurer && currentClass != actionClass )
|
|
|
|
{
|
|
|
|
// check if not a base class action
|
|
|
|
auto exdData = m_pFw->get< Data::ExdDataGenerated >();
|
|
|
|
assert( exdData );
|
|
|
|
|
|
|
|
auto classJob = exdData->get< Data::ClassJob >( static_cast< uint8_t >( currentClass ) );
|
|
|
|
if( !classJob )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( classJob->classJobParent != m_actionData->classJob )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset target on actions that can only be casted on yourself while having a target set
|
|
|
|
// todo: check what actions send when targeting an enemy
|
2019-03-07 21:58:12 +11:00
|
|
|
// if( m_actionData->canTargetSelf &&
|
|
|
|
// !m_actionData->canTargetFriendly &&
|
|
|
|
// !m_actionData->canTargetHostile &&
|
|
|
|
// !m_actionData->canTargetParty )
|
|
|
|
// {
|
|
|
|
// setTargetId( getSourceChara() );
|
|
|
|
// }
|
2019-03-07 20:27:45 +11:00
|
|
|
|
|
|
|
// todo: party/enemy validation
|
|
|
|
|
|
|
|
// validate range
|
|
|
|
|
|
|
|
|
2019-04-04 22:16:00 +11:00
|
|
|
if( !hasResources() )
|
2019-04-04 21:56:59 +11:00
|
|
|
return false;
|
2019-03-07 21:58:12 +11:00
|
|
|
|
2019-03-07 20:27:45 +11:00
|
|
|
return true;
|
2019-03-07 21:58:12 +11:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
uint32_t Action::getAdditionalData() const
|
2019-03-07 21:58:12 +11:00
|
|
|
{
|
|
|
|
return m_additionalData;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::setAdditionalData( uint32_t data )
|
2019-03-07 21:58:12 +11:00
|
|
|
{
|
|
|
|
m_additionalData = data;
|
2019-03-23 20:59:41 +11:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::isComboAction() const
|
2019-03-23 20:59:41 +11:00
|
|
|
{
|
|
|
|
auto lastActionId = m_pSource->getLastComboActionId();
|
|
|
|
|
|
|
|
if( lastActionId == 0 )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return m_actionData->actionCombo == lastActionId;
|
2019-04-04 21:56:59 +11:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::primaryCostCheck( bool subtractCosts )
|
2019-04-04 21:56:59 +11:00
|
|
|
{
|
|
|
|
switch( m_primaryCostType )
|
|
|
|
{
|
|
|
|
case Common::ActionPrimaryCostType::TacticsPoints:
|
|
|
|
{
|
|
|
|
auto curTp = m_pSource->getTp();
|
|
|
|
|
|
|
|
if( curTp < m_primaryCost )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( subtractCosts )
|
|
|
|
m_pSource->setTp( curTp - m_primaryCost );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Common::ActionPrimaryCostType::MagicPoints:
|
|
|
|
{
|
|
|
|
auto curMp = m_pSource->getMp();
|
|
|
|
|
|
|
|
auto cost = Math::CalcStats::calculateMpCost( *m_pSource, m_primaryCost );
|
|
|
|
|
|
|
|
if( curMp < cost )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( subtractCosts )
|
|
|
|
m_pSource->setMp( curMp - cost );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-04-04 22:00:36 +11:00
|
|
|
// free casts, likely just pure ogcds
|
2019-04-04 21:56:59 +11:00
|
|
|
case Common::ActionPrimaryCostType::None:
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::secondaryCostCheck( bool subtractCosts )
|
2019-04-04 21:56:59 +11:00
|
|
|
{
|
|
|
|
// todo: these need to be mapped
|
|
|
|
return true;
|
2019-04-04 22:16:00 +11:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::hasResources()
|
2019-04-04 22:16:00 +11:00
|
|
|
{
|
|
|
|
return primaryCostCheck( false ) && secondaryCostCheck( false );
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::consumeResources()
|
2019-04-04 22:16:00 +11:00
|
|
|
{
|
|
|
|
return primaryCostCheck( true ) && secondaryCostCheck( true );
|
2019-04-07 16:04:36 +10:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::snapshotAffectedActors( std::vector< Entity::CharaPtr >& actors )
|
2019-04-07 16:04:36 +10:00
|
|
|
{
|
2019-05-28 22:03:55 +10:00
|
|
|
for( const auto& actor : m_pSource->getInRangeActors( true ) )
|
2019-04-07 16:04:36 +10:00
|
|
|
{
|
|
|
|
// check for initial target validity based on flags in action exd (pc/enemy/etc.)
|
|
|
|
if( !preFilterActor( *actor ) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for( const auto& filter : m_actorFilters )
|
|
|
|
{
|
|
|
|
if( filter->conditionApplies( *actor ) )
|
|
|
|
{
|
|
|
|
actors.push_back( actor->getAsChara() );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( auto player = m_pSource->getAsPlayer() )
|
|
|
|
{
|
|
|
|
player->sendDebug( "Hit {} actors with {} filters", actors.size(), m_actorFilters.size() );
|
|
|
|
for( const auto& actor : actors )
|
|
|
|
{
|
|
|
|
player->sendDebug( "hit actor#{}", actor->getId() );
|
|
|
|
}
|
|
|
|
}
|
2019-05-28 22:03:55 +10:00
|
|
|
|
|
|
|
return !actors.empty();
|
2019-04-07 16:04:36 +10:00
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::addActorFilter( World::Util::ActorFilterPtr filter )
|
2019-04-07 16:04:36 +10:00
|
|
|
{
|
|
|
|
m_actorFilters.push_back( std::move( filter ) );
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
void Action::addDefaultActorFilters()
|
2019-04-07 16:04:36 +10:00
|
|
|
{
|
|
|
|
switch( m_castType )
|
|
|
|
{
|
|
|
|
case Common::CastType::SingleTarget:
|
|
|
|
{
|
|
|
|
auto filter = std::make_shared< World::Util::ActorFilterSingleTarget >( static_cast< uint32_t >( m_targetId ) );
|
|
|
|
|
|
|
|
addActorFilter( filter );
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Common::CastType::CircularAOE:
|
|
|
|
{
|
|
|
|
auto filter = std::make_shared< World::Util::ActorFilterInRange >( m_pos, m_effectRange );
|
|
|
|
|
|
|
|
addActorFilter( filter );
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// case Common::CastType::RectangularAOE:
|
|
|
|
// {
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
Logger::error( "[{}] Action#{} has CastType#{} but that cast type is unhandled. Cancelling cast.",
|
|
|
|
m_pSource->getId(), getId(), m_castType );
|
|
|
|
|
|
|
|
interrupt();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
bool Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
|
2019-04-07 16:04:36 +10:00
|
|
|
{
|
|
|
|
auto kind = actor.getObjKind();
|
|
|
|
|
|
|
|
// todo: are there any server side eobjs that players can hit?
|
|
|
|
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// todo: handle things such based on canTargetX
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
std::vector< Sapphire::Entity::CharaPtr >& Action::getHitCharas()
|
2019-04-07 16:04:36 +10:00
|
|
|
{
|
|
|
|
return m_hitActors;
|
|
|
|
}
|
|
|
|
|
2019-06-02 00:34:22 +10:00
|
|
|
Sapphire::Entity::CharaPtr Action::getHitChara()
|
2019-04-07 16:04:36 +10:00
|
|
|
{
|
|
|
|
if( !m_hitActors.empty() )
|
|
|
|
{
|
|
|
|
return m_hitActors.at( 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
2019-02-09 23:14:30 +11:00
|
|
|
}
|