1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-28 23:27:45 +00:00

add ref to Data::Action inside action obj, move can cast logic to action

This commit is contained in:
NotAdam 2019-03-07 20:27:45 +11:00
parent 2f9d86f962
commit 9d6622b522
15 changed files with 190 additions and 341 deletions

View file

@ -537,19 +537,19 @@ namespace Sapphire::Common
Unaspected = 7 // Doesn't imply magical unaspected damage - could be unaspected physical Unaspected = 7 // Doesn't imply magical unaspected damage - could be unaspected physical
}; };
enum class ActionCostType : uint8_t enum class ActionPrimaryCostType : uint8_t
{ {
None = 0, // ? // None = 0, // ?
MagicPoints = 3, MagicPoints = 3,
TacticsPoints = 5, TacticsPoints = 5,
WARGauge = 22, // WARGauge = 22,
DRKGauge = 25, // DRKGauge = 25,
AetherflowStack = 30, // AetherflowStack = 30,
Status = 32, // Status = 32,
PLDGauge = 41, // PLDGauge = 41,
RDMGaugeBoth = 74, // RDMGaugeBoth = 74,
// RDMGaugeBlack = 75, // not right? //// RDMGaugeBlack = 75, // not right?
DRGGauge3Eyes = 76, // DRGGauge3Eyes = 76,
}; };
enum class ActionType : int8_t enum class ActionType : int8_t

View file

@ -12,7 +12,7 @@ public:
{ {
} }
void onCastFinish( Sapphire::Action::Action& currentAction ) override void onFinish( Sapphire::Action::Action& currentAction ) override
{ {
if( !currentAction.getSourceChara()->isPlayer() ) if( !currentAction.getSourceChara()->isPlayer() )
return; return;

View file

@ -11,7 +11,7 @@ public:
{ {
} }
void onCastFinish( Sapphire::Action::Action& currentAction ) override void onFinish( Sapphire::Action::Action& currentAction ) override
{ {
auto sourceChara = currentAction.getSourceChara(); auto sourceChara = currentAction.getSourceChara();

View file

@ -12,9 +12,10 @@ public:
{ {
} }
void onCharaHit( Sapphire::Action::Action& currentAction, Sapphire::Entity::Chara& hitActor ) override void onFinish( Sapphire::Action::Action& currentAction ) override
{ {
currentAction.damageTarget( 150, hitActor ); if( auto player = currentAction.getSourceChara()->getAsPlayer() )
player->sendDebug( "Imagine you just hit an enemy for 150 potency. Incredible, right?" );
} }
}; };

View file

@ -27,7 +27,7 @@ Sapphire::Action::Action::Action() = default;
Sapphire::Action::Action::~Action() = default; Sapphire::Action::Action::~Action() = default;
Sapphire::Action::Action::Action( Entity::CharaPtr caster, uint32_t actionId, Sapphire::Action::Action::Action( Entity::CharaPtr caster, uint32_t actionId,
Data::ActionPtr action, FrameworkPtr fw ) : Data::ActionPtr actionData, FrameworkPtr fw ) :
m_pSource( std::move( caster ) ), m_pSource( std::move( caster ) ),
m_pFw( std::move( fw ) ), m_pFw( std::move( fw ) ),
m_id( actionId ), m_id( actionId ),
@ -35,17 +35,19 @@ Sapphire::Action::Action::Action( Entity::CharaPtr caster, uint32_t actionId,
m_interruptType( Common::ActionInterruptType::None ), m_interruptType( Common::ActionInterruptType::None ),
m_hasResidentTarget( false ) m_hasResidentTarget( false )
{ {
m_castTime = static_cast< uint32_t >( action->cast100ms * 100 ); m_actionData = actionData;
m_recastTime = static_cast< uint16_t >( action->recast100ms * 100 );
m_cooldownGroup = action->cooldownGroup; m_castTime = static_cast< uint32_t >( actionData->cast100ms * 100 );
m_range = action->range; m_recastTime = static_cast< uint16_t >( actionData->recast100ms * 100 );
m_effectRange = action->effectRange; m_cooldownGroup = actionData->cooldownGroup;
m_aspect = static_cast< Common::ActionAspect >( action->aspect ); m_range = actionData->range;
m_effectRange = actionData->effectRange;
m_aspect = static_cast< Common::ActionAspect >( actionData->aspect );
// a default range is set by the game for the class/job // a default range is set by the game for the class/job
if( m_range == -1 ) if( m_range == -1 )
{ {
switch( static_cast< Common::ClassJob >( action->classJob ) ) switch( static_cast< Common::ClassJob >( actionData->classJob ) )
{ {
case Common::ClassJob::Bard: case Common::ClassJob::Bard:
case Common::ClassJob::Archer: case Common::ClassJob::Archer:
@ -57,12 +59,10 @@ Sapphire::Action::Action::Action( Entity::CharaPtr caster, uint32_t actionId,
} }
} }
m_actionCost.fill( { Common::ActionCostType::None, 0 } ); m_primaryCostType = static_cast< Common::ActionPrimaryCostType >( actionData->costType );
m_primaryCost = actionData->cost;
m_actionCost[ 0 ] = { // todo: add missing rows for secondaryCostType/secondaryCostType and rename the current rows to primaryCostX
static_cast< Common::ActionCostType >( action->costType ),
action->cost
};
calculateActionCost(); calculateActionCost();
} }
@ -145,7 +145,7 @@ bool Sapphire::Action::Action::update()
if( isInterrupted() ) if( isInterrupted() )
{ {
castInterrupt(); onInterrupt();
return true; return true;
} }
@ -158,14 +158,14 @@ bool Sapphire::Action::Action::update()
if( !hasCastTime() || std::difftime( currTime, m_startTime ) > m_castTime ) if( !hasCastTime() || std::difftime( currTime, m_startTime ) > m_castTime )
{ {
castFinish(); onExecute();
return true; return true;
} }
return false; return false;
} }
void Sapphire::Action::Action::castStart() void Sapphire::Action::Action::onStart()
{ {
assert( m_pSource ); assert( m_pSource );
@ -193,14 +193,14 @@ void Sapphire::Action::Action::castStart()
} }
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >(); auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
if( !pScriptMgr->onCastStart( *this ) ) if( !pScriptMgr->onStart( *this ) )
{ {
// script not implemented // script not implemented
castInterrupt(); onInterrupt();
if( player ) if( player )
{ {
player->sendUrgent( "Action not implemented, missing script for actionId#{0}", getId() ); player->sendUrgent( "Action not implemented, missing script for action#{0}", getId() );
player->setCurrentAction( nullptr ); player->setCurrentAction( nullptr );
} }
@ -209,10 +209,10 @@ void Sapphire::Action::Action::castStart()
// instantly finish cast if there's no cast time // instantly finish cast if there's no cast time
if( !hasCastTime() ) if( !hasCastTime() )
castFinish(); onExecute();
} }
void Sapphire::Action::Action::castInterrupt() void Sapphire::Action::Action::onInterrupt()
{ {
assert( m_pSource ); assert( m_pSource );
@ -246,10 +246,10 @@ void Sapphire::Action::Action::castInterrupt()
} }
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >(); auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
pScriptMgr->onCastInterrupt( *this ); pScriptMgr->onInterrupt( *this );
} }
void Sapphire::Action::Action::castFinish() void Sapphire::Action::Action::onExecute()
{ {
assert( m_pSource ); assert( m_pSource );
@ -257,170 +257,60 @@ void Sapphire::Action::Action::castFinish()
if( hasCastTime() ) if( hasCastTime() )
{ {
// todo: what's this?
/*auto control = ActorControlPacket143( m_pTarget->getId(), ActorControlType::Unk7, /*auto control = ActorControlPacket143( m_pTarget->getId(), ActorControlType::Unk7,
0x219, m_id, m_id, m_id, m_id ); 0x219, m_id, m_id, m_id, m_id );
m_pSource->sendToInRangeSet( control, true );*/ m_pSource->sendToInRangeSet( control, true );*/
} }
pScriptMgr->onCastFinish( *this );
if( !hasResidentTarget() ) if( !hasResidentTarget() )
{ {
assert( m_pTarget ); assert( m_pTarget );
// todo: calculate final hit targets and call onCharaHit in action script pScriptMgr->onExecute( *this );
pScriptMgr->onCharaHit( *this, *m_pTarget );
} }
else if( auto player = m_pSource->getAsPlayer() ) else if( auto player = m_pSource->getAsPlayer() )
{ {
pScriptMgr->onEObjHit( *player, m_targetId ); pScriptMgr->onEObjHit( *player, m_targetId );
return; return;
} }
buildEffectPackets();
}
void Sapphire::Action::Action::buildEffectPackets()
{
for( int i = 0; i < EffectPacketIdentity::MAX_ACTION_EFFECT_PACKET_IDENT; ++i )
{
auto& packetData = m_effects[ static_cast< EffectPacketIdentity >( i ) ];
auto actorsHit = packetData.m_hitActors.size();
if( actorsHit == 0 )
continue;
// get effect sequence
auto zone = m_pSource->getCurrentZone();
assert( zone );
auto sequence = zone->getNextEffectSequence();
if( actorsHit == 1 )
{
// send normal effect
auto effectPacket = std::make_shared< Network::Packets::Server::EffectPacket >( m_pSource->getId(), m_pTarget->getId(), getId() );
effectPacket->setTargetActor( packetData.m_hitActors[ 0 ] );
effectPacket->setSequence( sequence );
effectPacket->setDisplayType( Common::ActionEffectDisplayType::ShowActionName );
for( auto& effect : packetData.m_entries )
{
effectPacket->addEffect( effect );
}
m_pSource->sendToInRangeSet( effectPacket, true );
}
else
{
// todo: aoe effects
}
}
}
void Sapphire::Action::Action::damageTarget( uint16_t potency, Entity::Chara& chara )
{
// todo: scale potency into damage from stats
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( potency > 65535 )
{
entry.value = static_cast< int16_t >( potency / 10 );
// todo: rename this? need to confirm how it works again
entry.valueMultiplier = 1;
}
else
entry.value = static_cast< int16_t >( potency );
// add to aggro table
// todo: probably move this into takeDamage? this is pretty garbage
if( chara.isBattleNpc() )
{
auto bNpc = chara.getAsBNpc();
if( bNpc )
{
if( bNpc->getStance() != Common::Stance::Active )
{
bNpc->aggro( getSourceChara() );
bNpc->hateListUpdate( getSourceChara(), potency );
}
else
{
bNpc->hateListUpdate( getSourceChara(), potency );
}
}
}
// todo: aspected damage?
chara.takeDamage( potency );
if( auto player = m_pSource->getAsPlayer() )
player->sendDebug( "hit actorId#{0} for potency: {1}", chara.getId(), potency );
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( uint16_t potency, Entity::Chara& chara )
{
// todo: scale potency into healing from stats
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( potency > 65535 )
{
entry.value = static_cast< int16_t >( potency / 10 );
// todo: rename this? need to confirm how it works again
entry.valueMultiplier = 1;
}
else
entry.value = static_cast< int16_t >( potency );
chara.heal( potency );
if( auto player = m_pSource->getAsPlayer() )
player->sendDebug( "hit actorId#{0} for heal: {1}", chara.getId(), potency );
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() void Sapphire::Action::Action::calculateActionCost()
{ {
for( uint8_t i = 0; i < m_actionCost.size(); ++i ) // todo: just a test handler for now to get MP output for each cast, not sure where we should put this
// check primary cost
switch( m_primaryCostType )
{ {
auto& entry = m_actionCost[ i ]; case ActionPrimaryCostType::MagicPoints:
{
calculateMPCost( m_primaryCost );
break;
}
if( entry.m_costType == ActionCostType::MagicPoints ) case ActionPrimaryCostType::TacticsPoints:
calculateMPCost( i ); {
break;
}
default:
{
if( auto player = m_pSource->getAsPlayer() )
{
player->sendDebug( "action#{0} is missing a handler for cost type: {1}",
m_id, static_cast< uint8_t >( m_primaryCostType ) );
}
break;
}
} }
// todo: secondary cost type needs to be handled
} }
// todo: this shouldn't be in action and instead be in some general stat calc util // todo: this shouldn't be in action and instead be in some general stat calc util
void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex ) void Sapphire::Action::Action::calculateMPCost( uint16_t baseCost )
{ {
auto level = m_pSource->getLevel(); auto level = m_pSource->getLevel();
@ -429,9 +319,7 @@ void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
// dividing by 10 on the border will break this unless we subtract 1 // dividing by 10 on the border will break this unless we subtract 1
auto levelGroup = std::max< uint8_t >( level - 1, 1 ) / 10; auto levelGroup = std::max< uint8_t >( level - 1, 1 ) / 10;
auto& costEntry = m_actionCost[ costArrayIndex ]; float cost = baseCost;
float cost = costEntry.m_cost;
// thanks to andrew for helping me figure this shit out, should be pretty accurate // thanks to andrew for helping me figure this shit out, should be pretty accurate
switch( levelGroup ) switch( levelGroup )
@ -444,7 +332,7 @@ void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
break; break;
} }
// level 11-20 // level 11-20
case 1: case 1:
{ {
// r^2 = 1 // r^2 = 1
@ -452,7 +340,7 @@ void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
break; break;
} }
// level 21-30 // level 21-30
case 2: case 2:
{ {
// r^2 = 1 // r^2 = 1
@ -460,7 +348,7 @@ void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
break; break;
} }
// level 31-40 // level 31-40
case 3: case 3:
{ {
// r^2 = 1 // r^2 = 1
@ -476,7 +364,7 @@ void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
break; break;
} }
// level 51-60 // level 51-60
case 5: case 5:
{ {
// r^2 = 1 // r^2 = 1
@ -484,7 +372,7 @@ void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
break; break;
} }
// level 61-70 // level 61-70
case 6: case 6:
{ {
// r^2 = 0.9998 // r^2 = 0.9998
@ -496,9 +384,70 @@ void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
return; return;
} }
// m_cost is the base cost, cost is the multiplier for the current player level // m_primaryCost 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 ) ); m_primaryCost = static_cast< uint16_t >( std::round( cost * baseCost ) );
if( auto player = m_pSource->getAsPlayer() ) if( auto player = m_pSource->getAsPlayer() )
player->sendDebug( "calculated mp cost: {0}", costEntry.m_cost ); player->sendDebug( "calculated mp cost: {0}", m_primaryCost );
}
bool Sapphire::Action::Action::precheck()
{
if( auto player = m_pSource->getAsPlayer() )
{
if( !playerPrecheck( *player ) )
return false;
}
return true;
}
bool Sapphire::Action::Action::playerPrecheck( Entity::Player& player )
{
// 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
if( m_actionData->canTargetSelf &&
!m_actionData->canTargetFriendly &&
!m_actionData->canTargetHostile &&
!m_actionData->canTargetParty )
{
setTargetChara( getSourceChara() );
}
// todo: party/enemy validation
// validate range
// todo: validate costs/conditionals here
return true;
} }

View file

@ -18,16 +18,9 @@ namespace Sapphire::Action
{ {
public: public:
struct ActionCostEntry
{
Common::ActionCostType m_costType;
uint16_t m_cost;
};
using ActionCostArray = std::array< ActionCostEntry, 2 >;
Action(); Action();
Action( Entity::CharaPtr caster, uint32_t actionId, Data::ActionPtr action, FrameworkPtr fw ); Action( Entity::CharaPtr caster, uint32_t actionId, Data::ActionPtr actionData, FrameworkPtr fw );
virtual ~Action(); virtual ~Action();
@ -53,36 +46,34 @@ namespace Sapphire::Action
*/ */
bool hasResidentTarget() const; bool hasResidentTarget() const;
const ActionCostArray& getCostArray() const;
/*! /*!
* @brief Tests whether the action is instantly usable or has a cast assoc'd with it * @brief Tests whether the action is instantly usable or has a cast assoc'd with it
* @return true if action has a cast time * @return true if action has a cast time
*/ */
bool hasCastTime() const; bool hasCastTime() const;
void buildEffectPackets();
/*! /*!
* @brief Damages a target and adds the effect entry * @brief Tests if an action is castable by the current source chara
* @param potency The amount of damage the target takes * @return true if castable, false if the caster doesn't meet the requirements
* @param chara The chara to inflict damage upon
*/ */
void damageTarget( uint16_t potency, Entity::Chara& chara ); bool precheck();
/*!
* @brief Heals a target and adds the effect entry
* @param potency Amount of healing to apply
* @param chara Chara to receive healing
*/
void healTarget( uint16_t potency, Entity::Chara& chara );
/*! /*!
* @brief Starts the cast. Finishes it immediately if there is no cast time (weaponskills). * @brief Starts the cast. Finishes it immediately if there is no cast time (weaponskills).
*/ */
virtual void castStart(); virtual void onStart();
virtual void castFinish();
virtual void castInterrupt(); /*!
* @brief Finishes the cast, effected targets are calculated here.
*/
virtual void onExecute();
/*!
* @brief Called when a cast is interrupted for any reason
*
* m_interruptType will have the reason why the action was interrupted (eg. damage, movement, ...)
*/
virtual void onInterrupt();
// update action, if returns true, action is done and has to be removed from the actor // update action, if returns true, action is done and has to be removed from the actor
virtual bool update(); virtual bool update();
@ -90,31 +81,14 @@ namespace Sapphire::Action
protected: protected:
void calculateActionCost(); void calculateActionCost();
void calculateMPCost( uint8_t costArrayIndex ); void calculateMPCost( uint16_t baseCost );
/*! bool playerPrecheck( Entity::Player& player );
* @brief Some actions are capable of both healing and dealing damage. This identifies them.
*/
enum EffectPacketIdentity : uint8_t
{
DamageEffect,
HealingEffect,
MAX_ACTION_EFFECT_PACKET_IDENT
};
struct EffectPacketData
{
std::vector< Common::EffectEntry > m_entries;
std::vector< uint32_t > m_hitActors;
};
uint32_t m_id; uint32_t m_id;
Common::ActionCostType m_costType; Common::ActionPrimaryCostType m_primaryCostType;
uint16_t m_cost; uint16_t m_primaryCost;
ActionCostArray m_actionCost;
uint64_t m_startTime; uint64_t m_startTime;
uint32_t m_castTime; uint32_t m_castTime;
@ -124,6 +98,7 @@ namespace Sapphire::Action
uint8_t m_effectRange; uint8_t m_effectRange;
Common::ActionAspect m_aspect; Common::ActionAspect m_aspect;
Entity::CharaPtr m_pSource; Entity::CharaPtr m_pSource;
Entity::CharaPtr m_pTarget; Entity::CharaPtr m_pTarget;
uint64_t m_targetId; uint64_t m_targetId;
@ -132,10 +107,9 @@ namespace Sapphire::Action
Common::ActionInterruptType m_interruptType; Common::ActionInterruptType m_interruptType;
FrameworkPtr m_pFw; FrameworkPtr m_pFw;
Data::ActionPtr m_actionData;
Common::FFXIVARR_POSITION3 m_pos; Common::FFXIVARR_POSITION3 m_pos;
std::array< EffectPacketData, MAX_ACTION_EFFECT_PACKET_IDENT > m_effects;
}; };
} }

View file

@ -35,7 +35,7 @@ Sapphire::Action::EventAction::EventAction( Entity::CharaPtr pActor, uint32_t ev
Sapphire::Action::EventAction::~EventAction() = default; Sapphire::Action::EventAction::~EventAction() = default;
void Sapphire::Action::EventAction::castStart() void Sapphire::Action::EventAction::onStart()
{ {
if( !m_pSource ) if( !m_pSource )
return; return;
@ -54,7 +54,7 @@ void Sapphire::Action::EventAction::castStart()
m_pSource->sendToInRangeSet( control ); m_pSource->sendToInRangeSet( control );
} }
void Sapphire::Action::EventAction::castFinish() void Sapphire::Action::EventAction::onExecute()
{ {
if( !m_pSource ) if( !m_pSource )
return; return;
@ -90,7 +90,7 @@ void Sapphire::Action::EventAction::castFinish()
} }
void Sapphire::Action::EventAction::castInterrupt() void Sapphire::Action::EventAction::onInterrupt()
{ {
if( !m_pSource ) if( !m_pSource )
return; return;

View file

@ -18,11 +18,11 @@ public:
EventAction( Entity::CharaPtr pActor, uint32_t eventId, uint16_t action, EventAction( Entity::CharaPtr pActor, uint32_t eventId, uint16_t action,
ActionCallback finishRef, ActionCallback interruptRef, uint64_t additional, FrameworkPtr pFw ); ActionCallback finishRef, ActionCallback interruptRef, uint64_t additional, FrameworkPtr pFw );
void castStart() override; void onStart() override;
void castFinish() override; void onExecute() override;
void castInterrupt() override; void onInterrupt() override;
private: private:
uint32_t m_eventId; uint32_t m_eventId;

View file

@ -303,7 +303,7 @@ void Sapphire::Entity::Player::eventActionStart( uint32_t eventId,
pEvent->setPlayedScene( true ); pEvent->setPlayedScene( true );
setCurrentAction( pEventAction ); setCurrentAction( pEventAction );
pEventAction->castStart(); pEventAction->onStart();
} }
@ -318,7 +318,7 @@ void Sapphire::Entity::Player::eventItemActionStart( uint32_t eventId,
// //
// setCurrentAction( pEventItemAction ); // setCurrentAction( pEventItemAction );
// //
// pEventItemAction->onCastStart(); // pEventItemAction->onStart();
} }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -29,7 +29,7 @@ void World::Manager::ActionMgr::handleAoEPlayerAction( Entity::Player& player, u
if( !actionData->targetArea ) if( !actionData->targetArea )
{ {
// not an action that has an aoe, cancel it // not an action that has an aoe, cancel it
action->castInterrupt(); action->onInterrupt();
return; return;
} }
@ -44,7 +44,7 @@ void World::Manager::ActionMgr::handleTargetedPlayerAction( Entity::Player& play
// cancel any aoe actions casted with this packet // cancel any aoe actions casted with this packet
if( actionData->targetArea ) if( actionData->targetArea )
{ {
action->castInterrupt(); action->onInterrupt();
return; return;
} }
@ -99,10 +99,10 @@ void World::Manager::ActionMgr::bootstrapAction( Entity::Player& player,
Action::ActionPtr currentAction, Action::ActionPtr currentAction,
Data::Action& actionData ) Data::Action& actionData )
{ {
if( !canPlayerUseAction( player, *currentAction, actionData ) ) if( !currentAction->precheck() )
{ {
// forcefully interrupt the action and reset the cooldown // forcefully interrupt the action and reset the cooldown
currentAction->castInterrupt(); currentAction->onInterrupt();
return; return;
} }
@ -113,63 +113,7 @@ void World::Manager::ActionMgr::bootstrapAction( Entity::Player& player,
} }
// todo: what do in cases of swiftcast/etc? script callback? // todo: what do in cases of swiftcast/etc? script callback?
currentAction->castStart(); currentAction->onStart();
}
bool World::Manager::ActionMgr::canPlayerUseAction( Entity::Player& player,
Action::Action& currentAction,
Data::Action& actionData )
{
// lol
if( !player.isAlive() )
return false;
// npc actions/non player actions
if( actionData.classJob == -1 )
return false;
if( player.getLevel() < actionData.classJobLevel )
return false;
auto currentClass = player.getClass();
auto actionClass = static_cast< Common::ClassJob >( actionData.classJob );
if( actionClass != Common::ClassJob::Adventurer && currentClass != actionClass )
{
// check if not a base class action
auto exdData = framework()->get< Data::ExdDataGenerated >();
assert( exdData );
auto classJob = exdData->get< Data::ClassJob >( static_cast< uint8_t >( currentClass ) );
if( !classJob )
return false;
if( classJob->classJobParent != 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
if( actionData.canTargetSelf &&
!actionData.canTargetFriendly &&
!actionData.canTargetHostile &&
!actionData.canTargetParty )
{
currentAction.setTargetChara( currentAction.getSourceChara() );
}
// todo: party/enemy validation
// validate range
auto& actionCost = currentAction.getCostArray();
for( uint8_t i = 0; i < actionCost.size(); ++i )
{
// todo: validate costs/conditionals here
}
return true;
} }
void World::Manager::ActionMgr::handleItemActionVFX( Sapphire::Entity::Player& player, uint32_t itemId, uint16_t vfxId ) void World::Manager::ActionMgr::handleItemActionVFX( Sapphire::Entity::Player& player, uint32_t itemId, uint16_t vfxId )

View file

@ -31,7 +31,6 @@ namespace Sapphire::World::Manager
private: private:
void bootstrapAction( Entity::Player& player, Action::ActionPtr currentAction, Data::Action& actionData ); void bootstrapAction( Entity::Player& player, Action::ActionPtr currentAction, Data::Action& actionData );
bool canPlayerUseAction( Entity::Player& player, Action::Action& currentAction, Data::Action& actionData );
// item action handlers // item action handlers
void handleItemActionVFX( Entity::Player& player, uint32_t itemId, uint16_t vfxId ); void handleItemActionVFX( Entity::Player& player, uint32_t itemId, uint16_t vfxId );

View file

@ -88,19 +88,15 @@ namespace Sapphire::ScriptAPI
{ {
} }
void ActionScript::onCastStart( Sapphire::Action::Action& currentAction ) void ActionScript::onStart( Sapphire::Action::Action& currentAction )
{ {
} }
void ActionScript::onCastFinish( Sapphire::Action::Action& currentAction ) void ActionScript::onFinish( Sapphire::Action::Action& currentAction )
{ {
} }
void ActionScript::onCastInterrupt( Sapphire::Action::Action& currentAction ) void ActionScript::onInterrupt( Sapphire::Action::Action& currentAction )
{
}
void ActionScript::onCharaHit( Sapphire::Action::Action& currentAction, Sapphire::Entity::Chara& hitActor )
{ {
} }

View file

@ -140,13 +140,11 @@ namespace Sapphire::ScriptAPI
public: public:
explicit ActionScript( uint32_t abilityId ); explicit ActionScript( uint32_t abilityId );
virtual void onCastStart( Sapphire::Action::Action& currentAction ); virtual void onStart( Sapphire::Action::Action& currentAction );
virtual void onCastFinish( Sapphire::Action::Action& currentAction ); virtual void onFinish( Sapphire::Action::Action& currentAction );
virtual void onCastInterrupt( Sapphire::Action::Action& currentAction ); virtual void onInterrupt( Sapphire::Action::Action& currentAction );
virtual void onCharaHit( Sapphire::Action::Action& currentAction, Sapphire::Entity::Chara& hitActor );
}; };
/*! /*!

View file

@ -328,50 +328,37 @@ bool Sapphire::Scripting::ScriptMgr::onEObjHit( Sapphire::Entity::Player& player
return didCallScript; return didCallScript;
} }
bool Sapphire::Scripting::ScriptMgr::onCastFinish( Action::Action& currentAction ) bool Sapphire::Scripting::ScriptMgr::onExecute( Action::Action& currentAction )
{ {
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( currentAction.getId() ); auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( currentAction.getId() );
if( script ) if( script )
{ {
script->onCastFinish( currentAction ); script->onFinish( currentAction );
return true; return true;
} }
return false; return false;
} }
bool Sapphire::Scripting::ScriptMgr::onCastInterrupt( Action::Action& currentAction ) bool Sapphire::Scripting::ScriptMgr::onInterrupt( Action::Action& currentAction )
{ {
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( currentAction.getId() ); auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( currentAction.getId() );
if( script ) if( script )
{ {
script->onCastInterrupt( currentAction ); script->onInterrupt( currentAction );
return true; return true;
} }
return false; return false;
} }
bool Sapphire::Scripting::ScriptMgr::onCastStart( Action::Action& currentAction ) bool Sapphire::Scripting::ScriptMgr::onStart( Action::Action& currentAction )
{ {
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( currentAction.getId() ); auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( currentAction.getId() );
if( script ) if( script )
{ {
script->onCastStart( currentAction ); script->onStart( currentAction );
return true;
}
return false;
}
bool Sapphire::Scripting::ScriptMgr::onCharaHit( Action::Action& currentAction, Entity::Chara& hitActor )
{
auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::ActionScript >( currentAction.getId() );
if( script )
{
script->onCharaHit( currentAction, hitActor );
return true; return true;
} }

View file

@ -72,10 +72,11 @@ namespace Sapphire::Scripting
bool onEObjHit( Entity::Player& player, uint64_t actorId ); bool onEObjHit( Entity::Player& player, uint64_t actorId );
bool onCastStart( Action::Action& currentAction ); bool onStart( Action::Action& currentAction );
bool onCastInterrupt( Action::Action& currentAction );
bool onCastFinish( Action::Action& currentAction ); bool onInterrupt( Action::Action& currentAction );
bool onCharaHit( Action::Action& currentAction, Entity::Chara& hitActor );
bool onExecute( Action::Action& currentAction );
bool onStatusReceive( Entity::CharaPtr pActor, uint32_t effectId ); bool onStatusReceive( Entity::CharaPtr pActor, uint32_t effectId );