2018-08-29 21:40:59 +02:00
|
|
|
#include <Util/Util.h>
|
|
|
|
#include <Util/UtilMath.h>
|
|
|
|
#include <Network/PacketContainer.h>
|
2021-11-27 00:53:57 +01:00
|
|
|
#include <Exd/ExdData.h>
|
2018-08-29 21:40:59 +02:00
|
|
|
#include <utility>
|
|
|
|
#include <Network/CommonActorControl.h>
|
2018-09-20 23:31:38 +02:00
|
|
|
#include <Network/PacketWrappers/EffectPacket.h>
|
2022-01-06 20:25:50 +01:00
|
|
|
#include <Network/PacketWrappers/EffectPacket1.h>
|
2019-01-17 23:54:47 +01:00
|
|
|
#include <Network/PacketDef/Zone/ClientZoneDef.h>
|
2019-01-23 22:37:55 +01:00
|
|
|
#include <Logging/Logger.h>
|
2018-08-29 21:40:59 +02:00
|
|
|
|
|
|
|
#include "Forwards.h"
|
|
|
|
#include "Action/Action.h"
|
|
|
|
|
2019-07-21 22:33:33 +10:00
|
|
|
#include "Territory/Territory.h"
|
2018-08-29 21:40:59 +02:00
|
|
|
|
|
|
|
#include "Network/GameConnection.h"
|
2019-10-09 18:14:53 +02:00
|
|
|
#include "Network/PacketWrappers/ActorControlPacket.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlSelfPacket.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlTargetPacket.h"
|
2018-08-29 21:40:59 +02:00
|
|
|
#include "Network/PacketWrappers/UpdateHpMpTpPacket.h"
|
2018-09-13 22:14:31 +02:00
|
|
|
#include "Network/PacketWrappers/NpcSpawnPacket.h"
|
2019-01-17 23:54:47 +01:00
|
|
|
#include "Network/PacketWrappers/MoveActorPacket.h"
|
2019-01-24 12:10:31 +01:00
|
|
|
#include "Navi/NaviProvider.h"
|
2018-08-29 21:40:59 +02:00
|
|
|
|
2019-04-24 23:25:07 +10:00
|
|
|
#include "Math/CalcBattle.h"
|
|
|
|
#include "Math/CalcStats.h"
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
#include "WorldServer.h"
|
2018-08-29 21:40:59 +02:00
|
|
|
#include "Session.h"
|
|
|
|
#include "Chara.h"
|
|
|
|
#include "Player.h"
|
2018-08-29 22:03:10 +02:00
|
|
|
#include "BNpc.h"
|
2019-04-24 23:25:07 +10:00
|
|
|
|
2018-08-29 21:40:59 +02:00
|
|
|
#include "Common.h"
|
2019-04-24 23:25:07 +10:00
|
|
|
|
|
|
|
#include <Manager/TerritoryMgr.h>
|
2019-01-23 19:23:49 +01:00
|
|
|
#include <Manager/NaviMgr.h>
|
2019-04-17 22:36:23 +02:00
|
|
|
#include <Manager/RNGMgr.h>
|
2021-11-27 00:53:57 +01:00
|
|
|
#include <Manager/PlayerMgr.h>
|
2022-01-20 22:42:26 +01:00
|
|
|
#include <Manager/TaskMgr.h>
|
2022-01-24 11:06:34 +01:00
|
|
|
#include <Script/ScriptMgr.h>
|
2022-01-20 22:42:26 +01:00
|
|
|
#include <Task/RemoveBNpcTask.h>
|
2022-01-21 00:42:40 +01:00
|
|
|
#include <Task/FadeBNpcTask.h>
|
2022-01-24 02:39:21 -06:00
|
|
|
#include <Task/DelayedEmnityTask.h>
|
2020-03-01 01:00:57 +11:00
|
|
|
#include <Service.h>
|
2018-08-29 21:40:59 +02:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
using namespace Sapphire::Common;
|
|
|
|
using namespace Sapphire::Network::Packets;
|
2021-11-27 00:53:57 +01:00
|
|
|
using namespace Sapphire::Network::Packets::WorldPackets::Server;
|
2018-11-29 16:55:48 +01:00
|
|
|
using namespace Sapphire::Network::ActorControl;
|
2018-08-29 21:40:59 +02:00
|
|
|
|
2020-03-01 01:00:57 +11:00
|
|
|
Sapphire::Entity::BNpc::BNpc() :
|
|
|
|
Npc( ObjKind::BattleNpc )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
Sapphire::Entity::BNpc::BNpc( uint32_t id, std::shared_ptr< Common::BNPCInstanceObject > pInfo, TerritoryPtr pZone ) :
|
2020-03-01 01:00:57 +11:00
|
|
|
Npc( ObjKind::BattleNpc )
|
2018-09-10 23:57:14 +02:00
|
|
|
{
|
2019-01-13 00:51:31 +01:00
|
|
|
m_id = id;
|
2021-11-27 00:53:57 +01:00
|
|
|
m_pInfo = pInfo;
|
2021-12-22 00:40:11 +01:00
|
|
|
m_layoutId = pInfo->instanceId;
|
2021-11-27 00:53:57 +01:00
|
|
|
|
|
|
|
m_aggressionMode = pInfo->ActiveType;
|
|
|
|
|
|
|
|
m_displayFlags = 0;
|
|
|
|
m_weaponMain = 0;
|
|
|
|
m_weaponSub = 0;
|
|
|
|
m_pose = 0;
|
|
|
|
|
|
|
|
m_bNpcNameId = pInfo->NameId;
|
|
|
|
m_bNpcBaseId = pInfo->BaseId;
|
|
|
|
|
|
|
|
m_pos.x = pInfo->x;
|
|
|
|
m_pos.y = pInfo->y;
|
|
|
|
m_pos.z = pInfo->z;
|
|
|
|
m_rot = pInfo->rotation;
|
|
|
|
m_level = pInfo->Level <= 0 ? 1 : pInfo->Level;
|
|
|
|
m_invincibilityType = InvincibilityNone;
|
|
|
|
m_currentStance = Common::Stance::Passive;
|
|
|
|
m_boundInstanceId = pInfo->BoundInstanceID;
|
|
|
|
m_flags = 0;
|
|
|
|
m_rank = pInfo->BNPCRankId;
|
|
|
|
|
|
|
|
if( pInfo->WanderingRange == 0 || pInfo->BoundInstanceID != 0 )
|
|
|
|
setFlag( Immobile );
|
|
|
|
|
2021-12-31 14:57:18 +01:00
|
|
|
// Striking Dummy
|
|
|
|
if( pInfo->NameId == 541 )
|
|
|
|
m_invincibilityType = Common::InvincibilityRefill;
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
|
|
|
|
|
|
auto bNpcBaseData = exdData.getRow< Component::Excel::BNpcBase >( m_bNpcBaseId );
|
|
|
|
if( !bNpcBaseData )
|
|
|
|
{
|
|
|
|
Logger::debug( "BNpcBase#{0} not found in exd data!", m_bNpcBaseId );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_modelChara = bNpcBaseData->data().Model;
|
|
|
|
m_enemyType = bNpcBaseData->data().Battalion;
|
|
|
|
|
2021-12-30 13:57:08 +01:00
|
|
|
m_class = ClassJob::Gladiator;
|
2021-11-27 00:53:57 +01:00
|
|
|
|
2022-01-10 23:50:44 +01:00
|
|
|
m_territoryTypeId = pZone->getTerritoryTypeId();
|
|
|
|
m_territoryId = pZone->getGuId();
|
2021-11-27 00:53:57 +01:00
|
|
|
|
|
|
|
m_spawnPos = m_pos;
|
|
|
|
|
|
|
|
m_timeOfDeath = 0;
|
|
|
|
m_targetId = Common::INVALID_GAME_OBJECT_ID64;
|
|
|
|
|
|
|
|
m_maxHp = 500;
|
|
|
|
m_maxMp = 200;
|
|
|
|
m_hp = m_maxHp;
|
|
|
|
m_mp = 200;
|
|
|
|
|
2021-12-30 13:57:08 +01:00
|
|
|
if( m_level <= BnpcBaseHp.size() )
|
|
|
|
m_maxHp = BnpcBaseHp[ m_level - 1 ];
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
m_state = BNpcState::Idle;
|
|
|
|
m_status = ActorStatus::Idle;
|
|
|
|
|
|
|
|
max_hp = m_maxHp;
|
|
|
|
max_mp = 200;
|
|
|
|
|
|
|
|
memset( m_customize, 0, sizeof( m_customize ) );
|
|
|
|
memset( m_modelEquip, 0, sizeof( m_modelEquip ) );
|
|
|
|
|
|
|
|
|
|
|
|
m_radius = bNpcBaseData->data().Scale;
|
|
|
|
if( bNpcBaseData->data().Customize != 0 )
|
|
|
|
{
|
|
|
|
auto bnpcCustom = exdData.getRow< Component::Excel::BNpcCustomize >( bNpcBaseData->data().Customize );
|
|
|
|
if( bnpcCustom )
|
|
|
|
{
|
|
|
|
memcpy( m_customize, reinterpret_cast< char* >( &bnpcCustom->data() ), sizeof( m_customize ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( bNpcBaseData->data().Equipment != 0 )
|
|
|
|
{
|
|
|
|
auto bnpcEquip = exdData.getRow< Component::Excel::NpcEquip >( bNpcBaseData->data().Equipment );
|
|
|
|
if( bnpcEquip )
|
|
|
|
{
|
|
|
|
m_weaponMain = bnpcEquip->data().WeaponModel;
|
|
|
|
m_weaponSub = bnpcEquip->data().SubWeaponModel;
|
|
|
|
memcpy( m_modelEquip, reinterpret_cast< char* >( bnpcEquip->data().Equip ), sizeof( m_modelEquip ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto modelChara = exdData.getRow< Component::Excel::ModelChara >( bNpcBaseData->data().Model );
|
|
|
|
if( modelChara )
|
|
|
|
{
|
2022-01-04 22:51:29 +01:00
|
|
|
auto modelSkeleton = exdData.getRow< Component::Excel::ModelSkeleton >( modelChara->data().SkeletonId );
|
2021-11-27 00:53:57 +01:00
|
|
|
if( modelSkeleton )
|
2022-01-04 22:51:29 +01:00
|
|
|
{
|
2021-11-27 00:53:57 +01:00
|
|
|
m_radius *= modelSkeleton->data().Radius;
|
2022-01-04 22:51:29 +01:00
|
|
|
}
|
2021-11-27 00:53:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// todo: is this actually good?
|
2022-01-04 22:51:29 +01:00
|
|
|
m_naviTargetReachedDistance = m_radius * 2;
|
2021-11-27 00:53:57 +01:00
|
|
|
|
|
|
|
calculateStats();
|
2021-12-30 13:57:08 +01:00
|
|
|
|
2021-12-31 16:20:11 +01:00
|
|
|
if( m_bnpcType == BNpcType::Friendly )
|
|
|
|
m_maxHp *= 5;
|
|
|
|
|
|
|
|
max_hp = m_maxHp;
|
2021-12-30 13:57:08 +01:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Sapphire::Entity::BNpc::BNpc( uint32_t id, std::shared_ptr< Common::BNPCInstanceObject > pInfo, TerritoryPtr pZone, uint32_t hp, Common::BNpcType type ) :
|
|
|
|
Npc( ObjKind::BattleNpc )
|
|
|
|
{
|
|
|
|
m_id = id;
|
|
|
|
m_pInfo = pInfo;
|
2021-12-22 00:40:11 +01:00
|
|
|
m_layoutId = pInfo->instanceId;
|
2021-11-27 00:53:57 +01:00
|
|
|
|
|
|
|
m_aggressionMode = pInfo->ActiveType;
|
|
|
|
|
|
|
|
m_displayFlags = 0;
|
|
|
|
m_weaponMain = 0;
|
|
|
|
m_weaponSub = 0;
|
|
|
|
m_pose = 0;
|
|
|
|
|
|
|
|
m_bNpcNameId = pInfo->NameId;
|
|
|
|
m_bNpcBaseId = pInfo->BaseId;
|
|
|
|
|
|
|
|
m_pos.x = pInfo->x;
|
|
|
|
m_pos.y = pInfo->y;
|
|
|
|
m_pos.z = pInfo->z;
|
|
|
|
m_rot = pInfo->rotation;
|
|
|
|
m_level = pInfo->Level <= 0 ? 1 : pInfo->Level;
|
2019-01-20 00:20:35 +01:00
|
|
|
m_invincibilityType = InvincibilityNone;
|
2019-01-29 23:54:52 +01:00
|
|
|
m_currentStance = Common::Stance::Passive;
|
2021-11-27 00:53:57 +01:00
|
|
|
m_boundInstanceId = pInfo->BoundInstanceID;
|
2019-04-22 00:16:39 +02:00
|
|
|
m_flags = 0;
|
2021-11-27 00:53:57 +01:00
|
|
|
m_rank = pInfo->BNPCRankId;
|
|
|
|
|
2022-01-10 23:50:44 +01:00
|
|
|
m_territoryTypeId = pZone->getTerritoryTypeId();
|
|
|
|
m_territoryId = pZone->getGuId();
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
if( pInfo->WanderingRange == 0 || pInfo->BoundInstanceID != 0 )
|
|
|
|
setFlag( Immobile );
|
|
|
|
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
|
|
|
|
|
|
auto bNpcBaseData = exdData.getRow< Component::Excel::BNpcBase >( m_bNpcBaseId );
|
|
|
|
if( !bNpcBaseData )
|
|
|
|
{
|
|
|
|
Logger::debug( "BNpcBase#{0} not found in exd data!", m_bNpcBaseId );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_modelChara = bNpcBaseData->data().Model;
|
|
|
|
m_enemyType = bNpcBaseData->data().Battalion;
|
2018-09-13 22:14:31 +02:00
|
|
|
|
2021-12-30 13:57:08 +01:00
|
|
|
m_class = ClassJob::Gladiator;
|
2019-04-24 23:25:07 +10:00
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
m_spawnPos = m_pos;
|
|
|
|
|
2019-01-23 22:37:55 +01:00
|
|
|
m_timeOfDeath = 0;
|
2019-01-29 23:54:52 +01:00
|
|
|
m_targetId = Common::INVALID_GAME_OBJECT_ID64;
|
2019-01-23 22:37:55 +01:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
m_maxHp = hp;
|
2018-09-13 22:14:31 +02:00
|
|
|
m_maxMp = 200;
|
2021-11-27 00:53:57 +01:00
|
|
|
m_hp = m_maxHp;
|
2018-09-13 22:14:31 +02:00
|
|
|
m_mp = 200;
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
m_state = BNpcState::Idle;
|
2019-01-20 23:49:43 +01:00
|
|
|
m_status = ActorStatus::Idle;
|
2019-01-17 23:54:47 +01:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
max_hp = hp;
|
|
|
|
max_mp = 200;
|
2018-09-10 23:57:14 +02:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
m_bnpcType = type;
|
2018-09-10 23:57:14 +02:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
memset( m_customize, 0, sizeof( m_customize ) );
|
|
|
|
memset( m_modelEquip, 0, sizeof( m_modelEquip ) );
|
2019-01-31 22:49:04 +11:00
|
|
|
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
m_radius = bNpcBaseData->data().Scale;
|
|
|
|
if( bNpcBaseData->data().Customize != 0 )
|
|
|
|
{
|
|
|
|
auto bnpcCustom = exdData.getRow< Component::Excel::BNpcCustomize >( bNpcBaseData->data().Customize );
|
|
|
|
if( bnpcCustom )
|
|
|
|
{
|
|
|
|
memcpy( m_customize, reinterpret_cast< char* >( &bnpcCustom->data() ), sizeof( m_customize ) );
|
|
|
|
}
|
|
|
|
}
|
2019-01-31 22:49:04 +11:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
if( bNpcBaseData->data().Equipment != 0 )
|
|
|
|
{
|
|
|
|
auto bnpcEquip = exdData.getRow< Component::Excel::NpcEquip >( bNpcBaseData->data().Equipment );
|
|
|
|
if( bnpcEquip )
|
|
|
|
{
|
|
|
|
m_weaponMain = bnpcEquip->data().WeaponModel;
|
|
|
|
m_weaponSub = bnpcEquip->data().SubWeaponModel;
|
|
|
|
memcpy( m_modelEquip, reinterpret_cast< char* >( bnpcEquip->data().Equip ), sizeof( m_modelEquip ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto modelChara = exdData.getRow< Component::Excel::ModelChara >( bNpcBaseData->data().Model );
|
2019-04-19 23:01:27 +10:00
|
|
|
if( modelChara )
|
|
|
|
{
|
2021-11-27 00:53:57 +01:00
|
|
|
auto modelSkeleton = exdData.getRow< Component::Excel::ModelSkeleton >( modelChara->data().ModelType );
|
2019-04-19 23:01:27 +10:00
|
|
|
if( modelSkeleton )
|
2021-11-27 00:53:57 +01:00
|
|
|
m_radius *= modelSkeleton->data().Radius;
|
2019-04-19 23:01:27 +10:00
|
|
|
}
|
|
|
|
|
2019-01-31 22:49:04 +11:00
|
|
|
// todo: is this actually good?
|
2022-01-04 22:51:29 +01:00
|
|
|
m_naviTargetReachedDistance = m_radius;
|
2019-04-24 23:25:07 +10:00
|
|
|
|
|
|
|
calculateStats();
|
2018-08-29 21:40:59 +02:00
|
|
|
}
|
2018-09-09 23:56:22 +02:00
|
|
|
|
2019-01-31 22:49:04 +11:00
|
|
|
Sapphire::Entity::BNpc::~BNpc() = default;
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint8_t Sapphire::Entity::BNpc::getAggressionMode() const
|
2018-09-13 22:14:31 +02:00
|
|
|
{
|
|
|
|
return m_aggressionMode;
|
|
|
|
}
|
|
|
|
|
2019-01-31 22:49:04 +11:00
|
|
|
float Sapphire::Entity::BNpc::getNaviTargetReachedDistance() const
|
|
|
|
{
|
|
|
|
return m_naviTargetReachedDistance;
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint8_t Sapphire::Entity::BNpc::getEnemyType() const
|
2018-09-13 22:14:31 +02:00
|
|
|
{
|
|
|
|
return m_enemyType;
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint64_t Sapphire::Entity::BNpc::getWeaponMain() const
|
2018-09-09 23:56:22 +02:00
|
|
|
{
|
2018-09-13 22:14:31 +02:00
|
|
|
return m_weaponMain;
|
|
|
|
}
|
2018-09-09 23:56:22 +02:00
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint64_t Sapphire::Entity::BNpc::getWeaponSub() const
|
2018-09-13 22:14:31 +02:00
|
|
|
{
|
|
|
|
return m_weaponSub;
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint16_t Sapphire::Entity::BNpc::getModelChara() const
|
2018-09-13 22:14:31 +02:00
|
|
|
{
|
|
|
|
return m_modelChara;
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint8_t Sapphire::Entity::BNpc::getLevel() const
|
2018-09-13 22:14:31 +02:00
|
|
|
{
|
|
|
|
return m_level;
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint32_t Sapphire::Entity::BNpc::getBNpcBaseId() const
|
2018-09-13 22:14:31 +02:00
|
|
|
{
|
|
|
|
return m_bNpcBaseId;
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
uint32_t Sapphire::Entity::BNpc::getBNpcNameId() const
|
2018-09-13 22:14:31 +02:00
|
|
|
{
|
|
|
|
return m_bNpcNameId;
|
|
|
|
}
|
|
|
|
|
2018-11-29 16:55:48 +01:00
|
|
|
void Sapphire::Entity::BNpc::spawn( PlayerPtr pTarget )
|
2018-09-13 22:14:31 +02:00
|
|
|
{
|
2019-04-17 00:10:32 +02:00
|
|
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
2021-11-27 00:53:57 +01:00
|
|
|
|
|
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
|
|
server.queueForPlayer( pTarget->getCharacterId(), std::make_shared< NpcSpawnPacket >( *this, *pTarget ) );
|
2018-09-26 03:32:43 -04:00
|
|
|
}
|
2019-01-17 23:54:47 +01:00
|
|
|
|
2019-01-19 17:50:33 +11:00
|
|
|
void Sapphire::Entity::BNpc::despawn( PlayerPtr pTarget )
|
|
|
|
{
|
|
|
|
pTarget->freePlayerSpawnId( getId() );
|
2021-11-27 00:53:57 +01:00
|
|
|
|
|
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
|
|
|
2022-01-07 21:22:55 +01:00
|
|
|
server.queueForPlayer( pTarget->getCharacterId(), makeActorControlSelf( m_id, WarpStart, 0x04, getId(), 0x01 ) );
|
2019-01-19 17:50:33 +11:00
|
|
|
}
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
Sapphire::Entity::BNpcState Sapphire::Entity::BNpc::getState() const
|
|
|
|
{
|
|
|
|
return m_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::setState( BNpcState state )
|
|
|
|
{
|
|
|
|
m_state = state;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos )
|
|
|
|
{
|
2022-01-24 09:20:34 +01:00
|
|
|
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
|
2022-01-10 23:50:44 +01:00
|
|
|
auto pZone = teriMgr.getTerritoryByGuId( getTerritoryId() );
|
2019-01-23 17:07:40 +01:00
|
|
|
|
2022-01-10 23:50:44 +01:00
|
|
|
auto pNaviProvider = pZone->getNaviProvider();
|
2019-01-27 23:26:34 +11:00
|
|
|
|
|
|
|
if( !pNaviProvider )
|
2019-01-21 15:15:28 +01:00
|
|
|
{
|
2022-01-10 23:50:44 +01:00
|
|
|
Logger::error( "No NaviProvider for zone#{0} - {1}", pZone->getGuId(), pZone->getInternalName() );
|
2019-01-27 23:26:34 +11:00
|
|
|
return false;
|
|
|
|
}
|
2019-01-23 19:23:49 +01:00
|
|
|
|
2019-04-19 00:39:42 +02:00
|
|
|
auto pos1 = pNaviProvider->getMovePos( *this );
|
2022-01-06 20:25:50 +01:00
|
|
|
auto distance = Util::distance( pos1, pos );
|
2019-04-19 00:39:42 +02:00
|
|
|
|
2022-01-06 20:25:50 +01:00
|
|
|
if( distance < getNaviTargetReachedDistance() )
|
2019-04-19 12:15:09 +02:00
|
|
|
{
|
|
|
|
// Reached destination
|
2019-04-20 15:13:46 +10:00
|
|
|
face( pos );
|
2019-04-19 14:04:38 +02:00
|
|
|
setPos( pos1 );
|
|
|
|
sendPositionUpdate();
|
|
|
|
pNaviProvider->updateAgentPosition( *this );
|
2019-04-19 12:15:09 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-04-19 02:15:18 +02:00
|
|
|
|
2022-01-10 23:50:44 +01:00
|
|
|
pZone->updateActorPosition( *this );
|
2019-04-20 15:13:46 +10:00
|
|
|
face( pos );
|
2022-01-06 20:25:50 +01:00
|
|
|
if( distance > 2.0f )
|
|
|
|
face( { ( pos.x - pos1.x ) + pos.x, 1.0f, ( pos.z - pos1.z ) + pos.z } );
|
|
|
|
else
|
|
|
|
face( pos );
|
2019-04-19 02:15:18 +02:00
|
|
|
setPos( pos1 );
|
|
|
|
sendPositionUpdate();
|
2019-01-17 23:54:47 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-04-20 00:11:00 +02:00
|
|
|
bool Sapphire::Entity::BNpc::moveTo( const Entity::Chara& targetChara )
|
|
|
|
{
|
|
|
|
|
2022-01-24 09:20:34 +01:00
|
|
|
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
|
2022-01-10 23:50:44 +01:00
|
|
|
auto pZone = teriMgr.getTerritoryByGuId( getTerritoryId() );
|
|
|
|
|
|
|
|
auto pNaviProvider = pZone->getNaviProvider();
|
2019-04-20 00:11:00 +02:00
|
|
|
|
|
|
|
if( !pNaviProvider )
|
|
|
|
{
|
2022-01-10 23:50:44 +01:00
|
|
|
Logger::error( "No NaviProvider for zone#{0} - {1}", pZone->getGuId(), pZone->getInternalName() );
|
2019-04-20 00:11:00 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto pos1 = pNaviProvider->getMovePos( *this );
|
2022-01-06 20:25:50 +01:00
|
|
|
auto distance = Util::distance( pos1, targetChara.getPos() );
|
2019-04-20 00:11:00 +02:00
|
|
|
|
2022-01-06 20:25:50 +01:00
|
|
|
if( distance <= ( getNaviTargetReachedDistance() + targetChara.getRadius() ) )
|
2019-04-20 00:11:00 +02:00
|
|
|
{
|
|
|
|
// Reached destination
|
2019-04-20 15:13:46 +10:00
|
|
|
face( targetChara.getPos() );
|
2019-04-20 00:11:00 +02:00
|
|
|
setPos( pos1 );
|
|
|
|
sendPositionUpdate();
|
2022-01-04 22:51:29 +01:00
|
|
|
pNaviProvider->resetMoveTarget( *this );
|
2019-04-20 00:11:00 +02:00
|
|
|
pNaviProvider->updateAgentPosition( *this );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-01-10 23:50:44 +01:00
|
|
|
pZone->updateActorPosition( *this );
|
2022-01-06 20:25:50 +01:00
|
|
|
if( distance > 2.0f )
|
|
|
|
face( { ( pos1.x - getPos().x ) + pos1.x, 1.0f, ( pos1.z - getPos().z ) + pos1.z } );
|
|
|
|
else
|
|
|
|
face( targetChara.getPos() );
|
2019-04-20 00:11:00 +02:00
|
|
|
setPos( pos1 );
|
|
|
|
sendPositionUpdate();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
void Sapphire::Entity::BNpc::sendPositionUpdate()
|
|
|
|
{
|
2019-01-26 11:39:25 +11:00
|
|
|
uint8_t animationType = 2;
|
|
|
|
|
2019-01-27 01:12:31 +01:00
|
|
|
if( m_state == BNpcState::Combat || m_state == BNpcState::Retreat )
|
2019-01-26 11:39:25 +11:00
|
|
|
animationType = 0;
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
auto movePacket = std::make_shared< MoveActorPacket >( *getAsChara(), 0x3A, animationType, 0, 0x5A / 4 );
|
2019-01-17 23:54:47 +01:00
|
|
|
sendToInRangeSet( movePacket );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::hateListClear()
|
|
|
|
{
|
2019-04-24 23:25:07 +10:00
|
|
|
for( auto& listEntry : m_hateList )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
if( isInRangeSet( listEntry->m_pChara ) )
|
|
|
|
deaggro( listEntry->m_pChara );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
m_hateList.clear();
|
|
|
|
}
|
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
Sapphire::Entity::CharaPtr Sapphire::Entity::BNpc::hateListGetHighest()
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
auto it = m_hateList.begin();
|
|
|
|
uint32_t maxHate = 0;
|
|
|
|
std::shared_ptr< HateListEntry > entry;
|
|
|
|
for( ; it != m_hateList.end(); ++it )
|
|
|
|
{
|
|
|
|
if( ( *it )->m_hateAmount > maxHate )
|
|
|
|
{
|
|
|
|
maxHate = ( *it )->m_hateAmount;
|
|
|
|
entry = *it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( entry && maxHate != 0 )
|
2019-01-19 01:15:17 +01:00
|
|
|
return entry->m_pChara;
|
2019-01-17 23:54:47 +01:00
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2022-01-19 17:55:29 +01:00
|
|
|
void Sapphire::Entity::BNpc::hateListAdd( const Sapphire::Entity::CharaPtr& pChara, int32_t hateAmount )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
auto hateEntry = std::make_shared< HateListEntry >();
|
2019-12-26 11:09:15 +03:00
|
|
|
hateEntry->m_hateAmount = static_cast< uint32_t >( hateAmount );
|
2019-01-19 01:15:17 +01:00
|
|
|
hateEntry->m_pChara = pChara;
|
2019-01-17 23:54:47 +01:00
|
|
|
|
|
|
|
m_hateList.insert( hateEntry );
|
2019-04-17 08:34:58 +02:00
|
|
|
if( pChara->isPlayer() )
|
|
|
|
{
|
|
|
|
auto pPlayer = pChara->getAsPlayer();
|
2021-11-27 00:53:57 +01:00
|
|
|
pPlayer->hateListAdd( *this );
|
2019-04-17 08:34:58 +02:00
|
|
|
}
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
|
2022-01-24 02:39:21 -06:00
|
|
|
void Sapphire::Entity::BNpc::hateListAddDelayed( const Sapphire::Entity::CharaPtr& pChara, int32_t hateAmount )
|
|
|
|
{
|
|
|
|
auto& taskMgr = Common::Service< World::Manager::TaskMgr >::ref();
|
|
|
|
auto delayedEmnityTask = std::make_shared< Sapphire::World::DelayedEmnityTask >( 5000, getAsBNpc(), pChara, hateAmount );
|
|
|
|
taskMgr.queueTask( delayedEmnityTask );
|
|
|
|
}
|
|
|
|
|
2022-01-19 17:55:29 +01:00
|
|
|
void Sapphire::Entity::BNpc::hateListUpdate( const Sapphire::Entity::CharaPtr& pChara, int32_t hateAmount )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2022-01-19 17:55:29 +01:00
|
|
|
for( const auto& listEntry : m_hateList )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
if( listEntry->m_pChara == pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-12-26 11:09:15 +03:00
|
|
|
listEntry->m_hateAmount += static_cast< uint32_t >( hateAmount );
|
2019-01-17 23:54:47 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto hateEntry = std::make_shared< HateListEntry >();
|
2019-12-26 11:09:15 +03:00
|
|
|
hateEntry->m_hateAmount = static_cast< uint32_t >( hateAmount );
|
2019-01-19 01:15:17 +01:00
|
|
|
hateEntry->m_pChara = pChara;
|
2019-01-17 23:54:47 +01:00
|
|
|
m_hateList.insert( hateEntry );
|
|
|
|
}
|
|
|
|
|
2022-01-19 17:55:29 +01:00
|
|
|
void Sapphire::Entity::BNpc::hateListRemove( const Sapphire::Entity::CharaPtr& pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2022-01-19 17:55:29 +01:00
|
|
|
for( const auto& listEntry : m_hateList )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
if( listEntry->m_pChara == pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
|
|
|
|
m_hateList.erase( listEntry );
|
2019-01-19 01:15:17 +01:00
|
|
|
if( pChara->isPlayer() )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
2021-11-27 00:53:57 +01:00
|
|
|
tmpPlayer->onMobDeaggro( *this );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-18 08:03:49 +01:00
|
|
|
uint32_t Sapphire::Entity::BNpc::getTriggerOwnerId() const
|
|
|
|
{
|
|
|
|
return m_triggerOwnerId;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::setTriggerOwnerId( uint32_t triggerOwnerId )
|
|
|
|
{
|
|
|
|
m_triggerOwnerId = triggerOwnerId;
|
|
|
|
}
|
|
|
|
|
2022-01-19 17:55:29 +01:00
|
|
|
bool Sapphire::Entity::BNpc::hateListHasActor( const Sapphire::Entity::CharaPtr& pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2022-01-21 08:41:48 +01:00
|
|
|
return std::any_of( m_hateList.begin(), m_hateList.end(),
|
|
|
|
[ pChara ]( const auto& entry ) { return entry->m_pChara == pChara; } );
|
2019-01-17 23:54:47 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-01-19 17:55:29 +01:00
|
|
|
void Sapphire::Entity::BNpc::aggro( const Sapphire::Entity::CharaPtr& pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2020-03-01 01:00:57 +11:00
|
|
|
auto& pRNGMgr = Common::Service< World::Manager::RNGMgr >::ref();
|
|
|
|
auto variation = static_cast< uint32_t >( pRNGMgr.getRandGenerator< float >( 500, 1000 ).next() );
|
2019-04-17 22:36:23 +02:00
|
|
|
|
|
|
|
m_lastAttack = Util::getTimeMs() + variation;
|
2019-01-19 01:15:17 +01:00
|
|
|
hateListUpdate( pChara, 1 );
|
2019-01-17 23:54:47 +01:00
|
|
|
|
|
|
|
setStance( Stance::Active );
|
|
|
|
m_state = BNpcState::Combat;
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
sendToInRangeSet( makeActorControl( getId(), ActorControlType::SetBattle, 1, 0, 0 ) );
|
2019-03-29 17:12:22 +01:00
|
|
|
|
2022-01-06 20:25:50 +01:00
|
|
|
changeTarget( pChara->getId() );
|
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
if( pChara->isPlayer() )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
2021-11-27 00:53:57 +01:00
|
|
|
tmpPlayer->onMobAggro( *getAsBNpc() );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
2019-03-29 17:12:22 +01:00
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
|
2022-01-19 17:55:29 +01:00
|
|
|
void Sapphire::Entity::BNpc::deaggro( const Sapphire::Entity::CharaPtr& pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
if( !hateListHasActor( pChara ) )
|
|
|
|
hateListRemove( pChara );
|
2019-01-17 23:54:47 +01:00
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
if( pChara->isPlayer() )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
2019-10-09 18:42:25 +02:00
|
|
|
sendToInRangeSet( makeActorControl( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) );
|
2021-11-27 00:53:57 +01:00
|
|
|
sendToInRangeSet( makeActorControl( getId(), ActorControlType::SetBattle, 0, 0, 0 ) );
|
|
|
|
tmpPlayer->onMobDeaggro( *this );
|
2022-01-24 11:06:34 +01:00
|
|
|
|
|
|
|
if( getTriggerOwnerId() == pChara->getId() )
|
|
|
|
{
|
2022-01-25 17:08:11 +01:00
|
|
|
auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref();
|
2022-01-27 21:08:43 +01:00
|
|
|
auto bnpc = *getAsBNpc();
|
|
|
|
scriptMgr.onTriggerOwnerDeaggro( *tmpPlayer, bnpc );
|
2022-01-24 11:06:34 +01:00
|
|
|
}
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-28 19:16:44 +11:00
|
|
|
void Sapphire::Entity::BNpc::onTick()
|
|
|
|
{
|
2020-01-19 21:20:01 +09:00
|
|
|
Chara::onTick();
|
2019-01-28 19:16:44 +11:00
|
|
|
if( m_state == BNpcState::Retreat )
|
|
|
|
{
|
|
|
|
regainHp();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-04 23:29:52 +02:00
|
|
|
void Sapphire::Entity::BNpc::update( uint64_t tickCount )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2022-01-24 09:20:34 +01:00
|
|
|
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
|
2022-01-10 23:50:44 +01:00
|
|
|
auto pZone = teriMgr.getTerritoryByGuId( getTerritoryId() );
|
|
|
|
|
2019-01-23 21:10:53 +01:00
|
|
|
const uint8_t maxDistanceToOrigin = 40;
|
2019-01-26 00:06:24 +01:00
|
|
|
const uint32_t roamTick = 20;
|
2019-01-19 01:15:17 +01:00
|
|
|
|
2022-01-10 23:50:44 +01:00
|
|
|
auto pNaviProvider = pZone->getNaviProvider();
|
2019-01-27 01:12:31 +01:00
|
|
|
|
2019-04-30 21:41:10 +10:00
|
|
|
if( !pNaviProvider )
|
|
|
|
return;
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
switch( m_state )
|
|
|
|
{
|
2019-01-23 22:37:55 +01:00
|
|
|
case BNpcState::Dead:
|
|
|
|
case BNpcState::JustDied:
|
2019-01-21 02:42:47 +01:00
|
|
|
return;
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
case BNpcState::Retreat:
|
|
|
|
{
|
2019-01-26 13:40:02 +11:00
|
|
|
setInvincibilityType( InvincibilityType::InvincibilityIgnoreDamage );
|
|
|
|
|
2019-04-19 14:50:00 +02:00
|
|
|
if( pNaviProvider )
|
|
|
|
pNaviProvider->setMoveTarget( *this, m_spawnPos );
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
if( moveTo( m_spawnPos ) )
|
2019-01-26 13:40:02 +11:00
|
|
|
{
|
|
|
|
setInvincibilityType( InvincibilityType::InvincibilityNone );
|
|
|
|
|
|
|
|
// retail doesn't seem to roam straight after retreating
|
|
|
|
// todo: perhaps requires more investigation?
|
|
|
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
|
|
|
|
2019-01-27 01:12:31 +01:00
|
|
|
// resetHp
|
2019-01-26 13:40:02 +11:00
|
|
|
setHp( getMaxHp() );
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
m_state = BNpcState::Idle;
|
2019-03-28 23:57:58 +01:00
|
|
|
setOwner( nullptr );
|
2019-01-26 13:40:02 +11:00
|
|
|
}
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-01-26 00:06:24 +01:00
|
|
|
case BNpcState::Roaming:
|
|
|
|
{
|
2019-04-19 00:39:42 +02:00
|
|
|
|
2019-04-19 02:15:18 +02:00
|
|
|
if( pNaviProvider )
|
2019-04-20 00:11:00 +02:00
|
|
|
pNaviProvider->setMoveTarget( *this, m_roamPos );
|
2019-04-19 00:39:42 +02:00
|
|
|
|
2019-01-26 00:06:24 +01:00
|
|
|
if( moveTo( m_roamPos ) )
|
|
|
|
{
|
|
|
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
|
|
|
m_state = BNpcState::Idle;
|
|
|
|
}
|
|
|
|
|
2019-01-31 17:53:20 +11:00
|
|
|
checkAggro();
|
2019-01-26 00:06:24 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
case BNpcState::Idle:
|
|
|
|
{
|
2019-04-17 00:10:32 +02:00
|
|
|
auto pHatedActor = hateListGetHighest();
|
|
|
|
if( pHatedActor )
|
|
|
|
aggro( pHatedActor );
|
|
|
|
|
2019-04-19 14:19:14 +02:00
|
|
|
if( pNaviProvider->syncPosToChara( *this ) )
|
|
|
|
sendPositionUpdate();
|
|
|
|
|
2019-04-17 12:37:24 +02:00
|
|
|
if( !hasFlag( Immobile ) && ( Util::getTimeSeconds() - m_lastRoamTargetReached > roamTick ) )
|
2019-01-26 00:06:24 +01:00
|
|
|
{
|
2019-01-26 00:12:54 +01:00
|
|
|
|
|
|
|
if( !pNaviProvider )
|
|
|
|
{
|
|
|
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
|
|
|
break;
|
|
|
|
}
|
2021-11-27 00:53:57 +01:00
|
|
|
if( m_pInfo->WanderingRange != 0 && getEnemyType() != 0 )
|
|
|
|
{
|
|
|
|
m_roamPos = pNaviProvider->findRandomPositionInCircle( m_spawnPos, m_pInfo->WanderingRange );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_roamPos = m_spawnPos;
|
|
|
|
}
|
2019-01-26 00:06:24 +01:00
|
|
|
m_state = BNpcState::Roaming;
|
|
|
|
}
|
|
|
|
|
2019-01-31 17:53:20 +11:00
|
|
|
checkAggro();
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
case BNpcState::Combat:
|
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
auto pHatedActor = hateListGetHighest();
|
|
|
|
if( !pHatedActor )
|
2019-01-17 23:54:47 +01:00
|
|
|
return;
|
|
|
|
|
2019-04-22 23:30:43 +02:00
|
|
|
pNaviProvider->updateAgentParameters( *this );
|
|
|
|
|
2022-01-04 22:51:29 +01:00
|
|
|
auto distanceOrig = Util::distance( getPos().x, getPos().y, getPos().z, m_spawnPos.x, m_spawnPos.y, m_spawnPos.z );
|
2019-01-17 23:54:47 +01:00
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
if( pHatedActor && !pHatedActor->isAlive() )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
hateListRemove( pHatedActor );
|
|
|
|
pHatedActor = hateListGetHighest();
|
2019-01-21 02:42:47 +01:00
|
|
|
}
|
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
if( pHatedActor )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
|
2019-04-20 15:42:48 +02:00
|
|
|
pHatedActor->getPos().x, pHatedActor->getPos().y, pHatedActor->getPos().z );
|
2019-01-17 23:54:47 +01:00
|
|
|
|
2021-12-31 16:20:11 +01:00
|
|
|
if( !hasFlag( NoDeaggro ) && ( ( distanceOrig > maxDistanceToOrigin ) || distance > 30.0f ) )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
hateListClear();
|
2019-01-29 00:10:21 +01:00
|
|
|
changeTarget( INVALID_GAME_OBJECT_ID64 );
|
2019-01-17 23:54:47 +01:00
|
|
|
setStance( Stance::Passive );
|
2019-03-28 23:57:58 +01:00
|
|
|
setOwner( nullptr );
|
2019-01-17 23:54:47 +01:00
|
|
|
m_state = BNpcState::Retreat;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-01-04 22:51:29 +01:00
|
|
|
if( distance > ( getNaviTargetReachedDistance() + pHatedActor->getRadius() ) )
|
2019-02-06 18:51:51 +01:00
|
|
|
{
|
2019-04-20 00:11:00 +02:00
|
|
|
if( hasFlag( Immobile ) )
|
|
|
|
break;
|
|
|
|
|
2019-04-19 14:50:00 +02:00
|
|
|
if( pNaviProvider )
|
|
|
|
pNaviProvider->setMoveTarget( *this, pHatedActor->getPos() );
|
2019-04-22 00:16:39 +02:00
|
|
|
|
2019-04-20 00:11:00 +02:00
|
|
|
moveTo( *pHatedActor );
|
2019-02-06 18:51:51 +01:00
|
|
|
}
|
2019-04-20 23:15:58 +02:00
|
|
|
|
2022-01-04 22:51:29 +01:00
|
|
|
if( pNaviProvider->syncPosToChara( *this ) )
|
|
|
|
sendPositionUpdate();
|
|
|
|
|
|
|
|
if( distance < ( getNaviTargetReachedDistance() + pHatedActor->getRadius() ) )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-04-17 12:37:24 +02:00
|
|
|
if( !hasFlag( TurningDisabled ) && face( pHatedActor->getPos() ) )
|
2019-01-17 23:54:47 +01:00
|
|
|
sendPositionUpdate();
|
2019-04-21 23:52:41 +10:00
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
// in combat range. ATTACK!
|
2019-01-19 01:15:17 +01:00
|
|
|
autoAttack( pHatedActor );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
}
|
2019-01-21 02:42:47 +01:00
|
|
|
else
|
|
|
|
{
|
2019-01-29 00:10:21 +01:00
|
|
|
changeTarget( INVALID_GAME_OBJECT_ID64 );
|
2019-01-17 23:54:47 +01:00
|
|
|
setStance( Stance::Passive );
|
|
|
|
//setOwner( nullptr );
|
|
|
|
m_state = BNpcState::Retreat;
|
2019-04-22 23:30:43 +02:00
|
|
|
pNaviProvider->updateAgentParameters( *this );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-01-28 13:40:03 +11:00
|
|
|
|
2019-01-28 19:16:44 +11:00
|
|
|
|
2019-04-04 23:29:52 +02:00
|
|
|
Chara::update( tickCount );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
2019-01-20 13:36:34 +01:00
|
|
|
|
2019-01-28 19:16:44 +11:00
|
|
|
void Sapphire::Entity::BNpc::regainHp()
|
2019-01-27 01:12:31 +01:00
|
|
|
{
|
|
|
|
if( this->m_hp < this->getMaxHp() )
|
|
|
|
{
|
2022-01-21 08:41:48 +01:00
|
|
|
auto addHp = static_cast< uint32_t >( getMaxHp() * 0.1f + 1 );
|
2019-01-27 01:12:31 +01:00
|
|
|
|
|
|
|
if( this->m_hp + addHp < this->getMaxHp() )
|
|
|
|
this->m_hp += addHp;
|
|
|
|
else
|
|
|
|
this->m_hp = this->getMaxHp();
|
|
|
|
}
|
|
|
|
|
|
|
|
this->sendStatusUpdate();
|
|
|
|
}
|
|
|
|
|
2019-01-20 13:36:34 +01:00
|
|
|
void Sapphire::Entity::BNpc::onActionHostile( Sapphire::Entity::CharaPtr pSource )
|
|
|
|
{
|
|
|
|
if( !hateListGetHighest() )
|
|
|
|
aggro( pSource );
|
|
|
|
|
2019-03-28 23:57:58 +01:00
|
|
|
if( !m_pOwner )
|
|
|
|
setOwner( pSource );
|
2019-01-20 13:36:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::onDeath()
|
|
|
|
{
|
2021-11-27 00:53:57 +01:00
|
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
2022-01-21 08:41:48 +01:00
|
|
|
auto& playerMgr = Common::Service< World::Manager::PlayerMgr >::ref();
|
2019-01-30 23:48:09 +01:00
|
|
|
setTargetId( INVALID_GAME_OBJECT_ID64 );
|
2019-01-20 13:36:34 +01:00
|
|
|
m_currentStance = Stance::Passive;
|
|
|
|
m_state = BNpcState::Dead;
|
2019-01-23 22:37:55 +01:00
|
|
|
m_timeOfDeath = Util::getTimeSeconds();
|
2019-03-28 23:57:58 +01:00
|
|
|
setOwner( nullptr );
|
2019-01-29 08:02:39 +01:00
|
|
|
|
2022-01-20 22:42:26 +01:00
|
|
|
auto& taskMgr = Common::Service< World::Manager::TaskMgr >::ref();
|
2022-01-21 08:41:48 +01:00
|
|
|
taskMgr.queueTask( World::makeFadeBNpcTask( 10000, getAsBNpc() ) );
|
|
|
|
taskMgr.queueTask( World::makeRemoveBNpcTask( 12000, getAsBNpc() ) );
|
2022-01-19 17:22:56 +01:00
|
|
|
|
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
|
|
|
auto paramGrowthInfo = exdData.getRow< Component::Excel::ParamGrow >( m_level );
|
|
|
|
|
2019-01-29 08:02:39 +01:00
|
|
|
for( auto& pHateEntry : m_hateList )
|
|
|
|
{
|
|
|
|
// TODO: handle drops
|
|
|
|
auto pPlayer = pHateEntry->m_pChara->getAsPlayer();
|
|
|
|
if( pPlayer )
|
2021-11-27 00:53:57 +01:00
|
|
|
{
|
2021-12-22 00:40:11 +01:00
|
|
|
playerMgr.onMobKill( *pPlayer, static_cast< uint16_t >( m_bNpcNameId ), getLayoutId() );
|
2022-01-19 17:22:56 +01:00
|
|
|
pPlayer->gainExp( paramGrowthInfo->data().BaseExp );
|
2021-11-27 00:53:57 +01:00
|
|
|
}
|
2019-01-29 08:02:39 +01:00
|
|
|
}
|
2019-01-20 13:36:34 +01:00
|
|
|
hateListClear();
|
|
|
|
}
|
2019-01-23 22:37:55 +01:00
|
|
|
|
|
|
|
uint32_t Sapphire::Entity::BNpc::getTimeOfDeath() const
|
|
|
|
{
|
|
|
|
return m_timeOfDeath;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::setTimeOfDeath( uint32_t timeOfDeath )
|
|
|
|
{
|
|
|
|
m_timeOfDeath = timeOfDeath;
|
|
|
|
}
|
2019-01-27 01:12:31 +01:00
|
|
|
|
2019-01-31 17:53:20 +11:00
|
|
|
void Sapphire::Entity::BNpc::checkAggro()
|
2019-01-27 01:12:31 +01:00
|
|
|
{
|
|
|
|
// passive mobs should ignore players unless aggro'd
|
|
|
|
if( m_aggressionMode == 1 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
CharaPtr pClosestChara = getClosestChara();
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
if( pClosestChara && pClosestChara->isAlive() && ( getEnemyType() != 0 && pClosestChara->isPlayer() ) )
|
|
|
|
{
|
|
|
|
|
|
|
|
// will use this range if chara level is lower than bnpc, otherwise diminishing equation applies
|
|
|
|
float range = 14.f;
|
|
|
|
|
|
|
|
if( pClosestChara->getLevel() > m_level )
|
|
|
|
{
|
|
|
|
auto levelDiff = std::abs( pClosestChara->getLevel() - this->getLevel() );
|
|
|
|
|
|
|
|
if( levelDiff >= 10 )
|
|
|
|
range = 0.f;
|
|
|
|
else
|
|
|
|
range = std::max< float >( 0.f, range - std::pow( 1.53f, levelDiff * 0.6f ) );
|
|
|
|
}
|
|
|
|
|
2022-01-21 08:41:48 +01:00
|
|
|
auto distance = Util::distance( getPos(), pClosestChara->getPos() );
|
2021-11-27 00:53:57 +01:00
|
|
|
|
|
|
|
if( distance < range )
|
|
|
|
{
|
|
|
|
aggro( pClosestChara );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( pClosestChara && pClosestChara->isAlive() && ( getEnemyType() == 0 && pClosestChara->isBattleNpc() ) )
|
2019-01-27 01:12:31 +01:00
|
|
|
{
|
2021-11-27 00:53:57 +01:00
|
|
|
if( getBNpcType() == Common::BNpcType::Friendly )
|
|
|
|
{
|
|
|
|
if( pClosestChara->getAsBNpc()->getBNpcType() == Common::BNpcType::Friendly )
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-13 22:51:47 +01:00
|
|
|
if( getEnemyType() == 0 && pClosestChara->getAsBNpc()->getEnemyType() == 0 )
|
|
|
|
return;
|
|
|
|
|
2019-01-31 23:19:25 +11:00
|
|
|
// will use this range if chara level is lower than bnpc, otherwise diminishing equation applies
|
2021-11-27 00:53:57 +01:00
|
|
|
float range = 14.f;
|
2019-01-31 17:53:20 +11:00
|
|
|
|
|
|
|
if( pClosestChara->getLevel() > m_level )
|
|
|
|
{
|
|
|
|
auto levelDiff = std::abs( pClosestChara->getLevel() - this->getLevel() );
|
|
|
|
|
2019-01-31 21:45:46 +11:00
|
|
|
if( levelDiff >= 10 )
|
|
|
|
range = 0.f;
|
|
|
|
else
|
2022-01-19 17:55:29 +01:00
|
|
|
range = std::max< float >( 0.f, range - std::pow( 1.53f, static_cast< float >( levelDiff ) * 0.6f ) );
|
2019-01-31 21:45:46 +11:00
|
|
|
}
|
2019-01-31 17:53:20 +11:00
|
|
|
|
2022-01-21 08:41:48 +01:00
|
|
|
auto distance = Util::distance( getPos(), pClosestChara->getPos() );
|
2019-01-27 01:12:31 +01:00
|
|
|
|
2019-01-31 17:53:20 +11:00
|
|
|
if( distance < range )
|
|
|
|
{
|
2019-01-27 01:12:31 +01:00
|
|
|
aggro( pClosestChara );
|
2019-01-31 17:53:20 +11:00
|
|
|
}
|
2019-01-27 01:12:31 +01:00
|
|
|
}
|
2019-01-31 22:49:04 +11:00
|
|
|
}
|
|
|
|
|
2022-01-19 17:55:29 +01:00
|
|
|
void Sapphire::Entity::BNpc::setOwner( const Sapphire::Entity::CharaPtr& m_pChara )
|
2019-03-28 23:57:58 +01:00
|
|
|
{
|
|
|
|
m_pOwner = m_pChara;
|
2022-01-21 08:41:48 +01:00
|
|
|
auto targetId = static_cast< uint32_t >( INVALID_GAME_OBJECT_ID );
|
2019-03-28 23:57:58 +01:00
|
|
|
if( m_pChara != nullptr )
|
2022-01-21 08:41:48 +01:00
|
|
|
targetId = m_pChara->getId();
|
|
|
|
|
|
|
|
auto setOwnerPacket = makeZonePacket< FFXIVIpcFirstAttack >( getId() );
|
|
|
|
setOwnerPacket->data().Type = 0x01;
|
|
|
|
setOwnerPacket->data().Id = targetId;
|
|
|
|
sendToInRangeSet( setOwnerPacket );
|
2022-01-06 20:25:50 +01:00
|
|
|
|
|
|
|
if( m_pChara != nullptr && m_pChara->isPlayer() )
|
|
|
|
{
|
|
|
|
auto letter = makeActorControl( getId(), ActorControlType::SetHateLetter, 1, getId(), 0 );
|
|
|
|
auto& server = Common::Service< World::WorldServer >::ref();
|
|
|
|
server.queueForPlayer( m_pChara->getAsPlayer()->getCharacterId(), letter );
|
|
|
|
}
|
|
|
|
|
2019-03-28 23:57:58 +01:00
|
|
|
}
|
2019-04-17 00:10:32 +02:00
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::setLevelId( uint32_t levelId )
|
|
|
|
{
|
|
|
|
m_levelId = levelId;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Sapphire::Entity::BNpc::getLevelId() const
|
|
|
|
{
|
|
|
|
return m_levelId;
|
2019-04-17 08:34:58 +02:00
|
|
|
}
|
2019-04-17 12:37:24 +02:00
|
|
|
|
|
|
|
bool Sapphire::Entity::BNpc::hasFlag( uint32_t flag ) const
|
|
|
|
{
|
|
|
|
return m_flags & flag;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::setFlag( uint32_t flag )
|
|
|
|
{
|
|
|
|
m_flags |= flag;
|
|
|
|
}
|
2019-04-17 22:36:23 +02:00
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::autoAttack( CharaPtr pTarget )
|
|
|
|
{
|
2022-01-24 09:20:34 +01:00
|
|
|
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
|
2022-01-10 23:50:44 +01:00
|
|
|
auto pZone = teriMgr.getTerritoryByGuId( getTerritoryId() );
|
2019-04-17 22:36:23 +02:00
|
|
|
|
|
|
|
uint64_t tick = Util::getTimeMs();
|
|
|
|
|
|
|
|
// todo: this needs to use the auto attack delay for the equipped weapon
|
|
|
|
if( ( tick - m_lastAttack ) > 2500 )
|
|
|
|
{
|
|
|
|
pTarget->onActionHostile( getAsChara() );
|
|
|
|
m_lastAttack = tick;
|
|
|
|
srand( static_cast< uint32_t >( tick ) );
|
|
|
|
|
2019-07-27 00:37:40 +10:00
|
|
|
auto damage = Math::CalcStats::calcAutoAttackDamage( *this );
|
2021-12-31 14:57:18 +01:00
|
|
|
//damage.first = 1;
|
2019-04-17 22:36:23 +02:00
|
|
|
|
2022-01-06 20:25:50 +01:00
|
|
|
auto effectPacket = std::make_shared< EffectPacket1 >( getId(), pTarget->getId(), 7 );
|
2019-04-17 22:36:23 +02:00
|
|
|
effectPacket->setRotation( Util::floatToUInt16Rot( getRot() ) );
|
2021-11-27 00:53:57 +01:00
|
|
|
Common::CalcResultParam effectEntry{};
|
|
|
|
effectEntry.Value = static_cast< int16_t >( damage.first );
|
|
|
|
effectEntry.Type = ActionEffectType::CALC_RESULT_TYPE_DAMAGE_HP;
|
2022-01-06 20:25:50 +01:00
|
|
|
effectEntry.Flag = 128;
|
|
|
|
effectEntry.Arg0 = 3;
|
|
|
|
effectEntry.Arg1 = 7;
|
2022-01-02 22:32:17 +01:00
|
|
|
//effectEntry.Arg2 = 0x71;
|
2022-01-10 23:50:44 +01:00
|
|
|
effectPacket->setSequence( pZone->getNextEffectSequence() );
|
2022-01-07 21:22:55 +01:00
|
|
|
effectPacket->addTargetEffect( effectEntry );
|
2019-04-17 22:36:23 +02:00
|
|
|
|
|
|
|
sendToInRangeSet( effectPacket );
|
|
|
|
|
2020-01-07 19:08:13 +09:00
|
|
|
pTarget->takeDamage( static_cast< uint16_t >( damage.first ) );
|
2019-04-17 22:36:23 +02:00
|
|
|
|
|
|
|
}
|
2019-04-24 23:25:07 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::calculateStats()
|
|
|
|
{
|
2022-01-19 17:55:29 +01:00
|
|
|
auto level = getLevel();
|
|
|
|
auto job = static_cast< uint8_t >( getClass() );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
auto& exdData = Common::Service< Data::ExdData >::ref();
|
2019-04-24 23:25:07 +10:00
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
auto classInfo = exdData.getRow< Component::Excel::ClassJob >( job );
|
|
|
|
auto paramGrowthInfo = exdData.getRow< Component::Excel::ParamGrow >( level );
|
2019-04-24 23:25:07 +10:00
|
|
|
|
|
|
|
float base = Math::CalcStats::calculateBaseStat( *this );
|
|
|
|
|
2021-11-27 00:53:57 +01:00
|
|
|
auto str = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().STR ) / 100 ) );
|
|
|
|
auto dex = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().DEX ) / 100 ) );
|
|
|
|
auto vit = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().VIT ) / 100 ) );
|
|
|
|
auto inte = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().INT_ ) / 100 ) );
|
|
|
|
auto mnd = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().MND ) / 100 ) );
|
|
|
|
auto pie = static_cast< uint32_t >( base * ( static_cast< float >( classInfo->data().PIE ) / 100 ) );
|
|
|
|
|
|
|
|
setStatValue( BaseParam::Strength, str );
|
|
|
|
setStatValue( BaseParam::Dexterity, dex );
|
|
|
|
setStatValue( BaseParam::Vitality, vit );
|
|
|
|
setStatValue( BaseParam::Intelligence, inte );
|
|
|
|
setStatValue( BaseParam::Mind, mnd );
|
|
|
|
setStatValue( BaseParam::Piety, pie );
|
|
|
|
|
|
|
|
auto determination = static_cast< uint32_t >( base );
|
|
|
|
auto skillSpeed = static_cast< uint32_t >( paramGrowthInfo->data().ParamBase );
|
|
|
|
auto spellSpeed = static_cast< uint32_t >( paramGrowthInfo->data().ParamBase );
|
|
|
|
auto accuracy = static_cast< uint32_t >( paramGrowthInfo->data().ParamBase );
|
|
|
|
auto critHitRate = static_cast< uint32_t >( paramGrowthInfo->data().ParamBase );
|
|
|
|
|
|
|
|
setStatValue( BaseParam::Determination, determination );
|
|
|
|
setStatValue( BaseParam::SkillSpeed, skillSpeed );
|
|
|
|
setStatValue( BaseParam::SpellSpeed, spellSpeed );
|
|
|
|
setStatValue( BaseParam::CriticalHit, critHitRate );
|
|
|
|
|
|
|
|
setStatValue( BaseParam::AttackPower, str );
|
|
|
|
setStatValue( BaseParam::AttackMagicPotency, inte );
|
|
|
|
setStatValue( BaseParam::HealingMagicPotency, mnd );
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Sapphire::Entity::BNpc::getRank() const
|
|
|
|
{
|
|
|
|
return m_rank;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t Sapphire::Entity::BNpc::getBoundInstanceId() const
|
|
|
|
{
|
|
|
|
return m_boundInstanceId;
|
|
|
|
}
|
|
|
|
|
|
|
|
BNpcType Sapphire::Entity::BNpc::getBNpcType() const
|
|
|
|
{
|
|
|
|
return m_bnpcType;
|
|
|
|
}
|
2021-12-22 00:40:11 +01:00
|
|
|
|
|
|
|
uint32_t Sapphire::Entity::BNpc::getLayoutId() const
|
|
|
|
{
|
|
|
|
return m_layoutId;
|
|
|
|
}
|
2021-12-30 13:57:08 +01:00
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::init()
|
|
|
|
{
|
|
|
|
m_maxHp = Sapphire::Math::CalcStats::calculateMaxHp( *getAsChara() );
|
|
|
|
m_hp = m_maxHp;
|
|
|
|
max_hp = m_maxHp;
|
|
|
|
}
|