mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-26 14:37:44 +00:00
commit
12ebf2e997
18 changed files with 1898 additions and 1393 deletions
|
@ -629,6 +629,7 @@ namespace Sapphire::Common
|
||||||
* @param value The actionid that starts/continues the combo. eg, 3617 will start a spinning slash and/or syphon strike combo
|
* @param value The actionid that starts/continues the combo. eg, 3617 will start a spinning slash and/or syphon strike combo
|
||||||
*/
|
*/
|
||||||
StartActionCombo = 28,
|
StartActionCombo = 28,
|
||||||
|
ComboSucceed = 29,
|
||||||
Knockback = 33,
|
Knockback = 33,
|
||||||
Mount = 38,
|
Mount = 38,
|
||||||
VFX = 59, // links to VFX sheet
|
VFX = 59, // links to VFX sheet
|
||||||
|
@ -644,6 +645,12 @@ namespace Sapphire::Common
|
||||||
CritDirectHitDamage = 3
|
CritDirectHitDamage = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ActionEffectResultFlag : uint8_t
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
EffectOnSource = 0x80,
|
||||||
|
};
|
||||||
|
|
||||||
enum ItemActionType : uint16_t
|
enum ItemActionType : uint16_t
|
||||||
{
|
{
|
||||||
ItemActionVFX = 852,
|
ItemActionVFX = 852,
|
||||||
|
|
|
@ -156,7 +156,7 @@ namespace Sapphire::Network::Packets
|
||||||
SilentSetClassJob = 0x018E, // updated 5.0 - seems to be the case, not sure if it's actually used for anything
|
SilentSetClassJob = 0x018E, // updated 5.0 - seems to be the case, not sure if it's actually used for anything
|
||||||
PlayerSetup = 0x0295, // updated 5.18
|
PlayerSetup = 0x0295, // updated 5.18
|
||||||
PlayerStats = 0x017A, // updated 5.18
|
PlayerStats = 0x017A, // updated 5.18
|
||||||
ActorOwner = 0x0322, // updated 5.11
|
ActorOwner = 0x03BB, // updated 5.18
|
||||||
PlayerStateFlags = 0x02C6, // updated 5.18
|
PlayerStateFlags = 0x02C6, // updated 5.18
|
||||||
PlayerClassInfo = 0x01B0, // updated 5.18
|
PlayerClassInfo = 0x01B0, // updated 5.18
|
||||||
|
|
||||||
|
|
|
@ -537,33 +537,6 @@ namespace Sapphire::Network::Packets::Server
|
||||||
/* 0012 */ uint32_t unknown_12;
|
/* 0012 */ uint32_t unknown_12;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structural representation of the packet sent by the server
|
|
||||||
* for battle actions
|
|
||||||
*/
|
|
||||||
struct EffectHeader
|
|
||||||
{
|
|
||||||
uint64_t animationTargetId; // who the animation targets
|
|
||||||
|
|
||||||
uint32_t actionId; // what the casting player casts, shown in battle log/ui
|
|
||||||
uint32_t globalEffectCounter; // seems to only increment on retail?
|
|
||||||
|
|
||||||
float animationLockTime; // maybe? doesn't seem to do anything
|
|
||||||
uint32_t someTargetId; // always 00 00 00 E0, 0x0E000000 is the internal def for INVALID TARGET ID
|
|
||||||
|
|
||||||
uint16_t hiddenAnimation; // if 0, always shows animation, otherwise hides it. counts up by 1 for each animation skipped on a caster
|
|
||||||
uint16_t rotation;
|
|
||||||
uint16_t actionAnimationId; // the animation that is played by the casting character
|
|
||||||
uint8_t variation; // variation in the animation
|
|
||||||
Common::ActionEffectDisplayType effectDisplayType;
|
|
||||||
|
|
||||||
uint8_t unknown20; // is read by handler, runs code which gets the LODWORD of animationLockTime (wtf?)
|
|
||||||
uint8_t effectCount; // ignores effects if 0, otherwise parses all of them
|
|
||||||
uint16_t padding_21;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FFXIVIpcEffect : FFXIVIpcBasePacket< Effect >
|
struct FFXIVIpcEffect : FFXIVIpcBasePacket< Effect >
|
||||||
{
|
{
|
||||||
uint64_t animationTargetId; // who the animation targets
|
uint64_t animationTargetId; // who the animation targets
|
||||||
|
@ -608,17 +581,35 @@ namespace Sapphire::Network::Packets::Server
|
||||||
template< int size >
|
template< int size >
|
||||||
struct FFXIVIpcAoeEffect
|
struct FFXIVIpcAoeEffect
|
||||||
{
|
{
|
||||||
EffectHeader header;
|
uint64_t animationTargetId; // who the animation targets
|
||||||
|
|
||||||
Common::EffectEntry effects[size];
|
uint32_t actionId; // what the casting player casts, shown in battle log/ui
|
||||||
|
uint32_t globalSequence; // seems to only increment on retail?
|
||||||
|
|
||||||
|
float animationLockTime; // maybe? doesn't seem to do anything
|
||||||
|
uint32_t someTargetId; // always 00 00 00 E0, 0x0E000000 is the internal def for INVALID TARGET ID
|
||||||
|
|
||||||
|
uint16_t sourceSequence; // if 0, always shows animation, otherwise hides it. counts up by 1 for each animation skipped on a caster
|
||||||
|
uint16_t rotation;
|
||||||
|
uint16_t actionAnimationId; // the animation that is played by the casting character
|
||||||
|
uint8_t variation; // variation in the animation
|
||||||
|
Common::ActionEffectDisplayType effectDisplayType;
|
||||||
|
|
||||||
|
uint8_t unknown20; // is read by handler, runs code which gets the LODWORD of animationLockTime (wtf?)
|
||||||
|
uint8_t effectCount; // ignores effects if 0, otherwise parses all of them
|
||||||
|
uint16_t padding_21[3];
|
||||||
|
uint16_t padding;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
Common::EffectEntry entries[8];
|
||||||
|
} effects[size];
|
||||||
|
|
||||||
uint16_t padding_6A[3];
|
uint16_t padding_6A[3];
|
||||||
|
|
||||||
uint32_t effectTargetId[size];
|
uint64_t effectTargetId[size];
|
||||||
Common::FFXIVARR_POSITION3 position;
|
uint16_t unkFlag[3]; // all 0x7FFF
|
||||||
uint32_t effectFlags;
|
uint16_t unk[3];
|
||||||
|
|
||||||
uint32_t padding_78;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FFXIVIpcAoeEffect8 :
|
struct FFXIVIpcAoeEffect8 :
|
||||||
|
|
|
@ -249,11 +249,11 @@ int main()
|
||||||
// action.first, data.name, data.potency, data.flankPotency, data.frontPotency, data.rearPotency,
|
// action.first, data.name, data.potency, data.flankPotency, data.frontPotency, data.rearPotency,
|
||||||
// data.curePotency, data.restorePercentage );
|
// data.curePotency, data.restorePercentage );
|
||||||
|
|
||||||
auto out = fmt::format( " // {}\n {{ {}, {{ {}, {}, {}, {}, {}, {} }} }},\n",
|
auto out = fmt::format( " // {}\n {{ {}, {{ {}, {}, {}, {}, {}, {}, {} }} }},\n",
|
||||||
data.name, action.first,
|
data.name, action.first,
|
||||||
data.potency, data.comboPotency,
|
data.potency, data.comboPotency,
|
||||||
data.flankPotency, data.frontPotency, data.rearPotency,
|
data.flankPotency, data.frontPotency, data.rearPotency,
|
||||||
data.curePotency );
|
data.curePotency, 0 );
|
||||||
|
|
||||||
output += out;
|
output += out;
|
||||||
// Logger::info( out );
|
// Logger::info( out );
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "Action.h"
|
#include "Action.h"
|
||||||
#include "ActionLut.h"
|
|
||||||
#include "EffectBuilder.h"
|
#include "EffectBuilder.h"
|
||||||
|
|
||||||
#include <Inventory/Item.h>
|
#include <Inventory/Item.h>
|
||||||
|
@ -127,6 +126,15 @@ bool Action::Action::init()
|
||||||
|
|
||||||
// todo: add missing rows for secondaryCostType/secondaryCostType and rename the current rows to primaryCostX
|
// todo: add missing rows for secondaryCostType/secondaryCostType and rename the current rows to primaryCostX
|
||||||
|
|
||||||
|
if( ActionLut::validEntryExists( static_cast< uint16_t >( getId() ) ) )
|
||||||
|
{
|
||||||
|
m_lutEntry = ActionLut::getEntry( static_cast< uint16_t >( getId() ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memset( &m_lutEntry, 0, sizeof( ActionEntry ) );
|
||||||
|
}
|
||||||
|
|
||||||
addDefaultActorFilters();
|
addDefaultActorFilters();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -162,6 +170,11 @@ bool Action::Action::isInterrupted() const
|
||||||
return m_interruptType != Common::ActionInterruptType::None;
|
return m_interruptType != Common::ActionInterruptType::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Common::ActionInterruptType Action::Action::getInterruptType() const
|
||||||
|
{
|
||||||
|
return m_interruptType;
|
||||||
|
}
|
||||||
|
|
||||||
void Action::Action::setInterrupted( Common::ActionInterruptType type )
|
void Action::Action::setInterrupted( Common::ActionInterruptType type )
|
||||||
{
|
{
|
||||||
m_interruptType = type;
|
m_interruptType = type;
|
||||||
|
@ -212,6 +225,30 @@ bool Action::Action::update()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( m_pTarget == nullptr && m_targetId != 0 )
|
||||||
|
{
|
||||||
|
// try to search for the target actor
|
||||||
|
for( auto actor : m_pSource->getInRangeActors( true ) )
|
||||||
|
{
|
||||||
|
if( actor->getId() == m_targetId )
|
||||||
|
{
|
||||||
|
m_pTarget = actor->getAsChara();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( m_pTarget != nullptr )
|
||||||
|
{
|
||||||
|
if( !m_pTarget->isAlive() )
|
||||||
|
{
|
||||||
|
// interrupt the cast if target died
|
||||||
|
setInterrupted( Common::ActionInterruptType::RegularInterrupt );
|
||||||
|
interrupt();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +373,7 @@ void Action::Action::execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( isComboAction() )
|
if( isCorrectCombo() )
|
||||||
{
|
{
|
||||||
auto player = m_pSource->getAsPlayer();
|
auto player = m_pSource->getAsPlayer();
|
||||||
|
|
||||||
|
@ -355,9 +392,17 @@ void Action::Action::execute()
|
||||||
// set currently casted action as the combo action if it interrupts a combo
|
// set currently casted action as the combo action if it interrupts a combo
|
||||||
// ignore it otherwise (ogcds, etc.)
|
// ignore it otherwise (ogcds, etc.)
|
||||||
if( !m_actionData->preservesCombo )
|
if( !m_actionData->preservesCombo )
|
||||||
|
{
|
||||||
|
// potential combo starter or correct combo from last action, must hit something to progress combo
|
||||||
|
if( !m_hitActors.empty() && ( !isComboAction() || isCorrectCombo() ) )
|
||||||
{
|
{
|
||||||
m_pSource->setLastComboActionId( getId() );
|
m_pSource->setLastComboActionId( getId() );
|
||||||
}
|
}
|
||||||
|
else // clear last combo action if the combo breaks
|
||||||
|
{
|
||||||
|
m_pSource->setLastComboActionId( 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcDamage( uint32_t potency )
|
std::pair< uint32_t, Common::ActionHitSeverityType > Action::Action::calcDamage( uint32_t potency )
|
||||||
|
@ -391,7 +436,7 @@ void Action::Action::buildEffects()
|
||||||
snapshotAffectedActors( m_hitActors );
|
snapshotAffectedActors( m_hitActors );
|
||||||
|
|
||||||
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
||||||
auto hasLutEntry = ActionLut::validEntryExists( static_cast< uint16_t >( getId() ) );
|
auto hasLutEntry = hasValidLutEntry();
|
||||||
|
|
||||||
if( !pScriptMgr->onExecute( *this ) && !hasLutEntry )
|
if( !pScriptMgr->onExecute( *this ) && !hasLutEntry )
|
||||||
{
|
{
|
||||||
|
@ -406,29 +451,68 @@ void Action::Action::buildEffects()
|
||||||
if( !hasLutEntry || m_hitActors.empty() )
|
if( !hasLutEntry || m_hitActors.empty() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto lutEntry = ActionLut::getEntry( static_cast< uint16_t >( getId() ) );
|
|
||||||
|
|
||||||
// no script exists but we have a valid lut entry
|
// no script exists but we have a valid lut entry
|
||||||
if( auto player = getSourceChara()->getAsPlayer() )
|
if( auto player = getSourceChara()->getAsPlayer() )
|
||||||
{
|
{
|
||||||
player->sendDebug( "Hit target: pot: {} (c: {}, f: {}, r: {}), heal pot: {}",
|
player->sendDebug( "Hit target: pot: {} (c: {}, f: {}, r: {}), heal pot: {}, mpp: {}",
|
||||||
lutEntry.potency, lutEntry.comboPotency, lutEntry.flankPotency, lutEntry.rearPotency,
|
m_lutEntry.potency, m_lutEntry.comboPotency, m_lutEntry.flankPotency, m_lutEntry.rearPotency,
|
||||||
lutEntry.curePotency );
|
m_lutEntry.curePotency, m_lutEntry.restoreMPPercentage );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when aoe, these effects are in the target whatever is hit first
|
||||||
|
bool shouldRestoreMP = true;
|
||||||
|
bool shouldApplyComboSucceedEffect = true;
|
||||||
|
|
||||||
for( auto& actor : m_hitActors )
|
for( auto& actor : m_hitActors )
|
||||||
{
|
{
|
||||||
// todo: this is shit
|
if( m_lutEntry.potency > 0 )
|
||||||
if( lutEntry.curePotency > 0 )
|
|
||||||
{
|
{
|
||||||
|
auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.comboPotency : m_lutEntry.potency );
|
||||||
|
m_effectBuilder->damage( actor, actor, dmg.first, dmg.second );
|
||||||
|
|
||||||
m_effectBuilder->healTarget( actor, lutEntry.curePotency );
|
if( dmg.first > 0 )
|
||||||
|
actor->onActionHostile( m_pSource );
|
||||||
|
|
||||||
|
if( isCorrectCombo() && shouldApplyComboSucceedEffect )
|
||||||
|
{
|
||||||
|
m_effectBuilder->comboSucceed( actor );
|
||||||
|
shouldApplyComboSucceedEffect = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if( lutEntry.potency > 0 )
|
if( !isComboAction() || isCorrectCombo() )
|
||||||
{
|
{
|
||||||
auto dmg = calcDamage( lutEntry.potency );
|
if( m_lutEntry.curePotency > 0 ) // actions with self heal
|
||||||
m_effectBuilder->damageTarget( actor, dmg.first, dmg.second );
|
{
|
||||||
|
m_effectBuilder->heal( actor, m_pSource, m_lutEntry.curePotency, Common::ActionHitSeverityType::NormalHeal, Common::ActionEffectResultFlag::EffectOnSource );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
|
||||||
|
{
|
||||||
|
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
||||||
|
shouldRestoreMP = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !m_actionData->preservesCombo ) // we need something like m_actionData->hasNextComboAction
|
||||||
|
{
|
||||||
|
m_effectBuilder->startCombo( actor, getId() ); // this is on all targets hit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( m_lutEntry.curePotency > 0 )
|
||||||
|
{
|
||||||
|
// todo: calcHealing()
|
||||||
|
m_effectBuilder->heal( actor, actor, m_lutEntry.curePotency );
|
||||||
|
|
||||||
|
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
|
||||||
|
{
|
||||||
|
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
||||||
|
shouldRestoreMP = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
|
||||||
|
{
|
||||||
|
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100, Common::ActionEffectResultFlag::EffectOnSource );
|
||||||
|
shouldRestoreMP = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,7 +595,7 @@ void Action::Action::setAdditionalData( uint32_t data )
|
||||||
m_additionalData = data;
|
m_additionalData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Action::Action::isComboAction() const
|
bool Action::Action::isCorrectCombo() const
|
||||||
{
|
{
|
||||||
auto lastActionId = m_pSource->getLastComboActionId();
|
auto lastActionId = m_pSource->getLastComboActionId();
|
||||||
|
|
||||||
|
@ -523,6 +607,11 @@ bool Action::Action::isComboAction() const
|
||||||
return m_actionData->actionCombo == lastActionId;
|
return m_actionData->actionCombo == lastActionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Action::Action::isComboAction() const
|
||||||
|
{
|
||||||
|
return m_actionData->actionCombo != 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool Action::Action::primaryCostCheck( bool subtractCosts )
|
bool Action::Action::primaryCostCheck( bool subtractCosts )
|
||||||
{
|
{
|
||||||
switch( m_primaryCostType )
|
switch( m_primaryCostType )
|
||||||
|
@ -657,12 +746,23 @@ void Action::Action::addDefaultActorFilters()
|
||||||
bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
|
bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
|
||||||
{
|
{
|
||||||
auto kind = actor.getObjKind();
|
auto kind = actor.getObjKind();
|
||||||
|
auto chara = actor.getAsChara();
|
||||||
|
|
||||||
// todo: are there any server side eobjs that players can hit?
|
// todo: are there any server side eobjs that players can hit?
|
||||||
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
|
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// todo: handle things such based on canTargetX
|
if( !m_canTargetSelf && chara->getId() == m_pSource->getId() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if( ( m_lutEntry.potency > 0 || m_lutEntry.curePotency > 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
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if( ( m_lutEntry.potency == 0 && m_lutEntry.curePotency > 0 ) && m_pSource->getObjKind() != chara->getObjKind() ) // !m_canTargetHostile not working for aoe
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -681,3 +781,9 @@ Sapphire::Entity::CharaPtr Action::Action::getHitChara()
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
#define _ACTION_H_
|
#define _ACTION_H_
|
||||||
|
|
||||||
#include <Common.h>
|
#include <Common.h>
|
||||||
|
#include "ActionLut.h"
|
||||||
#include "Util/ActorFilter.h"
|
#include "Util/ActorFilter.h"
|
||||||
#include "ForwardsZone.h"
|
#include "ForwardsZone.h"
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ namespace Sapphire::World::Action
|
||||||
Entity::CharaPtr getSourceChara() const;
|
Entity::CharaPtr getSourceChara() const;
|
||||||
|
|
||||||
bool isInterrupted() const;
|
bool isInterrupted() const;
|
||||||
|
Common::ActionInterruptType getInterruptType() const;
|
||||||
void setInterrupted( Common::ActionInterruptType type );
|
void setInterrupted( Common::ActionInterruptType type );
|
||||||
|
|
||||||
uint32_t getCastTime() const;
|
uint32_t getCastTime() const;
|
||||||
|
@ -45,6 +47,8 @@ namespace Sapphire::World::Action
|
||||||
uint32_t getAdditionalData() const;
|
uint32_t getAdditionalData() const;
|
||||||
void setAdditionalData( uint32_t data );
|
void setAdditionalData( uint32_t data );
|
||||||
|
|
||||||
|
bool isCorrectCombo() const;
|
||||||
|
|
||||||
bool isComboAction() const;
|
bool isComboAction() const;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -143,6 +147,8 @@ namespace Sapphire::World::Action
|
||||||
|
|
||||||
bool preFilterActor( Entity::Actor& actor ) const;
|
bool preFilterActor( Entity::Actor& actor ) const;
|
||||||
|
|
||||||
|
bool hasValidLutEntry() const;
|
||||||
|
|
||||||
uint32_t m_id;
|
uint32_t m_id;
|
||||||
|
|
||||||
uint16_t m_sequence;
|
uint16_t m_sequence;
|
||||||
|
@ -183,6 +189,8 @@ namespace Sapphire::World::Action
|
||||||
|
|
||||||
std::vector< World::Util::ActorFilterPtr > m_actorFilters;
|
std::vector< World::Util::ActorFilterPtr > m_actorFilters;
|
||||||
std::vector< Entity::CharaPtr > m_hitActors;
|
std::vector< Entity::CharaPtr > m_hitActors;
|
||||||
|
|
||||||
|
ActionEntry m_lutEntry;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace Sapphire::World::Action
|
||||||
uint16_t frontPotency;
|
uint16_t frontPotency;
|
||||||
uint16_t rearPotency;
|
uint16_t rearPotency;
|
||||||
uint16_t curePotency;
|
uint16_t curePotency;
|
||||||
// uint16_t restorePercentage;
|
uint16_t restoreMPPercentage;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ActionLut
|
class ActionLut
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -31,62 +31,216 @@ uint64_t EffectBuilder::getResultDelayMs()
|
||||||
return Common::Util::getTimeMs() + 850;
|
return Common::Util::getTimeMs() + 850;
|
||||||
}
|
}
|
||||||
|
|
||||||
EffectResultPtr EffectBuilder::getResult( Entity::CharaPtr& chara )
|
void EffectBuilder::moveToResultList( Entity::CharaPtr& chara, EffectResultPtr result )
|
||||||
{
|
{
|
||||||
auto it = m_resolvedEffects.find( chara->getId() );
|
auto it = m_resolvedEffects.find( chara->getId() );
|
||||||
if( it == m_resolvedEffects.end() )
|
if( it == m_resolvedEffects.end() )
|
||||||
{
|
{
|
||||||
// create a new one and return it
|
// create a new one
|
||||||
// todo: this feels kinda dirty but makes for easy work
|
auto resultList = std::make_shared< std::vector< EffectResultPtr > >();
|
||||||
auto result = make_EffectResult( chara, getResultDelayMs() );
|
|
||||||
|
|
||||||
m_resolvedEffects[ chara->getId() ] = result;
|
m_resolvedEffects[ chara->getId() ] = resultList;
|
||||||
|
|
||||||
return result;
|
resultList->push_back( std::move( result ) );
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return it->second;
|
it->second->push_back( std::move( result ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBuilder::healTarget( Entity::CharaPtr& target, uint32_t amount, Common::ActionHitSeverityType severity )
|
void EffectBuilder::heal( Entity::CharaPtr& effectTarget, Entity::CharaPtr& healingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
|
||||||
{
|
{
|
||||||
auto result = getResult( target );
|
EffectResultPtr nextResult = make_EffectResult( healingTarget, getResultDelayMs() );
|
||||||
assert( result );
|
nextResult->heal( amount, severity, flag );
|
||||||
|
moveToResultList( effectTarget, nextResult );
|
||||||
result->heal( amount, severity );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBuilder::damageTarget( Entity::CharaPtr& target, uint32_t amount, Common::ActionHitSeverityType severity )
|
void EffectBuilder::restoreMP( Entity::CharaPtr& target, Entity::CharaPtr& restoringTarget, uint32_t amount, Common::ActionEffectResultFlag flag )
|
||||||
{
|
{
|
||||||
auto result = getResult( target );
|
EffectResultPtr nextResult = make_EffectResult( restoringTarget, getResultDelayMs() ); // restore mp source actor
|
||||||
assert( result );
|
nextResult->restoreMP( amount, flag );
|
||||||
|
moveToResultList( target, nextResult );
|
||||||
|
}
|
||||||
|
|
||||||
result->damage( amount, severity );
|
void EffectBuilder::damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
|
||||||
|
{
|
||||||
|
EffectResultPtr nextResult = make_EffectResult( damagingTarget, getResultDelayMs() );
|
||||||
|
nextResult->damage( amount, severity, flag );
|
||||||
|
moveToResultList( effectTarget, nextResult );
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectBuilder::startCombo( Entity::CharaPtr& target, uint16_t actionId )
|
||||||
|
{
|
||||||
|
EffectResultPtr nextResult = make_EffectResult( target, 0 );
|
||||||
|
nextResult->startCombo( actionId );
|
||||||
|
moveToResultList( target, nextResult );
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectBuilder::comboSucceed( Entity::CharaPtr& target )
|
||||||
|
{
|
||||||
|
EffectResultPtr nextResult = make_EffectResult( target, 0 );
|
||||||
|
nextResult->comboSucceed();
|
||||||
|
moveToResultList( target, nextResult );
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectBuilder::buildAndSendPackets()
|
void EffectBuilder::buildAndSendPackets()
|
||||||
{
|
{
|
||||||
|
auto targetCount = m_resolvedEffects.size();
|
||||||
Logger::debug( "EffectBuilder result: " );
|
Logger::debug( "EffectBuilder result: " );
|
||||||
Logger::debug( "Targets afflicted: {}", m_resolvedEffects.size() );
|
Logger::debug( "Targets afflicted: {}", targetCount );
|
||||||
|
|
||||||
|
auto globalSequence = m_sourceChara->getCurrentTerritory()->getNextEffectSequence();
|
||||||
|
|
||||||
|
do // we want to send at least one packet even nothing is hit so other players can see
|
||||||
|
{
|
||||||
|
auto packet = buildNextEffectPacket( globalSequence );
|
||||||
|
m_sourceChara->sendToInRangeSet( packet, true );
|
||||||
|
}
|
||||||
|
while( !m_resolvedEffects.empty() );
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr< FFXIVPacketBase > EffectBuilder::buildNextEffectPacket( uint32_t globalSequence )
|
||||||
|
{
|
||||||
|
auto remainingTargetCount = m_resolvedEffects.size();
|
||||||
|
|
||||||
|
if( remainingTargetCount > 1 ) // use AoeEffect packets
|
||||||
|
{
|
||||||
|
int packetSize = remainingTargetCount <= 8 ? 8 : ( remainingTargetCount <= 16 ? 16 : ( remainingTargetCount <= 24 ? 24 : 32 ) );
|
||||||
|
|
||||||
|
using EffectHeader = Server::FFXIVIpcAoeEffect< 8 >; // dummy type to access header part of the packet
|
||||||
|
|
||||||
|
FFXIVPacketBasePtr effectPacket = nullptr;
|
||||||
|
EffectHeader* pHeader;
|
||||||
|
Common::EffectEntry* pEntry;
|
||||||
|
uint64_t* pEffectTargetId;
|
||||||
|
uint16_t* pFlag;
|
||||||
|
switch( packetSize )
|
||||||
|
{
|
||||||
|
case 8:
|
||||||
|
{
|
||||||
|
auto p = makeZonePacket< Server::FFXIVIpcAoeEffect8 >( m_sourceChara->getId() );
|
||||||
|
pHeader = reinterpret_cast< EffectHeader* >( &p->data() );
|
||||||
|
pEntry = reinterpret_cast< Common::EffectEntry* >( &p->data().effects );
|
||||||
|
pEffectTargetId = reinterpret_cast< uint64_t* >( &p->data().effectTargetId );
|
||||||
|
pFlag = reinterpret_cast< uint16_t* >( &p->data().unkFlag );
|
||||||
|
effectPacket = std::move( p );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 16:
|
||||||
|
{
|
||||||
|
auto p = makeZonePacket< Server::FFXIVIpcAoeEffect16 >( m_sourceChara->getId() );
|
||||||
|
pHeader = reinterpret_cast< EffectHeader* >( &p->data() );
|
||||||
|
pEntry = reinterpret_cast< Common::EffectEntry* >( &p->data().effects );
|
||||||
|
pEffectTargetId = reinterpret_cast< uint64_t* >( &p->data().effectTargetId );
|
||||||
|
pFlag = reinterpret_cast< uint16_t* >( &p->data().unkFlag );
|
||||||
|
effectPacket = std::move( p );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 24:
|
||||||
|
{
|
||||||
|
auto p = makeZonePacket< Server::FFXIVIpcAoeEffect24 >( m_sourceChara->getId() );
|
||||||
|
pHeader = reinterpret_cast< EffectHeader* >( &p->data() );
|
||||||
|
pEntry = reinterpret_cast< Common::EffectEntry* >( &p->data().effects );
|
||||||
|
pEffectTargetId = reinterpret_cast< uint64_t* >( &p->data().effectTargetId );
|
||||||
|
pFlag = reinterpret_cast< uint16_t* >( &p->data().unkFlag );
|
||||||
|
effectPacket = std::move( p );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 32:
|
||||||
|
{
|
||||||
|
auto p = makeZonePacket< Server::FFXIVIpcAoeEffect32 >( m_sourceChara->getId() );
|
||||||
|
pHeader = reinterpret_cast< EffectHeader* >( &p->data() );
|
||||||
|
pEntry = reinterpret_cast< Common::EffectEntry* >( &p->data().effects );
|
||||||
|
pEffectTargetId = reinterpret_cast< uint64_t* >( &p->data().effectTargetId );
|
||||||
|
pFlag = reinterpret_cast< uint16_t* >( &p->data().unkFlag );
|
||||||
|
effectPacket = std::move( p );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert( effectPacket );
|
||||||
|
|
||||||
|
pHeader->actionId = m_actionId;
|
||||||
|
pHeader->actionAnimationId = static_cast< uint16_t >( m_actionId );
|
||||||
|
pHeader->animationTargetId = m_sourceChara->getId();
|
||||||
|
pHeader->someTargetId = 0xE0000000;
|
||||||
|
pHeader->rotation = Common::Util::floatToUInt16Rot( m_sourceChara->getRot() );
|
||||||
|
pHeader->effectDisplayType = Common::ActionEffectDisplayType::ShowActionName;
|
||||||
|
pHeader->effectCount = static_cast< uint8_t >( remainingTargetCount > packetSize ? packetSize : remainingTargetCount );
|
||||||
|
pHeader->sourceSequence = m_sequence;
|
||||||
|
pHeader->globalSequence = globalSequence;
|
||||||
|
|
||||||
|
uint8_t targetIndex = 0;
|
||||||
for( auto it = m_resolvedEffects.begin(); it != m_resolvedEffects.end(); )
|
for( auto it = m_resolvedEffects.begin(); it != m_resolvedEffects.end(); )
|
||||||
{
|
{
|
||||||
auto result = it->second;
|
auto resultList = it->second;
|
||||||
Logger::debug( " - id: {}", result->getTarget()->getId() );
|
assert( !resultList->empty() );
|
||||||
|
auto firstResult = resultList->data()[ 0 ];
|
||||||
|
pEffectTargetId[ targetIndex ] = firstResult->getTarget()->getId();
|
||||||
|
Logger::debug( " - id: {}", pEffectTargetId[ targetIndex ] );
|
||||||
|
|
||||||
|
for( auto i = 0; i < resultList->size(); i++ )
|
||||||
|
{
|
||||||
|
auto result = resultList->data()[ i ];
|
||||||
|
pEntry[ targetIndex * 8 + i ] = result->buildEffectEntry();
|
||||||
|
m_sourceChara->getCurrentTerritory()->addEffectResult( std::move( result ) );
|
||||||
|
}
|
||||||
|
resultList->clear();
|
||||||
|
|
||||||
|
it = m_resolvedEffects.erase( it );
|
||||||
|
|
||||||
|
targetIndex++;
|
||||||
|
|
||||||
|
if( targetIndex == packetSize )
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pFlag[0] = 0x7FFF;
|
||||||
|
pFlag[1] = 0x7FFF;
|
||||||
|
pFlag[2] = 0x7FFF;
|
||||||
|
|
||||||
|
return effectPacket;
|
||||||
|
}
|
||||||
|
else if ( remainingTargetCount == 1 ) // use Effect for single target
|
||||||
|
{
|
||||||
|
auto resultList = m_resolvedEffects.begin()->second;
|
||||||
|
assert( !resultList->empty() );
|
||||||
|
auto firstResult = resultList->data()[ 0 ];
|
||||||
|
Logger::debug( " - id: {}", firstResult->getTarget()->getId() );
|
||||||
|
|
||||||
auto seq = m_sourceChara->getCurrentTerritory()->getNextEffectSequence();
|
auto seq = m_sourceChara->getCurrentTerritory()->getNextEffectSequence();
|
||||||
|
|
||||||
auto effectPacket = std::make_shared< Server::EffectPacket >( m_sourceChara->getId(), result->getTarget()->getId(), m_actionId );
|
auto effectPacket = std::make_shared< Server::EffectPacket >( m_sourceChara->getId(), firstResult->getTarget()->getId(), m_actionId );
|
||||||
effectPacket->setRotation( Common::Util::floatToUInt16Rot( m_sourceChara->getRot() ) );
|
effectPacket->setRotation( Common::Util::floatToUInt16Rot( m_sourceChara->getRot() ) );
|
||||||
effectPacket->setSequence( seq, m_sequence );
|
effectPacket->setSequence( seq, m_sequence );
|
||||||
|
|
||||||
|
for( int i = 0; i < resultList->size(); i++ )
|
||||||
|
{
|
||||||
|
auto result = resultList->data()[ i ];
|
||||||
effectPacket->addEffect( result->buildEffectEntry() );
|
effectPacket->addEffect( result->buildEffectEntry() );
|
||||||
|
|
||||||
m_sourceChara->sendToInRangeSet( effectPacket, true );
|
|
||||||
|
|
||||||
// add effect to territory
|
|
||||||
m_sourceChara->getCurrentTerritory()->addEffectResult( std::move( result ) );
|
m_sourceChara->getCurrentTerritory()->addEffectResult( std::move( result ) );
|
||||||
|
}
|
||||||
|
|
||||||
it = m_resolvedEffects.erase( it );
|
resultList->clear();
|
||||||
|
|
||||||
|
m_resolvedEffects.clear();
|
||||||
|
|
||||||
|
return effectPacket;
|
||||||
|
}
|
||||||
|
else // nothing is hit, this only happens when using aoe and AoeEffect8 is used on retail
|
||||||
|
{
|
||||||
|
auto effectPacket = makeZonePacket< Server::FFXIVIpcAoeEffect8 >( m_sourceChara->getId() );
|
||||||
|
|
||||||
|
effectPacket->data().actionId = m_actionId;
|
||||||
|
effectPacket->data().actionAnimationId = static_cast< uint16_t >( m_actionId );
|
||||||
|
effectPacket->data().animationTargetId = m_sourceChara->getId();
|
||||||
|
effectPacket->data().someTargetId = 0xE0000000;
|
||||||
|
effectPacket->data().rotation = Common::Util::floatToUInt16Rot( m_sourceChara->getRot() );
|
||||||
|
effectPacket->data().effectDisplayType = Common::ActionEffectDisplayType::HideActionName;
|
||||||
|
effectPacket->data().effectCount = 0;
|
||||||
|
effectPacket->data().sourceSequence = m_sequence;
|
||||||
|
effectPacket->data().globalSequence = globalSequence;
|
||||||
|
|
||||||
|
return effectPacket;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,27 +11,37 @@ namespace Sapphire::World::Action
|
||||||
public:
|
public:
|
||||||
EffectBuilder( Entity::CharaPtr source, uint32_t actionId, uint16_t sequence );
|
EffectBuilder( Entity::CharaPtr source, uint32_t actionId, uint16_t sequence );
|
||||||
|
|
||||||
|
void heal( Entity::CharaPtr& effectTarget, Entity::CharaPtr& healingTarget, uint32_t amount,
|
||||||
|
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalHeal,
|
||||||
|
Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None);
|
||||||
|
|
||||||
void healTarget( Entity::CharaPtr& target, uint32_t amount,
|
void restoreMP( Entity::CharaPtr& effectTarget, Entity::CharaPtr& restoringTarget, uint32_t amount,
|
||||||
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalHeal );
|
Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None);
|
||||||
|
|
||||||
void damageTarget( Entity::CharaPtr& target, uint32_t amount,
|
void damage( Entity::CharaPtr& effectTarget, Entity::CharaPtr& damagingTarget, uint32_t amount,
|
||||||
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalDamage );
|
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalDamage,
|
||||||
|
Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None);
|
||||||
|
|
||||||
|
void startCombo( Entity::CharaPtr& target, uint16_t actionId );
|
||||||
|
|
||||||
|
void comboSucceed( Entity::CharaPtr& target );
|
||||||
|
|
||||||
void buildAndSendPackets();
|
void buildAndSendPackets();
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EffectResultPtr getResult( Entity::CharaPtr& chara );
|
void moveToResultList( Entity::CharaPtr& chara, EffectResultPtr result );
|
||||||
|
|
||||||
uint64_t getResultDelayMs();
|
uint64_t getResultDelayMs();
|
||||||
|
|
||||||
|
std::shared_ptr< Sapphire::Network::Packets::FFXIVPacketBase > buildNextEffectPacket( uint32_t globalSequence );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_actionId;
|
uint32_t m_actionId;
|
||||||
uint16_t m_sequence;
|
uint16_t m_sequence;
|
||||||
|
|
||||||
Entity::CharaPtr m_sourceChara;
|
Entity::CharaPtr m_sourceChara;
|
||||||
std::unordered_map< uint32_t, EffectResultPtr > m_resolvedEffects;
|
std::unordered_map< uint32_t, std::shared_ptr< std::vector< EffectResultPtr > > > m_resolvedEffects;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ EffectResult::EffectResult( Entity::CharaPtr target, uint64_t runAfter ) :
|
||||||
m_value( 0 ),
|
m_value( 0 ),
|
||||||
m_severity( Common::ActionHitSeverityType::NormalDamage ),
|
m_severity( Common::ActionHitSeverityType::NormalDamage ),
|
||||||
m_type( Common::ActionEffectType::Nothing ),
|
m_type( Common::ActionEffectType::Nothing ),
|
||||||
m_param( 0 )
|
m_param( 0 ),
|
||||||
|
m_flag( Common::ActionEffectResultFlag::None )
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,22 +40,46 @@ void EffectResult::setParam( uint8_t param )
|
||||||
m_param = param;
|
m_param = param;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectResult::damage( uint32_t amount, Common::ActionHitSeverityType severity )
|
void EffectResult::damage( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
|
||||||
{
|
{
|
||||||
m_severity = severity;
|
m_severity = severity;
|
||||||
m_value = amount;
|
m_value = amount;
|
||||||
|
m_flag = flag;
|
||||||
|
|
||||||
m_type = Common::ActionEffectType::Damage;
|
m_type = Common::ActionEffectType::Damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EffectResult::heal( uint32_t amount, Sapphire::Common::ActionHitSeverityType severity )
|
void EffectResult::heal( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag )
|
||||||
{
|
{
|
||||||
m_severity = severity;
|
m_severity = severity;
|
||||||
m_value = amount;
|
m_value = amount;
|
||||||
|
m_flag = flag;
|
||||||
|
|
||||||
m_type = Common::ActionEffectType::Heal;
|
m_type = Common::ActionEffectType::Heal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EffectResult::restoreMP( uint32_t amount, Common::ActionEffectResultFlag flag )
|
||||||
|
{
|
||||||
|
m_value = amount;
|
||||||
|
m_flag = flag;
|
||||||
|
|
||||||
|
m_type = Common::ActionEffectType::MpGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectResult::startCombo( uint16_t actionId )
|
||||||
|
{
|
||||||
|
m_value = actionId;
|
||||||
|
m_flag = Common::ActionEffectResultFlag::EffectOnSource;
|
||||||
|
|
||||||
|
m_type = Common::ActionEffectType::StartActionCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EffectResult::comboSucceed()
|
||||||
|
{
|
||||||
|
// no EffectOnSource flag on this
|
||||||
|
m_type = Common::ActionEffectType::ComboSucceed;
|
||||||
|
}
|
||||||
|
|
||||||
Common::EffectEntry EffectResult::buildEffectEntry() const
|
Common::EffectEntry EffectResult::buildEffectEntry() const
|
||||||
{
|
{
|
||||||
Common::EffectEntry entry{};
|
Common::EffectEntry entry{};
|
||||||
|
@ -64,6 +89,7 @@ Common::EffectEntry EffectResult::buildEffectEntry() const
|
||||||
entry.hitSeverity = m_severity;
|
entry.hitSeverity = m_severity;
|
||||||
entry.effectType = m_type;
|
entry.effectType = m_type;
|
||||||
entry.param = m_param;
|
entry.param = m_param;
|
||||||
|
entry.flags = static_cast< uint8_t >( m_flag );
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -84,6 +110,12 @@ void EffectResult::execute()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Common::ActionEffectType::MpGain:
|
||||||
|
{
|
||||||
|
m_target->restoreMP( m_value );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,11 @@ namespace Sapphire::World::Action
|
||||||
public:
|
public:
|
||||||
explicit EffectResult( Entity::CharaPtr target, uint64_t delayMs );
|
explicit EffectResult( Entity::CharaPtr target, uint64_t delayMs );
|
||||||
|
|
||||||
void damage( uint32_t amount, Common::ActionHitSeverityType severity );
|
void damage( uint32_t amount, Common::ActionHitSeverityType severity, Common::ActionEffectResultFlag flag = Common::ActionEffectResultFlag::None );
|
||||||
void heal( uint32_t amount, Common::ActionHitSeverityType severity );
|
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();
|
||||||
|
|
||||||
Entity::CharaPtr getTarget() const;
|
Entity::CharaPtr getTarget() const;
|
||||||
|
|
||||||
|
@ -40,6 +43,7 @@ namespace Sapphire::World::Action
|
||||||
|
|
||||||
uint32_t m_value;
|
uint32_t m_value;
|
||||||
uint8_t m_param;
|
uint8_t m_param;
|
||||||
|
Common::ActionEffectResultFlag m_flag;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -438,6 +438,18 @@ void Sapphire::Entity::Chara::heal( uint32_t amount )
|
||||||
sendStatusUpdate();
|
sendStatusUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::Chara::restoreMP( uint32_t amount )
|
||||||
|
{
|
||||||
|
if( ( m_mp + amount ) > getMaxMp() )
|
||||||
|
{
|
||||||
|
m_mp = getMaxMp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_mp += amount;
|
||||||
|
|
||||||
|
sendStatusUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Send an HpMpTp update to players in range ( and potentially to self )
|
Send an HpMpTp update to players in range ( and potentially to self )
|
||||||
TODO: poor naming, should be changed. Status is not HP. Also should be virtual
|
TODO: poor naming, should be changed. Status is not HP. Also should be virtual
|
||||||
|
|
|
@ -263,6 +263,8 @@ namespace Sapphire::Entity
|
||||||
|
|
||||||
virtual void heal( uint32_t amount );
|
virtual void heal( uint32_t amount );
|
||||||
|
|
||||||
|
virtual void restoreMP( uint32_t amount );
|
||||||
|
|
||||||
virtual bool checkAction();
|
virtual bool checkAction();
|
||||||
|
|
||||||
virtual void update( uint64_t tickCount );
|
virtual void update( uint64_t tickCount );
|
||||||
|
|
|
@ -80,7 +80,8 @@ Sapphire::Entity::Player::Player( FrameworkPtr pFw ) :
|
||||||
m_emoteMode( 0 ),
|
m_emoteMode( 0 ),
|
||||||
m_directorInitialized( false ),
|
m_directorInitialized( false ),
|
||||||
m_onEnterEventDone( false ),
|
m_onEnterEventDone( false ),
|
||||||
m_falling( false )
|
m_falling( false ),
|
||||||
|
m_pQueuedAction( nullptr )
|
||||||
{
|
{
|
||||||
m_id = 0;
|
m_id = 0;
|
||||||
m_currentStance = Stance::Passive;
|
m_currentStance = Stance::Passive;
|
||||||
|
@ -2143,3 +2144,42 @@ Sapphire::Common::ActiveLand Sapphire::Entity::Player::getActiveLand() const
|
||||||
{
|
{
|
||||||
return m_activeLand;
|
return m_activeLand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Sapphire::Entity::Player::hasQueuedAction() const
|
||||||
|
{
|
||||||
|
return m_pQueuedAction != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sapphire::Entity::Player::setQueuedAction( Sapphire::World::Action::ActionPtr pAction )
|
||||||
|
{
|
||||||
|
m_pQueuedAction = std::move( pAction ); // overwrite previous queued action if any
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Sapphire::Entity::Player::checkAction()
|
||||||
|
{
|
||||||
|
if( m_pCurrentAction == nullptr )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if( m_pCurrentAction->update() )
|
||||||
|
{
|
||||||
|
if( m_pCurrentAction->isInterrupted() && m_pCurrentAction->getInterruptType() != Common::ActionInterruptType::DamageInterrupt )
|
||||||
|
{
|
||||||
|
// we moved (or whatever not damage interrupt) so we don't want to execute queued cast
|
||||||
|
m_pQueuedAction = nullptr;
|
||||||
|
}
|
||||||
|
m_pCurrentAction = nullptr;
|
||||||
|
|
||||||
|
if( hasQueuedAction() )
|
||||||
|
{
|
||||||
|
sendDebug( "Queued skill start: {0}", m_pQueuedAction->getId() );
|
||||||
|
if( m_pQueuedAction->hasCastTime() )
|
||||||
|
{
|
||||||
|
setCurrentAction( m_pQueuedAction );
|
||||||
|
}
|
||||||
|
m_pQueuedAction->start();
|
||||||
|
m_pQueuedAction = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -643,6 +643,11 @@ namespace Sapphire::Entity
|
||||||
/*! return a const pointer to the mount guide bitmask array */
|
/*! return a const pointer to the mount guide bitmask array */
|
||||||
const uint8_t* getMountGuideBitmask() const;
|
const uint8_t* getMountGuideBitmask() const;
|
||||||
|
|
||||||
|
bool checkAction() override;
|
||||||
|
|
||||||
|
bool hasQueuedAction() const;
|
||||||
|
|
||||||
|
void setQueuedAction( World::Action::ActionPtr pAction );
|
||||||
|
|
||||||
// Spawn handling
|
// Spawn handling
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1002,6 +1007,8 @@ namespace Sapphire::Entity
|
||||||
|
|
||||||
uint32_t m_inventorySequence;
|
uint32_t m_inventorySequence;
|
||||||
|
|
||||||
|
World::Action::ActionPtr m_pQueuedAction;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using InventoryMap = std::map< uint16_t, Sapphire::ItemContainerPtr >;
|
using InventoryMap = std::map< uint16_t, Sapphire::ItemContainerPtr >;
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ void World::Manager::ActionMgr::handlePlacedPlayerAction( Entity::Player& player
|
||||||
|
|
||||||
auto action = Action::make_Action( player.getAsPlayer(), actionId, sequence, actionData, framework() );
|
auto action = Action::make_Action( player.getAsPlayer(), actionId, sequence, actionData, framework() );
|
||||||
|
|
||||||
|
action->setPos( pos );
|
||||||
|
|
||||||
if( !action->init() )
|
if( !action->init() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -37,8 +39,6 @@ void World::Manager::ActionMgr::handlePlacedPlayerAction( Entity::Player& player
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
action->setPos( pos );
|
|
||||||
|
|
||||||
bootstrapAction( player, action, *actionData );
|
bootstrapAction( player, action, *actionData );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@ void World::Manager::ActionMgr::handleTargetedPlayerAction( Entity::Player& play
|
||||||
|
|
||||||
action->setTargetId( targetId );
|
action->setTargetId( targetId );
|
||||||
|
|
||||||
|
action->setPos( player.getPos() );
|
||||||
|
|
||||||
if( !action->init() )
|
if( !action->init() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -87,6 +89,13 @@ void World::Manager::ActionMgr::bootstrapAction( Entity::Player& player,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( player.getCurrentAction() )
|
||||||
|
{
|
||||||
|
player.sendDebug( "Skill queued: {0}", currentAction->getId() );
|
||||||
|
player.setQueuedAction( currentAction );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// if we have a cast time we want to associate the action with the player so update is called
|
// if we have a cast time we want to associate the action with the player so update is called
|
||||||
if( currentAction->hasCastTime() )
|
if( currentAction->hasCastTime() )
|
||||||
{
|
{
|
||||||
|
@ -95,4 +104,5 @@ 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->start();
|
currentAction->start();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -25,13 +25,14 @@ namespace Sapphire::Network::Packets::Server
|
||||||
m_data.effectTargetId = targetId;
|
m_data.effectTargetId = targetId;
|
||||||
|
|
||||||
m_data.effectDisplayType = Common::ActionEffectDisplayType::ShowActionName;
|
m_data.effectDisplayType = Common::ActionEffectDisplayType::ShowActionName;
|
||||||
|
|
||||||
|
std::memset( m_data.effects, 0, sizeof( Common::EffectEntry ) * 8 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void addEffect( const Common::EffectEntry& effect )
|
void addEffect( const Common::EffectEntry& effect )
|
||||||
{
|
{
|
||||||
assert( m_data.effectCount <= 8 );
|
assert( m_data.effectCount <= 8 );
|
||||||
|
|
||||||
std::memset( m_data.effects, 0, sizeof( Common::EffectEntry ) * 8 );
|
|
||||||
std::memcpy( &m_data.effects[ m_data.effectCount * 8 ], &effect, sizeof( Common::EffectEntry ) );
|
std::memcpy( &m_data.effects[ m_data.effectCount * 8 ], &effect, sizeof( Common::EffectEntry ) );
|
||||||
m_data.effectCount++;
|
m_data.effectCount++;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue