2018-03-06 22:22:19 +01:00
|
|
|
#include <Util/Util.h>
|
|
|
|
#include <Util/UtilMath.h>
|
|
|
|
#include <Network/PacketContainer.h>
|
|
|
|
#include <Exd/ExdDataGenerated.h>
|
2018-06-28 00:07:07 +02:00
|
|
|
#include <utility>
|
2018-06-23 21:38:04 +02:00
|
|
|
#include <Network/CommonActorControl.h>
|
2018-07-21 23:32:10 +10:00
|
|
|
#include <sapphire_zone/Network/PacketWrappers/EffectPacket.h>
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
#include "Forwards.h"
|
|
|
|
#include "Action/Action.h"
|
|
|
|
|
|
|
|
#include "Zone/Zone.h"
|
|
|
|
|
|
|
|
#include "Network/GameConnection.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket142.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket143.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket144.h"
|
|
|
|
#include "Network/PacketWrappers/UpdateHpMpTpPacket.h"
|
|
|
|
|
|
|
|
#include "StatusEffect/StatusEffect.h"
|
|
|
|
#include "Action/ActionCollision.h"
|
|
|
|
#include "ServerZone.h"
|
|
|
|
#include "Session.h"
|
|
|
|
#include "Math/CalcBattle.h"
|
|
|
|
#include "Chara.h"
|
|
|
|
#include "Player.h"
|
|
|
|
#include "Zone/TerritoryMgr.h"
|
2018-03-06 00:10:36 +01:00
|
|
|
#include "Framework.h"
|
2018-08-28 19:05:52 +02:00
|
|
|
#include "Common.h"
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-03-09 00:06:44 +01:00
|
|
|
extern Core::Framework g_fw;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
using namespace Core::Common;
|
|
|
|
using namespace Core::Network::Packets;
|
|
|
|
using namespace Core::Network::Packets::Server;
|
2018-06-23 21:38:04 +02:00
|
|
|
using namespace Core::Network::ActorControl;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
Core::Entity::Chara::Chara( ObjKind type ) :
|
2018-08-29 21:40:59 +02:00
|
|
|
Actor( type ),
|
|
|
|
m_targetId( INVALID_GAME_OBJECT_ID )
|
2018-02-20 22:46:44 +01:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
// initialize the free slot queue
|
|
|
|
for( uint8_t i = 0; i < MAX_STATUS_EFFECTS; i++ )
|
|
|
|
{
|
|
|
|
m_statusEffectFreeSlotQueue.push( i );
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Core::Entity::Chara::~Chara()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return the actors name */
|
|
|
|
std::string Core::Entity::Chara::getName() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return std::string( m_name );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*! \return current stance of the actors */
|
2018-08-28 19:05:52 +02:00
|
|
|
Core::Common::Stance Core::Entity::Chara::getStance() const
|
2018-02-20 22:46:44 +01:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_currentStance;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return actor stats */
|
|
|
|
Core::Entity::Chara::ActorStats Core::Entity::Chara::getStats() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_baseStats;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return current HP */
|
|
|
|
uint32_t Core::Entity::Chara::getHp() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_hp;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return current MP */
|
|
|
|
uint32_t Core::Entity::Chara::getMp() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_mp;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return current TP */
|
|
|
|
uint16_t Core::Entity::Chara::getTp() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_tp;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return current GP */
|
|
|
|
uint16_t Core::Entity::Chara::getGp() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_gp;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return current invincibility type */
|
|
|
|
InvincibilityType Core::Entity::Chara::getInvincibilityType() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_invincibilityType;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return current class or job */
|
|
|
|
Core::Common::ClassJob Core::Entity::Chara::getClass() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_class;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return current class or job as int32_t ( this feels pointless ) */
|
|
|
|
uint8_t Core::Entity::Chara::getClassAsInt() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return static_cast< uint8_t >( m_class );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param ClassJob to set */
|
|
|
|
void Core::Entity::Chara::setClass( Common::ClassJob classJob )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_class = classJob;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param Id of the target to set */
|
|
|
|
void Core::Entity::Chara::setTargetId( uint64_t targetId )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_targetId = targetId;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return Id of the current target */
|
|
|
|
uint64_t Core::Entity::Chara::getTargetId() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_targetId;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return True if the actor is alive */
|
|
|
|
bool Core::Entity::Chara::isAlive() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return ( m_hp > 0 );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return max hp for the actor */
|
|
|
|
uint32_t Core::Entity::Chara::getMaxHp() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_baseStats.max_hp;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return max mp for the actor */
|
|
|
|
uint32_t Core::Entity::Chara::getMaxMp() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_baseStats.max_mp;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return reset hp to current max hp */
|
|
|
|
void Core::Entity::Chara::resetHp()
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_hp = getMaxHp();
|
|
|
|
sendStatusUpdate( true );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return reset mp to current max mp */
|
|
|
|
void Core::Entity::Chara::resetMp()
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_mp = getMaxMp();
|
|
|
|
sendStatusUpdate( true );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param hp amount to set ( caps to maxHp ) */
|
|
|
|
void Core::Entity::Chara::setHp( uint32_t hp )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_hp = hp < getMaxHp() ? hp : getMaxHp();
|
|
|
|
sendStatusUpdate( true );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param mp amount to set ( caps to maxMp ) */
|
|
|
|
void Core::Entity::Chara::setMp( uint32_t mp )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_mp = mp < getMaxMp() ? mp : getMaxMp();
|
|
|
|
sendStatusUpdate( true );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param gp amount to set*/
|
|
|
|
void Core::Entity::Chara::setGp( uint32_t gp )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_gp = gp;
|
|
|
|
sendStatusUpdate( true );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param type invincibility type to set */
|
|
|
|
void Core::Entity::Chara::setInvincibilityType( Common::InvincibilityType type )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_invincibilityType = type;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return current status of the actor */
|
2018-08-28 19:05:52 +02:00
|
|
|
Core::Common::ActorStatus Core::Entity::Chara::getStatus() const
|
2018-02-20 22:46:44 +01:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_status;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param status to set */
|
|
|
|
void Core::Entity::Chara::setStatus( ActorStatus status )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_status = status;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Performs necessary steps to mark an actor dead.
|
|
|
|
Sets hp/mp/tp, sets status, plays animation and fires onDeath event
|
|
|
|
*/
|
|
|
|
void Core::Entity::Chara::die()
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_status = ActorStatus::Dead;
|
|
|
|
m_hp = 0;
|
|
|
|
m_mp = 0;
|
|
|
|
m_tp = 0;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// fire onDeath event
|
|
|
|
onDeath();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// if the actor is a player, the update needs to be send to himself too
|
|
|
|
bool selfNeedsUpdate = isPlayer();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
FFXIVPacketBasePtr packet = makeActorControl142( m_id, SetStatus, static_cast< uint8_t >( ActorStatus::Dead ) );
|
|
|
|
sendToInRangeSet( packet, selfNeedsUpdate );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// TODO: not all actor show the death animation when they die, some quest npcs might just despawn
|
|
|
|
// although that might be handled by setting the HP to 1 and doing some script magic
|
2018-06-28 00:07:07 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
FFXIVPacketBasePtr packet1 = makeActorControl142( m_id, DeathAnimation, 0, 0, 0, 0x20 );
|
|
|
|
sendToInRangeSet( packet1, selfNeedsUpdate );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Calculates and sets the rotation to look towards a specified
|
|
|
|
position
|
|
|
|
|
|
|
|
\param Position to look towards
|
|
|
|
*/
|
|
|
|
bool Core::Entity::Chara::face( const Common::FFXIVARR_POSITION3& p )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
float oldRot = getRot();
|
|
|
|
float rot = Math::Util::calcAngFrom( getPos().x, getPos().z, p.x, p.z );
|
|
|
|
float newRot = PI - rot + ( PI / 2 );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
m_pCell = nullptr;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
setRot( newRot );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return oldRot != newRot ? true : false;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Set and propagate the actor stance to in range players
|
|
|
|
( not the actor himself )
|
|
|
|
|
|
|
|
\param stance to set
|
|
|
|
*/
|
|
|
|
void Core::Entity::Chara::setStance( Stance stance )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_currentStance = stance;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
FFXIVPacketBasePtr packet = makeActorControl142( m_id, ToggleWeapon, stance, 0 );
|
|
|
|
sendToInRangeSet( packet );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Check if an action is queued for execution, if so update it
|
|
|
|
and if fully performed, clean up again.
|
|
|
|
|
|
|
|
\return true if a queued action has been updated
|
|
|
|
*/
|
|
|
|
bool Core::Entity::Chara::checkAction()
|
|
|
|
{
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( m_pCurrentAction == nullptr )
|
|
|
|
return false;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( m_pCurrentAction->update() )
|
|
|
|
m_pCurrentAction.reset();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return true;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Change the current target and propagate to in range players
|
|
|
|
|
|
|
|
\param target actor id
|
|
|
|
*/
|
|
|
|
void Core::Entity::Chara::changeTarget( uint64_t targetId )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
setTargetId( targetId );
|
|
|
|
FFXIVPacketBasePtr packet = makeActorControl144( m_id, SetTarget, 0, 0, 0, 0, targetId );
|
|
|
|
sendToInRangeSet( packet );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Dummy function \return 0
|
|
|
|
*/
|
|
|
|
uint8_t Core::Entity::Chara::getLevel() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return 0;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Let an actor take damage and perform necessary steps
|
|
|
|
according to resulting hp, propagates new hp value to players
|
|
|
|
in range
|
|
|
|
TODO: eventually this needs to distinguish between physical and
|
|
|
|
magical dmg and take status effects into account
|
|
|
|
|
|
|
|
\param amount of damage to be taken
|
|
|
|
*/
|
|
|
|
void Core::Entity::Chara::takeDamage( uint32_t damage )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
if( damage >= m_hp )
|
|
|
|
{
|
|
|
|
switch( m_invincibilityType )
|
|
|
|
{
|
|
|
|
case InvincibilityNone:
|
|
|
|
setHp( 0 );
|
|
|
|
die();
|
|
|
|
break;
|
|
|
|
case InvincibilityRefill:
|
|
|
|
resetHp();
|
|
|
|
break;
|
|
|
|
case InvincibilityStayAlive:
|
|
|
|
setHp( 0 );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_hp -= damage;
|
|
|
|
|
|
|
|
sendStatusUpdate( false );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Let an actor get healed and perform necessary steps
|
|
|
|
according to resulting hp, propagates new hp value to players
|
|
|
|
in range
|
|
|
|
|
|
|
|
\param amount of hp to be healed
|
|
|
|
*/
|
|
|
|
void Core::Entity::Chara::heal( uint32_t amount )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
if( ( m_hp + amount ) > getMaxHp() )
|
|
|
|
{
|
|
|
|
m_hp = getMaxHp();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_hp += amount;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
sendStatusUpdate( false );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
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
|
|
|
|
so players can have their own version and we can abolish the param.
|
|
|
|
|
|
|
|
\param true if the update should also be sent to the actor ( player ) himself
|
|
|
|
*/
|
|
|
|
void Core::Entity::Chara::sendStatusUpdate( bool toSelf )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
FFXIVPacketBasePtr packet = boost::make_shared< UpdateHpMpTpPacket >( *this );
|
|
|
|
sendToInRangeSet( packet );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \return ActionPtr of the currently registered action, or nullptr */
|
|
|
|
Core::Action::ActionPtr Core::Entity::Chara::getCurrentAction() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_pCurrentAction;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param ActionPtr of the action to be registered */
|
|
|
|
void Core::Entity::Chara::setCurrentAction( Core::Action::ActionPtr pAction )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_pCurrentAction = pAction;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
Autoattack prototype implementation
|
|
|
|
TODO: move the check if the autoAttack can be performed to the callee
|
|
|
|
also rename autoAttack to autoAttack as that is more elaborate
|
|
|
|
On top of that, this only solves attacks from melee classes.
|
|
|
|
Will have to be extended for ranged attacks.
|
|
|
|
|
|
|
|
\param ActorPtr the autoAttack is performed on
|
|
|
|
*/
|
|
|
|
void Core::Entity::Chara::autoAttack( CharaPtr pTarget )
|
|
|
|
{
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint64_t tick = Util::getTimeMs();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( ( tick - m_lastAttack ) > 2500 )
|
|
|
|
{
|
|
|
|
pTarget->onActionHostile( *this );
|
|
|
|
m_lastAttack = tick;
|
|
|
|
srand( static_cast< uint32_t >( tick ) );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint16_t damage = static_cast< uint16_t >( 10 + rand() % 12 );
|
|
|
|
uint32_t variation = static_cast< uint32_t >( 0 + rand() % 4 );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto effectPacket = boost::make_shared< Server::EffectPacket >( getId(), pTarget->getId(), 0x336 );
|
|
|
|
effectPacket->setRotation( Math::Util::floatToUInt16Rot( getRot() ) );
|
2018-07-03 00:01:26 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
Server::EffectEntry effectEntry{};
|
|
|
|
effectEntry.value = damage;
|
|
|
|
effectEntry.effectType = ActionEffectType::Damage;
|
|
|
|
effectEntry.hitSeverity = static_cast< ActionHitSeverityType >( variation );
|
2018-07-21 23:32:10 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
effectPacket->addEffect( effectEntry );
|
2018-07-21 23:32:10 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
sendToInRangeSet( effectPacket );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( isPlayer() )
|
|
|
|
getAsPlayer()->queuePacket( effectPacket );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
pTarget->takeDamage( damage );
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
ChaiScript Skill Handler.
|
|
|
|
|
|
|
|
\param GamePacketPtr to send
|
|
|
|
\param bool should be send to self?
|
|
|
|
*/
|
|
|
|
void Core::Entity::Chara::handleScriptSkill( uint32_t type, uint16_t actionId, uint64_t param1,
|
|
|
|
uint64_t param2, Entity::Chara& target )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
auto pExdData = g_fw.get< Data::ExdDataGenerated >();
|
|
|
|
if( isPlayer() )
|
|
|
|
{
|
|
|
|
getAsPlayer()->sendDebug( std::to_string( target.getId() ) );
|
|
|
|
getAsPlayer()->sendDebug( "Handle script skill type: " + std::to_string( type ) );
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto actionInfoPtr = pExdData->get< Core::Data::Action >( actionId );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// Todo: Effect packet generator. 90% of this is basically setting params and it's basically unreadable.
|
|
|
|
// Prepare packet. This is seemingly common for all packets in the action handler.
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto effectPacket = boost::make_shared< Server::EffectPacket >( getId(), target.getId(), actionId );
|
|
|
|
effectPacket->setRotation( Math::Util::floatToUInt16Rot( getRot() ) );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// Todo: for each actor, calculate how much damage the calculated value should deal to them - 2-step damage calc. we only have 1-step
|
|
|
|
switch( type )
|
|
|
|
{
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
case ActionEffectType::Damage:
|
|
|
|
{
|
2018-07-21 23:32:10 +10:00
|
|
|
Server::EffectEntry effectEntry{};
|
|
|
|
effectEntry.value = static_cast< uint16_t >( param1 );
|
|
|
|
effectEntry.effectType = ActionEffectType::Damage;
|
|
|
|
effectEntry.hitSeverity = ActionHitSeverityType::NormalDamage;
|
|
|
|
|
|
|
|
effectPacket->addEffect( effectEntry );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
if( actionInfoPtr->castType == 1 && actionInfoPtr->effectRange != 0 || actionInfoPtr->castType != 1 )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
// If action on this specific target is valid...
|
|
|
|
if( isPlayer() && !ActionCollision::isActorApplicable( target, TargetFilter::Enemies ) )
|
|
|
|
break;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
sendToInRangeSet( effectPacket, true );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( target.isAlive() )
|
|
|
|
target.onActionHostile( *this );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
target.takeDamage( static_cast< uint32_t >( param1 ) );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto actorsCollided = ActionCollision::getActorsHitFromAction( target.getPos(), getInRangeActors( true ),
|
|
|
|
actionInfoPtr, TargetFilter::Enemies );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
for( const auto& pHitActor : actorsCollided )
|
|
|
|
{
|
|
|
|
effectPacket->setTargetActor( pHitActor->getId() );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// todo: send to range of what? ourselves? when mob script hits this is going to be lacking
|
|
|
|
sendToInRangeSet( effectPacket, true );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( pHitActor->getAsChara()->isAlive() )
|
|
|
|
pHitActor->getAsChara()->onActionHostile( *this );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
pHitActor->getAsChara()->takeDamage( static_cast< uint32_t >( param1 ) );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
// Debug
|
|
|
|
if( isPlayer() )
|
|
|
|
{
|
|
|
|
if( pHitActor->isPlayer() )
|
|
|
|
getAsPlayer()->sendDebug( "AoE hit actor " + std::to_string( pHitActor->getId() ) +
|
|
|
|
" (" + pHitActor->getAsChara()->getName() + ")" );
|
|
|
|
else
|
|
|
|
getAsPlayer()->sendDebug( "AoE hit actor " + std::to_string( pHitActor->getId() ) );
|
|
|
|
}
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
case ActionEffectType::Heal:
|
|
|
|
{
|
|
|
|
uint32_t calculatedHeal = Math::CalcBattle::calculateHealValue( getAsPlayer(),
|
|
|
|
static_cast< uint32_t >( param1 ) );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-07-21 23:32:10 +10:00
|
|
|
Server::EffectEntry effectEntry{};
|
|
|
|
effectEntry.value = calculatedHeal;
|
|
|
|
effectEntry.effectType = ActionEffectType::Heal;
|
|
|
|
effectEntry.hitSeverity = ActionHitSeverityType::NormalHeal;
|
|
|
|
|
|
|
|
effectPacket->addEffect( effectEntry );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
if( actionInfoPtr->castType == 1 && actionInfoPtr->effectRange != 0 || actionInfoPtr->castType != 1 )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
if( isPlayer() && !ActionCollision::isActorApplicable( target, TargetFilter::Allies ) )
|
|
|
|
break;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
sendToInRangeSet( effectPacket, true );
|
|
|
|
target.heal( calculatedHeal );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
// todo: get proper packets: the following was just kind of thrown together from what we know.
|
|
|
|
// atm buggy (packets look "delayed" from client)
|
|
|
|
|
|
|
|
auto actorsCollided = ActionCollision::getActorsHitFromAction( target.getPos(), getInRangeActors( true ),
|
|
|
|
actionInfoPtr, TargetFilter::Allies );
|
|
|
|
|
|
|
|
for( auto pHitActor : actorsCollided )
|
|
|
|
{
|
|
|
|
effectPacket->setTargetActor( pHitActor->getId() );
|
|
|
|
|
|
|
|
sendToInRangeSet( effectPacket, true );
|
|
|
|
pHitActor->getAsChara()->heal( calculatedHeal );
|
|
|
|
|
|
|
|
// Debug
|
|
|
|
if( isPlayer() )
|
|
|
|
{
|
|
|
|
if( pHitActor->isPlayer() )
|
|
|
|
getAsPlayer()->sendDebug( "AoE hit actor " + std::to_string( pHitActor->getId() ) +
|
|
|
|
" (" + pHitActor->getAsChara()->getName() + ")" );
|
|
|
|
else
|
|
|
|
getAsPlayer()->sendDebug( "AoE hit actor " + std::to_string( pHitActor->getId() ) );
|
|
|
|
}
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
break;
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
default:
|
2018-02-20 22:46:44 +01:00
|
|
|
break;
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param StatusEffectPtr to be applied to the actor */
|
|
|
|
void Core::Entity::Chara::addStatusEffect( StatusEffect::StatusEffectPtr pEffect )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
int8_t nextSlot = getStatusEffectFreeSlot();
|
|
|
|
// if there is no slot left, do not add the effect
|
|
|
|
if( nextSlot == -1 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
pEffect->applyStatus();
|
|
|
|
m_statusEffectMap[ nextSlot ] = pEffect;
|
|
|
|
|
|
|
|
auto statusEffectAdd = makeZonePacket< FFXIVIpcAddStatusEffect >( getId() );
|
|
|
|
|
|
|
|
statusEffectAdd->data().actor_id = pEffect->getTargetActorId();
|
|
|
|
statusEffectAdd->data().actor_id1 = pEffect->getSrcActorId();
|
|
|
|
statusEffectAdd->data().current_hp = getHp();
|
|
|
|
statusEffectAdd->data().current_mp = getMp();
|
|
|
|
statusEffectAdd->data().current_tp = getTp();
|
|
|
|
statusEffectAdd->data().duration = static_cast< float >( pEffect->getDuration() ) / 1000;
|
|
|
|
statusEffectAdd->data().effect_id = pEffect->getId();
|
|
|
|
statusEffectAdd->data().effect_index = nextSlot;
|
|
|
|
statusEffectAdd->data().max_hp = getMaxHp();
|
|
|
|
statusEffectAdd->data().max_mp = getMaxMp();
|
|
|
|
statusEffectAdd->data().max_something = 1;
|
|
|
|
//statusEffectAdd->data().unknown2 = 28;
|
|
|
|
statusEffectAdd->data().param = pEffect->getParam();
|
|
|
|
|
|
|
|
sendToInRangeSet( statusEffectAdd, isPlayer() );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param StatusEffectPtr to be applied to the actor */
|
|
|
|
void Core::Entity::Chara::addStatusEffectById( uint32_t id, int32_t duration, Entity::Chara& source, uint16_t param )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000 );
|
|
|
|
effect->setParam( param );
|
|
|
|
addStatusEffect( effect );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*! \param StatusEffectPtr to be applied to the actor */
|
2018-08-29 21:40:59 +02:00
|
|
|
void Core::Entity::Chara::addStatusEffectByIdIfNotExist( uint32_t id, int32_t duration, Entity::Chara& source,
|
|
|
|
uint16_t param )
|
2018-02-20 22:46:44 +01:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
if( hasStatusEffect( id ) )
|
|
|
|
return;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto effect = StatusEffect::make_StatusEffect( id, source.getAsChara(), getAsChara(), duration, 3000 );
|
|
|
|
effect->setParam( param );
|
|
|
|
addStatusEffect( effect );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int8_t Core::Entity::Chara::getStatusEffectFreeSlot()
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
int8_t freeEffectSlot = -1;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( m_statusEffectFreeSlotQueue.empty() )
|
|
|
|
return freeEffectSlot;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
freeEffectSlot = m_statusEffectFreeSlotQueue.front();
|
|
|
|
m_statusEffectFreeSlotQueue.pop();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
return freeEffectSlot;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Chara::statusEffectFreeSlot( uint8_t slotId )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
m_statusEffectFreeSlotQueue.push( slotId );
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Chara::removeSingleStatusEffectById( uint32_t id )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
for( auto effectIt : m_statusEffectMap )
|
|
|
|
{
|
|
|
|
if( effectIt.second->getId() == id )
|
|
|
|
{
|
|
|
|
removeStatusEffect( effectIt.first );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Chara::removeStatusEffect( uint8_t effectSlotId )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
auto pEffectIt = m_statusEffectMap.find( effectSlotId );
|
|
|
|
if( pEffectIt == m_statusEffectMap.end() )
|
|
|
|
return;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
statusEffectFreeSlot( effectSlotId );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto pEffect = pEffectIt->second;
|
|
|
|
pEffect->removeStatus();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
sendToInRangeSet( makeActorControl142( getId(), StatusEffectLose, pEffect->getId() ), isPlayer() );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
m_statusEffectMap.erase( effectSlotId );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
sendStatusEffectUpdate();
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
std::map< uint8_t, Core::StatusEffect::StatusEffectPtr > Core::Entity::Chara::getStatusEffectMap() const
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_statusEffectMap;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
2018-09-10 23:57:14 +02:00
|
|
|
uint32_t* Core::Entity::Chara::getModels()
|
|
|
|
{
|
|
|
|
return m_modelEquip;
|
|
|
|
}
|
|
|
|
|
2018-02-20 22:46:44 +01:00
|
|
|
void Core::Entity::Chara::sendStatusEffectUpdate()
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
uint64_t currentTimeMs = Util::getTimeMs();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-07-03 00:01:26 +02:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto statusEffectList = makeZonePacket< FFXIVIpcStatusEffectList >( getId() );
|
|
|
|
statusEffectList->data().classId = static_cast< uint8_t >( getClass() );
|
|
|
|
statusEffectList->data().level = getLevel();
|
|
|
|
statusEffectList->data().level1 = getLevel();
|
|
|
|
statusEffectList->data().current_hp = getHp();
|
|
|
|
statusEffectList->data().current_mp = getMp();
|
|
|
|
statusEffectList->data().currentTp = getTp();
|
|
|
|
statusEffectList->data().max_hp = getMaxHp();
|
|
|
|
statusEffectList->data().max_mp = getMaxMp();
|
|
|
|
uint8_t slot = 0;
|
|
|
|
for( auto effectIt : m_statusEffectMap )
|
|
|
|
{
|
|
|
|
float timeLeft = static_cast< float >( effectIt.second->getDuration() -
|
|
|
|
( currentTimeMs - effectIt.second->getStartTimeMs() ) ) / 1000;
|
|
|
|
statusEffectList->data().effect[ slot ].duration = timeLeft;
|
|
|
|
statusEffectList->data().effect[ slot ].effect_id = effectIt.second->getId();
|
|
|
|
statusEffectList->data().effect[ slot ].sourceActorId = effectIt.second->getSrcActorId();
|
|
|
|
slot++;
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
sendToInRangeSet( statusEffectList, isPlayer() );
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Core::Entity::Chara::updateStatusEffects()
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
uint64_t currentTimeMs = Util::getTimeMs();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint32_t thisTickDmg = 0;
|
|
|
|
uint32_t thisTickHeal = 0;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
for( auto effectIt : m_statusEffectMap )
|
|
|
|
{
|
|
|
|
uint8_t effectIndex = effectIt.first;
|
|
|
|
auto effect = effectIt.second;
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
uint64_t lastTick = effect->getLastTickMs();
|
|
|
|
uint64_t startTime = effect->getStartTimeMs();
|
|
|
|
uint32_t duration = effect->getDuration();
|
|
|
|
uint32_t tickRate = effect->getTickRate();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( ( currentTimeMs - startTime ) > duration )
|
|
|
|
{
|
|
|
|
// remove status effect
|
|
|
|
removeStatusEffect( effectIndex );
|
|
|
|
// break because removing invalidates iterators
|
|
|
|
break;
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( ( currentTimeMs - lastTick ) > tickRate )
|
|
|
|
{
|
|
|
|
effect->setLastTick( currentTimeMs );
|
|
|
|
effect->onTick();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
auto thisEffect = effect->getTickEffect();
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
switch( thisEffect.first )
|
|
|
|
{
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
case 1:
|
|
|
|
{
|
|
|
|
thisTickDmg += thisEffect.second;
|
|
|
|
break;
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
case 2:
|
|
|
|
{
|
|
|
|
thisTickHeal += thisEffect.second;
|
|
|
|
break;
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
|
|
|
}
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( thisTickDmg != 0 )
|
|
|
|
{
|
|
|
|
takeDamage( thisTickDmg );
|
|
|
|
sendToInRangeSet( makeActorControl142( getId(), HPFloatingText, 0,
|
|
|
|
static_cast< uint8_t >( ActionEffectType::Damage ), thisTickDmg ) );
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
if( thisTickHeal != 0 )
|
|
|
|
{
|
|
|
|
heal( thisTickDmg );
|
|
|
|
sendToInRangeSet( makeActorControl142( getId(), HPFloatingText, 0,
|
|
|
|
static_cast< uint8_t >( ActionEffectType::Heal ), thisTickHeal ) );
|
|
|
|
}
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Core::Entity::Chara::hasStatusEffect( uint32_t id )
|
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
if( m_statusEffectMap.find( id ) != m_statusEffectMap.end() )
|
|
|
|
return true;
|
|
|
|
return false;
|
2018-02-20 22:46:44 +01:00
|
|
|
}
|
2018-02-22 15:31:10 +01:00
|
|
|
|
2018-08-28 19:05:52 +02:00
|
|
|
Core::Common::ObjKind Chara::getObjKind() const
|
2018-02-22 15:31:10 +01:00
|
|
|
{
|
2018-08-29 21:40:59 +02:00
|
|
|
return m_modelType;
|
2018-02-22 15:31:10 +01:00
|
|
|
}
|