1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-25 14:07:46 +00:00

Action system updates.

This commit is contained in:
collett 2020-01-05 17:09:27 +09:00
parent 85625244c8
commit e8dcef63ba
18 changed files with 1896 additions and 1385 deletions

View file

@ -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
*/
StartActionCombo = 28,
ComboVisualEffect = 29,
Knockback = 33,
Mount = 38,
VFX = 59, // links to VFX sheet

View file

@ -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
PlayerSetup = 0x0295, // updated 5.18
PlayerStats = 0x017A, // updated 5.18
ActorOwner = 0x0322, // updated 5.11
ActorOwner = 0x03BB, // updated 5.18
PlayerStateFlags = 0x02C6, // updated 5.18
PlayerClassInfo = 0x01B0, // updated 5.18
@ -341,8 +341,8 @@ namespace Sapphire::Network::Packets
PlaceFieldMarker = 0x013C, // updated 5.0
SkillHandler = 0x01BE, // updated 5.18
GMCommand1 = 0x00A4, // updated 5.1
GMCommand2 = 0x013F, // updated 5.0
GMCommand1 = 0x014D, // updated 5.18
GMCommand2 = 0x032C, // updated 5.18
AoESkillHandler = 0x140, // updated 5.0
UpdatePositionHandler = 0x0318, // updated 5.18

View file

@ -537,33 +537,6 @@ namespace Sapphire::Network::Packets::Server
/* 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 >
{
uint64_t animationTargetId; // who the animation targets
@ -608,17 +581,35 @@ namespace Sapphire::Network::Packets::Server
template< int size >
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];
uint32_t effectTargetId[size];
Common::FFXIVARR_POSITION3 position;
uint32_t effectFlags;
uint32_t padding_78;
uint64_t effectTargetId[size];
uint16_t unkFlag[3]; // all 0x7FFF
uint16_t unk[3];
};
struct FFXIVIpcAoeEffect8 :

View file

@ -249,11 +249,11 @@ int main()
// action.first, data.name, data.potency, data.flankPotency, data.frontPotency, data.rearPotency,
// data.curePotency, data.restorePercentage );
auto out = fmt::format( " // {}\n {{ {}, {{ {}, {}, {}, {}, {}, {} }} }},\n",
auto out = fmt::format( " // {}\n {{ {}, {{ {}, {}, {}, {}, {}, {}, {} }} }},\n",
data.name, action.first,
data.potency, data.comboPotency,
data.flankPotency, data.frontPotency, data.rearPotency,
data.curePotency );
data.curePotency, 0 );
output += out;
// Logger::info( out );

View file

@ -1,5 +1,4 @@
#include "Action.h"
#include "ActionLut.h"
#include "EffectBuilder.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
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();
return true;
@ -162,6 +170,11 @@ bool Action::Action::isInterrupted() const
return m_interruptType != Common::ActionInterruptType::None;
}
Common::ActionInterruptType Action::Action::getInterruptType() const
{
return m_interruptType;
}
void Action::Action::setInterrupted( Common::ActionInterruptType type )
{
m_interruptType = type;
@ -212,6 +225,30 @@ bool Action::Action::update()
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;
}
@ -336,7 +373,7 @@ void Action::Action::execute()
}
}
if( isComboAction() )
if( isCorrectCombo() )
{
auto player = m_pSource->getAsPlayer();
@ -356,7 +393,15 @@ void Action::Action::execute()
// ignore it otherwise (ogcds, etc.)
if( !m_actionData->preservesCombo )
{
m_pSource->setLastComboActionId( getId() );
// potential combo starter or correct combo from last action
if( ( !isComboAction() || isCorrectCombo() ) )
{
m_pSource->setLastComboActionId( getId() );
}
else // clear last combo action if the combo breaks
{
m_pSource->setLastComboActionId( 0 );
}
}
}
@ -391,7 +436,7 @@ void Action::Action::buildEffects()
snapshotAffectedActors( m_hitActors );
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
auto hasLutEntry = ActionLut::validEntryExists( static_cast< uint16_t >( getId() ) );
auto hasLutEntry = hasValidLutEntry();
if( !pScriptMgr->onExecute( *this ) && !hasLutEntry )
{
@ -406,29 +451,69 @@ void Action::Action::buildEffects()
if( !hasLutEntry || m_hitActors.empty() )
return;
auto lutEntry = ActionLut::getEntry( static_cast< uint16_t >( getId() ) );
// no script exists but we have a valid lut entry
if( auto player = getSourceChara()->getAsPlayer() )
{
player->sendDebug( "Hit target: pot: {} (c: {}, f: {}, r: {}), heal pot: {}",
lutEntry.potency, lutEntry.comboPotency, lutEntry.flankPotency, lutEntry.rearPotency,
lutEntry.curePotency );
player->sendDebug( "Hit target: pot: {} (c: {}, f: {}, r: {}), heal pot: {}, mpp: {}",
m_lutEntry.potency, m_lutEntry.comboPotency, m_lutEntry.flankPotency, m_lutEntry.rearPotency,
m_lutEntry.curePotency, m_lutEntry.restoreMPPercentage );
}
// when aoe, these effects are in the target whatever is hit first
bool shouldRestoreMP = true;
bool shouldShowComboEffect = true;
for( auto& actor : m_hitActors )
{
// todo: this is shit
if( lutEntry.curePotency > 0 )
if( m_lutEntry.potency > 0 )
{
m_effectBuilder->healTarget( actor, lutEntry.curePotency );
}
else if( lutEntry.potency > 0 )
{
auto dmg = calcDamage( lutEntry.potency );
auto dmg = calcDamage( isCorrectCombo() ? m_lutEntry.comboPotency : m_lutEntry.potency );
m_effectBuilder->damageTarget( actor, dmg.first, dmg.second );
if( dmg.first > 0 )
actor->onActionHostile( m_pSource );
if( isCorrectCombo() && shouldShowComboEffect )
{
m_effectBuilder->comboVisualEffect( actor );
shouldShowComboEffect = false;
}
if( !isComboAction() || isCorrectCombo() )
{
if( m_lutEntry.curePotency > 0 ) // actions with self heal
{
m_effectBuilder->selfHeal( actor, m_pSource, m_lutEntry.curePotency );
}
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
{
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100 );
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->healTarget( actor, m_lutEntry.curePotency );
if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
{
// always restore caster mp I don't think there are any actions that can restore target MP post 5.0
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100 );
shouldRestoreMP = false;
}
}
else if( m_lutEntry.restoreMPPercentage > 0 && shouldRestoreMP )
{
m_effectBuilder->restoreMP( actor, m_pSource, m_pSource->getMaxMp() * m_lutEntry.restoreMPPercentage / 100 );
shouldRestoreMP = false;
}
}
@ -511,7 +596,7 @@ void Action::Action::setAdditionalData( uint32_t data )
m_additionalData = data;
}
bool Action::Action::isComboAction() const
bool Action::Action::isCorrectCombo() const
{
auto lastActionId = m_pSource->getLastComboActionId();
@ -523,6 +608,11 @@ bool Action::Action::isComboAction() const
return m_actionData->actionCombo == lastActionId;
}
bool Action::Action::isComboAction() const
{
return m_actionData->actionCombo != 0;
}
bool Action::Action::primaryCostCheck( bool subtractCosts )
{
switch( m_primaryCostType )
@ -657,11 +747,24 @@ void Action::Action::addDefaultActorFilters()
bool Action::Action::preFilterActor( Sapphire::Entity::Actor& actor ) const
{
auto kind = actor.getObjKind();
auto chara = actor.getAsChara();
// todo: are there any server side eobjs that players can hit?
if( kind != ObjKind::BattleNpc && kind != ObjKind::Player )
return false;
if( m_lutEntry.potency > 0 && chara == m_pSource )
{
// damage action shouldn't hit self
return false;
}
if( ( m_lutEntry.potency > 0 || m_lutEntry.curePotency > 0 ) && !chara->isAlive() )
{
// can't deal damage or heal a dead entity
return false;
}
// todo: handle things such based on canTargetX
return true;
@ -680,4 +783,10 @@ Sapphire::Entity::CharaPtr Action::Action::getHitChara()
}
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;
}

View file

@ -2,6 +2,7 @@
#define _ACTION_H_
#include <Common.h>
#include "ActionLut.h"
#include "Util/ActorFilter.h"
#include "ForwardsZone.h"
@ -37,6 +38,7 @@ namespace Sapphire::World::Action
Entity::CharaPtr getSourceChara() const;
bool isInterrupted() const;
Common::ActionInterruptType getInterruptType() const;
void setInterrupted( Common::ActionInterruptType type );
uint32_t getCastTime() const;
@ -45,6 +47,8 @@ namespace Sapphire::World::Action
uint32_t getAdditionalData() const;
void setAdditionalData( uint32_t data );
bool isCorrectCombo() const;
bool isComboAction() const;
/*!
@ -143,6 +147,8 @@ namespace Sapphire::World::Action
bool preFilterActor( Entity::Actor& actor ) const;
bool hasValidLutEntry() const;
uint32_t m_id;
uint16_t m_sequence;
@ -183,6 +189,8 @@ namespace Sapphire::World::Action
std::vector< World::Util::ActorFilterPtr > m_actorFilters;
std::vector< Entity::CharaPtr > m_hitActors;
ActionEntry m_lutEntry;
};
}

View file

@ -13,7 +13,7 @@ namespace Sapphire::World::Action
uint16_t frontPotency;
uint16_t rearPotency;
uint16_t curePotency;
// uint16_t restorePercentage;
uint16_t restoreMPPercentage;
};
class ActionLut

File diff suppressed because it is too large Load diff

View file

@ -31,18 +31,17 @@ uint64_t EffectBuilder::getResultDelayMs()
return Common::Util::getTimeMs() + 850;
}
EffectResultPtr EffectBuilder::getResult( Entity::CharaPtr& chara )
std::shared_ptr< std::vector< EffectResultPtr > > EffectBuilder::getResultList( Entity::CharaPtr& chara )
{
auto it = m_resolvedEffects.find( chara->getId() );
if( it == m_resolvedEffects.end() )
{
// create a new one and return it
// todo: this feels kinda dirty but makes for easy work
auto result = make_EffectResult( chara, getResultDelayMs() );
auto resultList = std::make_shared< std::vector< EffectResultPtr > >();
m_resolvedEffects[ chara->getId() ] = result;
m_resolvedEffects[ chara->getId() ] = resultList;
return result;
return resultList;
}
return it->second;
@ -50,43 +49,207 @@ EffectResultPtr EffectBuilder::getResult( Entity::CharaPtr& chara )
void EffectBuilder::healTarget( Entity::CharaPtr& target, uint32_t amount, Common::ActionHitSeverityType severity )
{
auto result = getResult( target );
assert( result );
auto resultList = getResultList( target );
assert( resultList );
result->heal( amount, severity );
EffectResultPtr nextResult = make_EffectResult( target, getResultDelayMs() );
nextResult->heal( amount, severity, false );
resultList->push_back( std::move( nextResult ) );
}
void EffectBuilder::selfHeal( Entity::CharaPtr& target, Entity::CharaPtr& source, uint32_t amount, Common::ActionHitSeverityType severity )
{
auto resultList = getResultList( target );
assert( resultList );
EffectResultPtr nextResult = make_EffectResult( source, getResultDelayMs() ); // heal the source actor
nextResult->heal( amount, severity, true );
resultList->push_back( std::move( nextResult ) );
}
void EffectBuilder::restoreMP( Entity::CharaPtr& target, Entity::CharaPtr& source, uint32_t amount )
{
auto resultList = getResultList( target );
assert( resultList );
EffectResultPtr nextResult = make_EffectResult( source, getResultDelayMs() ); // restore mp source actor
nextResult->restoreMP( amount );
resultList->push_back( std::move( nextResult ) );
}
void EffectBuilder::damageTarget( Entity::CharaPtr& target, uint32_t amount, Common::ActionHitSeverityType severity )
{
auto result = getResult( target );
assert( result );
auto resultList = getResultList( target );
assert( resultList );
result->damage( amount, severity );
EffectResultPtr nextResult = make_EffectResult( target, getResultDelayMs() );
nextResult->damage( amount, severity );
resultList->push_back( std::move( nextResult ) );
}
void EffectBuilder::startCombo( Entity::CharaPtr& target, uint16_t actionId )
{
auto resultList = getResultList( target );
assert( resultList );
EffectResultPtr nextResult = make_EffectResult( target, 0 );
nextResult->startCombo( actionId );
resultList->push_back( std::move( nextResult ) );
}
void EffectBuilder::comboVisualEffect( Entity::CharaPtr& target )
{
auto resultList = getResultList( target );
assert( resultList );
EffectResultPtr nextResult = make_EffectResult( target, 0 );
nextResult->comboVisualEffect();
resultList->push_back( std::move( nextResult ) );
}
void EffectBuilder::buildAndSendPackets()
{
auto targetCount = m_resolvedEffects.size();
Logger::debug( "EffectBuilder result: " );
Logger::debug( "Targets afflicted: {}", m_resolvedEffects.size() );
Logger::debug( "Targets afflicted: {}", targetCount );
for( auto it = m_resolvedEffects.begin(); it != m_resolvedEffects.end(); )
auto globalSequence = m_sourceChara->getCurrentTerritory()->getNextEffectSequence();
while( m_resolvedEffects.size() > 0 )
{
auto result = it->second;
Logger::debug( " - id: {}", result->getTarget()->getId() );
auto packet = buildNextEffectPacket( globalSequence );
m_sourceChara->sendToInRangeSet( packet, true );
}
}
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 = ( EffectHeader* )( &( p->data() ) );
pEntry = ( Common::EffectEntry* )( &( p->data().effects ) );
pEffectTargetId = ( uint64_t* )( &( p->data().effectTargetId ) );
pFlag = ( uint16_t* )( &( p->data().unkFlag ) );
effectPacket = std::move( p );
break;
}
case 16:
{
auto p = makeZonePacket< Server::FFXIVIpcAoeEffect16 >( m_sourceChara->getId() );
pHeader = ( EffectHeader* )( &( p->data() ) );
pEntry = ( Common::EffectEntry* )( &( p->data().effects ) );
pEffectTargetId = ( uint64_t* )( &( p->data().effectTargetId ) );
pFlag = ( uint16_t* )( &( p->data().unkFlag ) );
effectPacket = std::move( p );
break;
}
case 24:
{
auto p = makeZonePacket< Server::FFXIVIpcAoeEffect24 >( m_sourceChara->getId() );
pHeader = ( EffectHeader* )( &( p->data() ) );
pEntry = ( Common::EffectEntry* )( &( p->data().effects ) );
pEffectTargetId = ( uint64_t* )( &( p->data().effectTargetId ) );
pFlag = ( uint16_t* )( &( p->data().unkFlag ) );
effectPacket = std::move( p );
break;
}
case 32:
{
auto p = makeZonePacket< Server::FFXIVIpcAoeEffect32 >( m_sourceChara->getId() );
pHeader = ( EffectHeader* )( &( p->data() ) );
pEntry = ( Common::EffectEntry* )( &( p->data().effects ) );
pEffectTargetId = ( uint64_t* )( &( p->data().effectTargetId ) );
pFlag = ( uint16_t* )( &( p->data().unkFlag ) );
effectPacket = std::move( p );
break;
}
}
assert( effectPacket != nullptr );
pHeader->actionAnimationId = m_sourceChara->getId();
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(); )
{
auto resultList = it->second;
assert( resultList->size() > 0 );
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
{
auto resultList = m_resolvedEffects.begin()->second;
assert( resultList->size() > 0 );
auto firstResult = resultList->data()[ 0 ];
Logger::debug( " - id: {}", firstResult->getTarget()->getId() );
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->setSequence( seq, m_sequence );
effectPacket->addEffect( result->buildEffectEntry() );
for( int i = 0; i < resultList->size(); i++ )
{
auto result = resultList->data()[ i ];
effectPacket->addEffect( result->buildEffectEntry() );
m_sourceChara->getCurrentTerritory()->addEffectResult( std::move( result ) );
}
m_sourceChara->sendToInRangeSet( effectPacket, true );
resultList->clear();
// add effect to territory
m_sourceChara->getCurrentTerritory()->addEffectResult( std::move( result ) );
m_resolvedEffects.clear();
it = m_resolvedEffects.erase( it );
return effectPacket;
}
}

View file

@ -15,23 +15,34 @@ namespace Sapphire::World::Action
void healTarget( Entity::CharaPtr& target, uint32_t amount,
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalHeal );
void selfHeal( Entity::CharaPtr& target, Entity::CharaPtr& source, uint32_t amount,
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalHeal );
void restoreMP( Entity::CharaPtr& target, Entity::CharaPtr& source, uint32_t amount );
void damageTarget( Entity::CharaPtr& target, uint32_t amount,
Common::ActionHitSeverityType severity = Common::ActionHitSeverityType::NormalDamage );
void startCombo( Entity::CharaPtr& target, uint16_t actionId );
void comboVisualEffect( Entity::CharaPtr& target );
void buildAndSendPackets();
private:
EffectResultPtr getResult( Entity::CharaPtr& chara );
std::shared_ptr< std::vector< EffectResultPtr > > getResultList( Entity::CharaPtr& chara );
uint64_t getResultDelayMs();
std::shared_ptr< Sapphire::Network::Packets::FFXIVPacketBase > buildNextEffectPacket( uint32_t globalSequence );
private:
uint32_t m_actionId;
uint16_t m_sequence;
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;
};
}

View file

@ -14,7 +14,8 @@ EffectResult::EffectResult( Entity::CharaPtr target, uint64_t runAfter ) :
m_value( 0 ),
m_severity( Common::ActionHitSeverityType::NormalDamage ),
m_type( Common::ActionEffectType::Nothing ),
m_param( 0 )
m_param( 0 ),
m_flag( 0 )
{
}
@ -47,14 +48,36 @@ void EffectResult::damage( uint32_t amount, Common::ActionHitSeverityType severi
m_type = Common::ActionEffectType::Damage;
}
void EffectResult::heal( uint32_t amount, Sapphire::Common::ActionHitSeverityType severity )
void EffectResult::heal( uint32_t amount, Sapphire::Common::ActionHitSeverityType severity, bool isSelfHeal )
{
m_severity = severity;
m_value = amount;
m_flag = isSelfHeal ? 0x80 : 0; // flag == 0x80 displays healing text at source actor
m_type = Common::ActionEffectType::Heal;
}
void EffectResult::restoreMP( uint32_t amount )
{
m_value = amount;
m_flag = 0x80;
m_type = Common::ActionEffectType::MpGain;
}
void EffectResult::startCombo( uint16_t actionId )
{
m_value = actionId;
m_flag = 0x80;
m_type = Common::ActionEffectType::StartActionCombo;
}
void EffectResult::comboVisualEffect()
{
m_type = Common::ActionEffectType::ComboVisualEffect;
}
Common::EffectEntry EffectResult::buildEffectEntry() const
{
Common::EffectEntry entry{};
@ -64,6 +87,7 @@ Common::EffectEntry EffectResult::buildEffectEntry() const
entry.hitSeverity = m_severity;
entry.effectType = m_type;
entry.param = m_param;
entry.flags = m_flag;
return entry;
}
@ -84,6 +108,12 @@ void EffectResult::execute()
break;
}
case Common::ActionEffectType::MpGain:
{
m_target->restoreMP( m_value );
break;
}
default:
break;
}

View file

@ -16,7 +16,10 @@ namespace Sapphire::World::Action
explicit EffectResult( Entity::CharaPtr target, uint64_t delayMs );
void damage( uint32_t amount, Common::ActionHitSeverityType severity );
void heal( uint32_t amount, Common::ActionHitSeverityType severity );
void heal( uint32_t amount, Common::ActionHitSeverityType severity, bool isSelfHeal );
void restoreMP( uint32_t amount );
void startCombo( uint16_t actionId );
void comboVisualEffect();
Entity::CharaPtr getTarget() const;
@ -40,6 +43,7 @@ namespace Sapphire::World::Action
uint32_t m_value;
uint8_t m_param;
uint8_t m_flag;
};
}

View file

@ -438,6 +438,18 @@ void Sapphire::Entity::Chara::heal( uint32_t amount )
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 )
TODO: poor naming, should be changed. Status is not HP. Also should be virtual

View file

@ -263,6 +263,8 @@ namespace Sapphire::Entity
virtual void heal( uint32_t amount );
virtual void restoreMP( uint32_t amount );
virtual bool checkAction();
virtual void update( uint64_t tickCount );

View file

@ -76,7 +76,8 @@ Sapphire::Entity::Player::Player( FrameworkPtr pFw ) :
m_emoteMode( 0 ),
m_directorInitialized( false ),
m_onEnterEventDone( false ),
m_falling( false )
m_falling( false ),
m_pQueuedAction( nullptr )
{
m_id = 0;
m_currentStance = Stance::Passive;
@ -2131,3 +2132,43 @@ Sapphire::Common::ActiveLand Sapphire::Entity::Player::getActiveLand() const
{
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 = nullptr; // overwrite whatever is already there
m_pQueuedAction = std::move( pAction );
}
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;
}

View file

@ -643,6 +643,11 @@ namespace Sapphire::Entity
/*! return a const pointer to the mount guide bitmask array */
const uint8_t* getMountGuideBitmask() const;
bool checkAction() override;
bool hasQueuedAction() const;
void setQueuedAction( World::Action::ActionPtr pAction );
// Spawn handling
//////////////////////////////////////////////////////////////////////////////////////////////////////
@ -1002,6 +1007,8 @@ namespace Sapphire::Entity
uint32_t m_inventorySequence;
World::Action::ActionPtr m_pQueuedAction;
private:
using InventoryMap = std::map< uint16_t, Sapphire::ItemContainerPtr >;

View file

@ -27,6 +27,8 @@ void World::Manager::ActionMgr::handlePlacedPlayerAction( Entity::Player& player
auto action = Action::make_Action( player.getAsPlayer(), actionId, sequence, actionData, framework() );
action->setPos( pos );
if( !action->init() )
return;
@ -37,8 +39,6 @@ void World::Manager::ActionMgr::handlePlacedPlayerAction( Entity::Player& player
return;
}
action->setPos( pos );
bootstrapAction( player, action, *actionData );
}
@ -50,6 +50,8 @@ void World::Manager::ActionMgr::handleTargetedPlayerAction( Entity::Player& play
action->setTargetId( targetId );
action->setPos( player.getPos() );
if( !action->init() )
return;
@ -87,12 +89,20 @@ void World::Manager::ActionMgr::bootstrapAction( Entity::Player& player,
return;
}
// if we have a cast time we want to associate the action with the player so update is called
if( currentAction->hasCastTime() )
if( player.getCurrentAction() )
{
player.setCurrentAction( currentAction );
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( currentAction->hasCastTime() )
{
player.setCurrentAction( currentAction );
}
// todo: what do in cases of swiftcast/etc? script callback?
currentAction->start();
// todo: what do in cases of swiftcast/etc? script callback?
currentAction->start();
}
}

View file

@ -25,13 +25,14 @@ namespace Sapphire::Network::Packets::Server
m_data.effectTargetId = targetId;
m_data.effectDisplayType = Common::ActionEffectDisplayType::ShowActionName;
std::memset( m_data.effects, 0, sizeof(Common::EffectEntry) * 8 );
}
void addEffect( const Common::EffectEntry& effect )
{
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 ) );
m_data.effectCount++;
}