2017-08-08 13:53:47 +02:00
|
|
|
#include "Action.h"
|
|
|
|
|
2019-02-08 22:09:48 +11:00
|
|
|
#include <Exd/ExdDataGenerated.h>
|
2018-03-06 22:22:19 +01:00
|
|
|
#include <Util/Util.h>
|
2019-02-08 22:09:48 +11:00
|
|
|
#include "Framework.h"
|
|
|
|
#include "Script/ScriptMgr.h"
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2019-02-08 22:09:48 +11:00
|
|
|
#include "Actor/Player.h"
|
2019-02-11 10:36:17 +11:00
|
|
|
#include "Actor/BNpc.h"
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2019-02-11 11:47:04 +11:00
|
|
|
#include "Territory/Zone.h"
|
|
|
|
|
2019-02-08 22:09:48 +11:00
|
|
|
#include <Network/CommonActorControl.h>
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket142.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket143.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket144.h"
|
2019-02-11 00:11:29 +11:00
|
|
|
#include <Network/PacketWrappers/EffectPacket.h>
|
2019-02-08 22:09:48 +11:00
|
|
|
|
|
|
|
using namespace Sapphire::Common;
|
|
|
|
using namespace Sapphire::Network;
|
|
|
|
using namespace Sapphire::Network::Packets;
|
|
|
|
using namespace Sapphire::Network::Packets::Server;
|
|
|
|
using namespace Sapphire::Network::ActorControl;
|
|
|
|
|
|
|
|
|
|
|
|
Sapphire::Action::Action::Action() = default;
|
|
|
|
Sapphire::Action::Action::~Action() = default;
|
|
|
|
|
2019-02-09 15:39:05 +11:00
|
|
|
Sapphire::Action::Action::Action( Entity::CharaPtr caster, uint32_t actionId,
|
|
|
|
Data::ActionPtr action, FrameworkPtr fw ) :
|
2019-02-08 22:09:48 +11:00
|
|
|
m_pSource( std::move( caster ) ),
|
|
|
|
m_pFw( std::move( fw ) ),
|
|
|
|
m_id( actionId ),
|
2019-02-09 18:32:10 +11:00
|
|
|
m_startTime( 0 ),
|
2019-02-10 22:13:47 +11:00
|
|
|
m_interruptType( Common::ActionInterruptType::None ),
|
|
|
|
m_hasResidentTarget( false )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-02-10 21:01:04 +11:00
|
|
|
m_castTime = static_cast< uint32_t >( action->cast100ms * 100 );
|
2019-02-10 22:13:47 +11:00
|
|
|
m_recastTime = static_cast< uint16_t >( action->recast100ms * 100 );
|
2019-02-10 21:01:04 +11:00
|
|
|
m_cooldownGroup = action->cooldownGroup;
|
2019-02-11 01:50:41 +11:00
|
|
|
m_range = action->range;
|
|
|
|
m_effectRange = action->effectRange;
|
2019-02-11 10:03:36 +11:00
|
|
|
m_aspect = static_cast< Common::ActionAspect >( action->aspect );
|
2019-02-11 01:50:41 +11:00
|
|
|
|
|
|
|
// a default range is set by the game for the class/job
|
|
|
|
if( m_range == -1 )
|
|
|
|
{
|
|
|
|
switch( static_cast< Common::ClassJob >( action->classJob ) )
|
|
|
|
{
|
|
|
|
case Common::ClassJob::Bard:
|
|
|
|
case Common::ClassJob::Archer:
|
|
|
|
m_range = 25;
|
|
|
|
|
|
|
|
// anything that isnt ranged
|
|
|
|
default:
|
|
|
|
m_range = 3;
|
|
|
|
}
|
|
|
|
}
|
2019-02-09 18:01:39 +11:00
|
|
|
|
2019-02-09 23:14:30 +11:00
|
|
|
m_actionCost.fill( { Common::ActionCostType::None, 0 } );
|
2019-02-09 20:49:22 +11:00
|
|
|
|
2019-02-09 23:14:30 +11:00
|
|
|
m_actionCost[ 0 ] = {
|
|
|
|
static_cast< Common::ActionCostType >( action->costType ),
|
|
|
|
action->cost
|
|
|
|
};
|
|
|
|
|
|
|
|
calculateActionCost();
|
2019-02-09 15:39:05 +11:00
|
|
|
}
|
2017-08-08 13:53:47 +02:00
|
|
|
|
2019-02-09 15:39:05 +11:00
|
|
|
uint32_t Sapphire::Action::Action::getId() const
|
|
|
|
{
|
|
|
|
return m_id;
|
|
|
|
}
|
2019-02-08 22:09:48 +11:00
|
|
|
|
2019-02-09 18:32:10 +11:00
|
|
|
void Sapphire::Action::Action::setPos( Sapphire::Common::FFXIVARR_POSITION3 pos )
|
|
|
|
{
|
|
|
|
m_pos = pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Common::FFXIVARR_POSITION3 Sapphire::Action::Action::getPos() const
|
|
|
|
{
|
|
|
|
return m_pos;
|
|
|
|
}
|
|
|
|
|
2019-02-09 15:39:05 +11:00
|
|
|
void Sapphire::Action::Action::setTargetChara( Sapphire::Entity::CharaPtr chara )
|
|
|
|
{
|
|
|
|
assert( chara );
|
|
|
|
|
|
|
|
m_pTarget = chara;
|
|
|
|
m_targetId = chara->getId();
|
2019-02-11 01:50:41 +11:00
|
|
|
m_hasResidentTarget = false;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-02-10 22:13:47 +11:00
|
|
|
void Sapphire::Action::Action::setResidentTargetId( uint64_t targetId )
|
|
|
|
{
|
|
|
|
m_targetId = targetId;
|
|
|
|
m_hasResidentTarget = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::Action::Action::hasResidentTarget() const
|
|
|
|
{
|
|
|
|
return m_hasResidentTarget;
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
Sapphire::Entity::CharaPtr Sapphire::Action::Action::getTargetChara() const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_pTarget;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
bool Sapphire::Action::Action::isInterrupted() const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-02-10 21:21:34 +11:00
|
|
|
return m_interruptType != Common::ActionInterruptType::None;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-02-10 21:21:34 +11:00
|
|
|
void Sapphire::Action::Action::setInterrupted( Common::ActionInterruptType type )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2019-02-10 21:12:22 +11:00
|
|
|
m_interruptType = type;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint32_t Sapphire::Action::Action::getCastTime() const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_castTime;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Action::Action::setCastTime( uint32_t castTime )
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_castTime = castTime;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
bool Sapphire::Action::Action::hasCastTime() const
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
|
|
|
return m_castTime > 0;
|
|
|
|
}
|
|
|
|
|
2019-02-11 10:14:14 +11:00
|
|
|
Sapphire::Entity::CharaPtr Sapphire::Action::Action::getSourceChara() const
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_pSource;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
bool Sapphire::Action::Action::update()
|
2017-08-08 13:53:47 +02:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
// action has not been started yet
|
|
|
|
if( m_startTime == 0 )
|
|
|
|
return false;
|
|
|
|
|
2019-02-10 21:12:22 +11:00
|
|
|
if( isInterrupted() )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
2019-02-11 00:40:00 +11:00
|
|
|
castInterrupt();
|
2018-08-29 21:40:59 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-02-10 22:13:47 +11:00
|
|
|
if( !hasResidentTarget() )
|
|
|
|
{
|
|
|
|
// todo: check if the target is still in range
|
|
|
|
}
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint64_t currTime = Util::getTimeMs();
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
if( !hasCastTime() || std::difftime( currTime, m_startTime ) > m_castTime )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
2019-02-11 00:40:00 +11:00
|
|
|
castFinish();
|
2018-08-29 21:40:59 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-02-10 21:01:04 +11:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return false;
|
2017-08-08 13:53:47 +02:00
|
|
|
}
|
2019-02-08 22:09:48 +11:00
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
void Sapphire::Action::Action::castStart()
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
|
|
|
assert( m_pSource );
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
m_startTime = Util::getTimeMs();
|
|
|
|
|
2019-02-10 22:13:47 +11:00
|
|
|
auto player = m_pSource->getAsPlayer();
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
if( hasCastTime() )
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
2019-02-09 15:45:02 +11:00
|
|
|
auto castPacket = makeZonePacket< Server::FFXIVIpcActorCast >( getId() );
|
2019-02-08 22:09:48 +11:00
|
|
|
|
2019-02-10 21:18:03 +11:00
|
|
|
castPacket->data().action_id = static_cast< uint16_t >( m_id );
|
2019-02-08 22:09:48 +11:00
|
|
|
castPacket->data().skillType = Common::SkillType::Normal;
|
|
|
|
castPacket->data().unknown_1 = m_id;
|
|
|
|
// This is used for the cast bar above the target bar of the caster.
|
2019-02-09 15:39:05 +11:00
|
|
|
castPacket->data().cast_time = m_castTime / 1000.f;
|
2019-02-10 21:18:03 +11:00
|
|
|
castPacket->data().target_id = static_cast< uint32_t >( m_targetId );
|
2019-02-08 22:09:48 +11:00
|
|
|
|
|
|
|
m_pSource->sendToInRangeSet( castPacket, true );
|
2019-02-10 21:18:03 +11:00
|
|
|
|
2019-02-10 22:13:47 +11:00
|
|
|
if( player )
|
2019-02-10 21:18:03 +11:00
|
|
|
{
|
|
|
|
player->setStateFlag( PlayerStateFlag::Casting );
|
|
|
|
}
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
2019-02-10 23:34:05 +11:00
|
|
|
|
|
|
|
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
2019-02-11 10:14:14 +11:00
|
|
|
if( !pScriptMgr->onCastStart( *this ) )
|
2019-02-11 00:40:00 +11:00
|
|
|
{
|
|
|
|
// script not implemented
|
|
|
|
castInterrupt();
|
|
|
|
|
|
|
|
if( player )
|
|
|
|
{
|
|
|
|
player->sendUrgent( "Action not implemented, missing script for actionId#{0}", getId() );
|
|
|
|
player->setCurrentAction( nullptr );
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// instantly finish cast if there's no cast time
|
|
|
|
if( !hasCastTime() )
|
|
|
|
castFinish();
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
void Sapphire::Action::Action::castInterrupt()
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
|
|
|
assert( m_pSource );
|
|
|
|
|
2019-02-10 21:01:04 +11:00
|
|
|
// things that aren't players don't care about cooldowns and state flags
|
|
|
|
if( m_pSource->isPlayer() )
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
2019-02-10 21:01:04 +11:00
|
|
|
auto player = m_pSource->getAsPlayer();
|
|
|
|
|
|
|
|
auto resetCooldownPkt = makeActorControl143( m_pSource->getId(), ActorControlType::SetActionCooldown, 1, getId(), 0 );
|
|
|
|
player->queuePacket( resetCooldownPkt );
|
|
|
|
|
|
|
|
// todo: reset cooldown for actual player
|
|
|
|
|
|
|
|
// reset state flag
|
2019-02-10 21:18:03 +11:00
|
|
|
//player->unsetStateFlag( PlayerStateFlag::Occupied1 );
|
2019-02-10 21:01:04 +11:00
|
|
|
player->unsetStateFlag( PlayerStateFlag::Casting );
|
|
|
|
}
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
if( hasCastTime() )
|
2019-02-10 21:01:04 +11:00
|
|
|
{
|
2019-02-10 21:12:22 +11:00
|
|
|
uint8_t interruptEffect = 0;
|
|
|
|
if( m_interruptType == ActionInterruptType::DamageInterrupt )
|
|
|
|
interruptEffect = 1;
|
|
|
|
|
|
|
|
// Note: When cast interrupt from taking too much damage, set the last value to 1.
|
|
|
|
// This enables the cast interrupt effect.
|
|
|
|
auto control = makeActorControl142( m_pSource->getId(), ActorControlType::CastInterrupt,
|
|
|
|
0x219, 1, m_id, interruptEffect );
|
2019-02-08 22:09:48 +11:00
|
|
|
|
|
|
|
m_pSource->sendToInRangeSet( control, true );
|
|
|
|
}
|
2019-02-10 23:34:05 +11:00
|
|
|
|
|
|
|
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
2019-02-11 10:14:14 +11:00
|
|
|
pScriptMgr->onCastInterrupt( *this );
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
void Sapphire::Action::Action::castFinish()
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
|
|
|
assert( m_pSource );
|
|
|
|
|
|
|
|
auto pScriptMgr = m_pFw->get< Scripting::ScriptMgr >();
|
|
|
|
|
2019-02-11 00:40:00 +11:00
|
|
|
if( hasCastTime() )
|
2019-02-08 22:09:48 +11:00
|
|
|
{
|
|
|
|
/*auto control = ActorControlPacket143( m_pTarget->getId(), ActorControlType::Unk7,
|
|
|
|
0x219, m_id, m_id, m_id, m_id );
|
|
|
|
m_pSource->sendToInRangeSet( control, true );*/
|
|
|
|
|
2019-02-10 22:13:47 +11:00
|
|
|
}
|
|
|
|
|
2019-02-11 10:14:14 +11:00
|
|
|
pScriptMgr->onCastFinish( *this );
|
2019-02-10 23:34:05 +11:00
|
|
|
|
2019-02-10 22:13:47 +11:00
|
|
|
if( !hasResidentTarget() )
|
|
|
|
{
|
2019-02-10 23:53:44 +11:00
|
|
|
assert( m_pTarget );
|
2019-02-10 23:34:05 +11:00
|
|
|
// todo: calculate final hit targets and call onCharaHit in action script
|
2019-02-11 10:14:14 +11:00
|
|
|
pScriptMgr->onCharaHit( *this, *m_pTarget );
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
2019-02-11 14:51:56 +11:00
|
|
|
else if( auto player = m_pSource->getAsPlayer() )
|
2019-02-10 22:13:47 +11:00
|
|
|
{
|
2019-02-11 14:51:56 +11:00
|
|
|
pScriptMgr->onEObjHit( *player, m_targetId );
|
2019-02-10 23:28:15 +11:00
|
|
|
return;
|
2019-02-10 22:13:47 +11:00
|
|
|
}
|
2019-02-10 23:28:15 +11:00
|
|
|
|
2019-02-11 00:11:29 +11:00
|
|
|
buildEffectPackets();
|
2019-02-08 22:09:48 +11:00
|
|
|
}
|
2019-02-09 17:36:44 +11:00
|
|
|
|
2019-02-11 00:11:29 +11:00
|
|
|
void Sapphire::Action::Action::buildEffectPackets()
|
2019-02-09 17:36:44 +11:00
|
|
|
{
|
|
|
|
for( int i = 0; i < EffectPacketIdentity::MAX_ACTION_EFFECT_PACKET_IDENT; ++i )
|
|
|
|
{
|
|
|
|
auto& packetData = m_effects[ static_cast< EffectPacketIdentity >( i ) ];
|
|
|
|
|
2019-02-11 11:47:04 +11:00
|
|
|
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 )
|
2019-02-11 00:11:29 +11:00
|
|
|
{
|
|
|
|
// 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 ] );
|
2019-02-11 11:47:04 +11:00
|
|
|
effectPacket->setSequence( sequence );
|
2019-02-11 00:11:29 +11:00
|
|
|
effectPacket->setDisplayType( Common::ActionEffectDisplayType::ShowActionName );
|
|
|
|
|
|
|
|
for( auto& effect : packetData.m_entries )
|
|
|
|
{
|
|
|
|
effectPacket->addEffect( effect );
|
|
|
|
}
|
|
|
|
|
|
|
|
m_pSource->sendToInRangeSet( effectPacket, true );
|
|
|
|
}
|
2019-02-11 11:47:04 +11:00
|
|
|
else
|
2019-02-11 00:11:29 +11:00
|
|
|
{
|
|
|
|
// todo: aoe effects
|
|
|
|
}
|
2019-02-09 17:36:44 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-17 16:55:00 +11:00
|
|
|
void Sapphire::Action::Action::damageTarget( uint16_t potency, Entity::Chara& chara )
|
2019-02-09 17:36:44 +11:00
|
|
|
{
|
2019-02-11 00:40:00 +11:00
|
|
|
// todo: scale potency into damage from stats
|
|
|
|
|
2019-02-09 17:36:44 +11:00
|
|
|
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?
|
2019-02-11 00:11:29 +11:00
|
|
|
if( potency > 65535 )
|
2019-02-09 17:36:44 +11:00
|
|
|
{
|
2019-02-11 00:11:29 +11:00
|
|
|
entry.value = static_cast< int16_t >( potency / 10 );
|
2019-02-09 17:36:44 +11:00
|
|
|
// todo: rename this? need to confirm how it works again
|
2019-02-17 16:55:00 +11:00
|
|
|
entry.valueMultiplier = 1;
|
2019-02-09 17:36:44 +11:00
|
|
|
}
|
|
|
|
else
|
2019-02-11 00:11:29 +11:00
|
|
|
entry.value = static_cast< int16_t >( potency );
|
2019-02-09 17:36:44 +11:00
|
|
|
|
2019-02-11 10:36:17 +11:00
|
|
|
// 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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-09 17:36:44 +11:00
|
|
|
// todo: aspected damage?
|
2019-02-11 00:11:29 +11:00
|
|
|
chara.takeDamage( potency );
|
2019-02-09 17:36:44 +11:00
|
|
|
|
2019-02-10 23:53:44 +11:00
|
|
|
if( auto player = m_pSource->getAsPlayer() )
|
2019-02-11 14:51:56 +11:00
|
|
|
player->sendDebug( "hit actorId#{0} for potency: {1}", chara.getId(), potency );
|
2019-02-10 23:53:44 +11:00
|
|
|
|
2019-02-09 17:36:44 +11:00
|
|
|
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() );
|
|
|
|
}
|
|
|
|
|
2019-02-17 16:55:00 +11:00
|
|
|
void Sapphire::Action::Action::healTarget( uint16_t potency, Entity::Chara& chara )
|
2019-02-09 17:36:44 +11:00
|
|
|
{
|
2019-02-11 00:40:00 +11:00
|
|
|
// todo: scale potency into healing from stats
|
|
|
|
|
2019-02-09 17:36:44 +11:00
|
|
|
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?
|
2019-02-11 00:11:29 +11:00
|
|
|
if( potency > 65535 )
|
2019-02-09 17:36:44 +11:00
|
|
|
{
|
2019-02-11 00:11:29 +11:00
|
|
|
entry.value = static_cast< int16_t >( potency / 10 );
|
2019-02-09 17:36:44 +11:00
|
|
|
// todo: rename this? need to confirm how it works again
|
2019-02-17 16:55:00 +11:00
|
|
|
entry.valueMultiplier = 1;
|
2019-02-09 17:36:44 +11:00
|
|
|
}
|
|
|
|
else
|
2019-02-11 00:11:29 +11:00
|
|
|
entry.value = static_cast< int16_t >( potency );
|
2019-02-09 17:36:44 +11:00
|
|
|
|
2019-02-11 00:11:29 +11:00
|
|
|
chara.heal( potency );
|
2019-02-09 17:36:44 +11:00
|
|
|
|
2019-02-10 23:53:44 +11:00
|
|
|
if( auto player = m_pSource->getAsPlayer() )
|
2019-02-11 00:11:29 +11:00
|
|
|
player->sendDebug( "hit actorId#{0} for heal: {1}", chara.getId(), potency );
|
2019-02-10 23:53:44 +11:00
|
|
|
|
2019-02-09 17:36:44 +11:00
|
|
|
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() );
|
|
|
|
}
|
2019-02-09 23:14:30 +11:00
|
|
|
|
|
|
|
const Sapphire::Action::Action::ActionCostArray& Sapphire::Action::Action::getCostArray() const
|
|
|
|
{
|
|
|
|
return m_actionCost;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Action::Action::calculateActionCost()
|
|
|
|
{
|
|
|
|
for( uint8_t i = 0; i < m_actionCost.size(); ++i )
|
|
|
|
{
|
|
|
|
auto& entry = m_actionCost[ i ];
|
|
|
|
|
|
|
|
if( entry.m_costType == ActionCostType::MagicPoints )
|
|
|
|
calculateMPCost( i );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-17 15:47:03 +11:00
|
|
|
// todo: this shouldn't be in action and instead be in some general stat calc util
|
2019-02-09 23:14:30 +11:00
|
|
|
void Sapphire::Action::Action::calculateMPCost( uint8_t costArrayIndex )
|
|
|
|
{
|
|
|
|
auto level = m_pSource->getLevel();
|
|
|
|
|
|
|
|
// each level range is 1-10, 11-20, 21-30, ... therefore:
|
|
|
|
// level 50 should be in the 4th group, not the 5th
|
|
|
|
// dividing by 10 on the border will break this unless we subtract 1
|
|
|
|
auto levelGroup = std::max< uint8_t >( level - 1, 1 ) / 10;
|
|
|
|
|
|
|
|
auto& costEntry = m_actionCost[ costArrayIndex ];
|
|
|
|
|
|
|
|
float cost = costEntry.m_cost;
|
|
|
|
|
|
|
|
// thanks to andrew for helping me figure this shit out, should be pretty accurate
|
|
|
|
switch( levelGroup )
|
|
|
|
{
|
|
|
|
// level 1-10
|
|
|
|
case 0:
|
|
|
|
{
|
|
|
|
// r^2 = 0.9999
|
|
|
|
cost = 0.0952f * level + 0.9467f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// level 11-20
|
|
|
|
case 1:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 0.19f * level;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// level 21-30
|
|
|
|
case 2:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 0.38f * level - 3.8f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// level 31-40
|
|
|
|
case 3:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 0.6652f * level - 12.358f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// level 41-50
|
|
|
|
case 4:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 1.2352f * level - 35.159f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// level 51-60
|
|
|
|
case 5:
|
|
|
|
{
|
|
|
|
// r^2 = 1
|
|
|
|
cost = 0.0654f * std::exp( 0.1201f * level );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// level 61-70
|
|
|
|
case 6:
|
|
|
|
{
|
|
|
|
// r^2 = 0.9998
|
|
|
|
cost = 0.2313f * ( level * level ) - 26.98f * level + 875.21f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// m_cost is the base cost, cost is the multiplier for the current player level
|
|
|
|
costEntry.m_cost = static_cast< uint16_t >( std::round( cost * costEntry.m_cost ) );
|
2019-02-10 23:34:05 +11:00
|
|
|
|
|
|
|
if( auto player = m_pSource->getAsPlayer() )
|
|
|
|
player->sendDebug( "calculated mp cost: {0}", costEntry.m_cost );
|
2019-02-09 23:14:30 +11:00
|
|
|
}
|