2018-08-29 21:40:59 +02:00
|
|
|
#include <Util/Util.h>
|
|
|
|
#include <Util/UtilMath.h>
|
|
|
|
#include <Network/PacketContainer.h>
|
|
|
|
#include <Exd/ExdDataGenerated.h>
|
|
|
|
#include <utility>
|
|
|
|
#include <Network/CommonActorControl.h>
|
2018-09-20 23:31:38 +02:00
|
|
|
#include <Network/PacketWrappers/EffectPacket.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"
|
|
|
|
|
2018-12-01 00:27:16 +11:00
|
|
|
#include "Territory/Zone.h"
|
2018-08-29 21:40:59 +02:00
|
|
|
|
|
|
|
#include "Network/GameConnection.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket142.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket143.h"
|
|
|
|
#include "Network/PacketWrappers/ActorControlPacket144.h"
|
|
|
|
#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
|
|
|
|
|
|
|
#include "StatusEffect/StatusEffect.h"
|
2018-11-20 21:32:13 +01:00
|
|
|
#include "ServerMgr.h"
|
2018-08-29 21:40:59 +02:00
|
|
|
#include "Session.h"
|
|
|
|
#include "Math/CalcBattle.h"
|
|
|
|
#include "Chara.h"
|
|
|
|
#include "Player.h"
|
2018-08-29 22:03:10 +02:00
|
|
|
#include "BNpc.h"
|
2018-09-10 23:57:14 +02:00
|
|
|
#include "BNpcTemplate.h"
|
2018-12-01 00:27:16 +11:00
|
|
|
#include "Manager/TerritoryMgr.h"
|
2018-08-29 21:40:59 +02:00
|
|
|
#include "Common.h"
|
2019-01-23 19:23:49 +01:00
|
|
|
#include "Framework.h"
|
2019-01-21 15:15:28 +01:00
|
|
|
#include <Logging/Logger.h>
|
2019-01-23 19:23:49 +01:00
|
|
|
#include <Manager/NaviMgr.h>
|
2019-02-06 18:51:51 +01:00
|
|
|
#include <Manager/TerritoryMgr.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;
|
|
|
|
using namespace Sapphire::Network::Packets::Server;
|
|
|
|
using namespace Sapphire::Network::ActorControl;
|
2018-08-29 21:40:59 +02:00
|
|
|
|
2018-12-29 00:53:52 +01:00
|
|
|
Sapphire::Entity::BNpc::BNpc( FrameworkPtr pFw ) :
|
|
|
|
Npc( ObjKind::BattleNpc, pFw )
|
2018-08-29 21:40:59 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-01-13 00:51:31 +01:00
|
|
|
Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX, float posY, float posZ, float rot,
|
2019-01-17 23:54:47 +01:00
|
|
|
uint8_t level, uint32_t maxHp, ZonePtr pZone, FrameworkPtr pFw ) :
|
2018-12-29 00:53:52 +01:00
|
|
|
Npc( ObjKind::BattleNpc, pFw )
|
2018-09-10 23:57:14 +02:00
|
|
|
{
|
2019-01-13 00:51:31 +01:00
|
|
|
m_id = id;
|
2018-09-10 23:57:14 +02:00
|
|
|
m_modelChara = pTemplate->getModelChara();
|
|
|
|
m_displayFlags = pTemplate->getDisplayFlags();
|
|
|
|
m_pose = pTemplate->getPose();
|
|
|
|
m_aggressionMode = pTemplate->getAggressionMode();
|
|
|
|
m_weaponMain = pTemplate->getWeaponMain();
|
|
|
|
m_weaponSub = pTemplate->getWeaponSub();
|
|
|
|
m_bNpcNameId = pTemplate->getBNpcNameId();
|
|
|
|
m_bNpcBaseId = pTemplate->getBNpcBaseId();
|
2018-09-13 22:14:31 +02:00
|
|
|
m_enemyType = pTemplate->getEnemyType();
|
|
|
|
m_pos.x = posX;
|
|
|
|
m_pos.y = posY;
|
|
|
|
m_pos.z = posZ;
|
2019-01-13 00:51:31 +01:00
|
|
|
m_rot = rot;
|
2018-09-13 22:14:31 +02:00
|
|
|
m_level = 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;
|
2018-09-13 22:14:31 +02:00
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
m_pCurrentZone = pZone;
|
|
|
|
|
|
|
|
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
|
|
|
|
2019-01-08 17:08:48 +01:00
|
|
|
m_maxHp = maxHp;
|
2018-09-13 22:14:31 +02:00
|
|
|
m_maxMp = 200;
|
2019-01-08 17:08:48 +01:00
|
|
|
m_hp = 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
|
|
|
|
2019-01-08 17:08:48 +01:00
|
|
|
m_baseStats.max_hp = maxHp;
|
2018-09-13 22:14:31 +02:00
|
|
|
m_baseStats.max_mp = 200;
|
2018-09-10 23:57:14 +02:00
|
|
|
|
|
|
|
memcpy( m_customize, pTemplate->getCustomize(), sizeof( m_customize ) );
|
|
|
|
memcpy( m_modelEquip, pTemplate->getModelEquip(), sizeof( m_modelEquip ) );
|
|
|
|
|
2019-01-31 22:49:04 +11:00
|
|
|
auto exdData = m_pFw->get< Data::ExdDataGenerated >();
|
|
|
|
assert( exdData );
|
|
|
|
|
|
|
|
auto bNpcBaseData = exdData->get< Data::BNpcBase >( m_bNpcBaseId );
|
|
|
|
assert( bNpcBaseData );
|
|
|
|
|
|
|
|
m_scale = bNpcBaseData->scale;
|
|
|
|
|
|
|
|
// todo: is this actually good?
|
2019-01-31 23:08:43 +01:00
|
|
|
//m_naviTargetReachedDistance = m_scale * 2.f;
|
|
|
|
m_naviTargetReachedDistance = 4.f;
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Sapphire::Entity::BNpc::getScale() const
|
|
|
|
{
|
|
|
|
return m_scale;
|
|
|
|
}
|
|
|
|
|
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-01-20 23:49:43 +01:00
|
|
|
pTarget->queuePacket( 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() );
|
2019-01-31 12:46:51 +11:00
|
|
|
pTarget->queuePacket( makeActorControl143( m_id, DespawnZoneScreenMsg, 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;
|
|
|
|
}
|
|
|
|
|
2019-01-23 17:07:40 +01:00
|
|
|
void Sapphire::Entity::BNpc::step()
|
|
|
|
{
|
|
|
|
if( m_naviLastPath.empty() )
|
|
|
|
// No path to track
|
|
|
|
return;
|
|
|
|
|
2019-01-23 21:36:26 +01:00
|
|
|
auto stepPos = m_naviLastPath[ m_naviPathStep ];
|
2019-01-23 17:07:40 +01:00
|
|
|
|
2019-01-28 13:40:03 +11:00
|
|
|
auto distanceToStep = Util::distance( getPos(), stepPos );
|
|
|
|
auto distanceToDest = Util::distance( getPos(), m_naviTarget );
|
2019-01-27 23:47:36 +11:00
|
|
|
|
|
|
|
if( distanceToStep <= 4 && m_naviPathStep < m_naviLastPath.size() - 1 )
|
2019-01-23 17:07:40 +01:00
|
|
|
{
|
|
|
|
// Reached step in path
|
|
|
|
m_naviPathStep++;
|
2019-01-23 21:36:26 +01:00
|
|
|
stepPos = m_naviLastPath[ m_naviPathStep ];
|
2019-01-23 17:07:40 +01:00
|
|
|
}
|
|
|
|
|
2019-01-23 17:42:09 +01:00
|
|
|
// This is probably not a good way to do it but works fine for now
|
2019-01-23 17:07:40 +01:00
|
|
|
float angle = Util::calcAngFrom( getPos().x, getPos().z, stepPos.x, stepPos.z ) + PI;
|
|
|
|
|
2019-01-28 19:16:44 +11:00
|
|
|
auto delta = static_cast< float >( Util::getTimeMs() - m_lastUpdate ) / 1000.f;
|
2019-01-28 15:44:27 +11:00
|
|
|
|
2019-01-28 15:47:46 +11:00
|
|
|
float speed = 7.5f * delta;
|
2019-01-27 01:12:31 +01:00
|
|
|
|
|
|
|
if( m_state == BNpcState::Roaming )
|
2019-01-28 15:47:46 +11:00
|
|
|
speed *= 0.27f;
|
2019-01-27 01:12:31 +01:00
|
|
|
|
2019-01-28 15:44:27 +11:00
|
|
|
// this seems to fix it but i don't know why :(
|
|
|
|
if( speed > distanceToDest )
|
|
|
|
speed = distanceToDest / delta;
|
2019-01-27 23:47:36 +11:00
|
|
|
|
2019-01-27 01:12:31 +01:00
|
|
|
auto x = ( cosf( angle ) * speed );
|
2019-01-24 13:15:30 +01:00
|
|
|
auto y = stepPos.y;
|
2019-01-27 01:12:31 +01:00
|
|
|
auto z = ( sinf( angle ) * speed );
|
2019-01-23 17:07:40 +01:00
|
|
|
|
2019-01-27 23:47:36 +11:00
|
|
|
|
2019-01-24 13:15:30 +01:00
|
|
|
face( stepPos );
|
2019-01-26 00:06:24 +01:00
|
|
|
setPos( { getPos().x + x, y, getPos().z + z } );
|
2019-01-23 17:07:40 +01:00
|
|
|
sendPositionUpdate();
|
2019-01-27 01:12:31 +01:00
|
|
|
|
2019-01-23 17:07:40 +01:00
|
|
|
}
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
bool Sapphire::Entity::BNpc::moveTo( const FFXIVARR_POSITION3& pos )
|
|
|
|
{
|
2019-01-31 22:49:04 +11:00
|
|
|
// do this first, this will update local actor position and the position of other actors
|
|
|
|
// and then this npc will then path from the position after pushing/being pushed
|
2019-01-31 23:08:43 +01:00
|
|
|
//pushNearbyBNpcs();
|
2019-01-31 22:49:04 +11:00
|
|
|
|
|
|
|
if( Util::distance( getPos(), pos ) <= m_naviTargetReachedDistance )
|
2019-01-26 00:06:24 +01:00
|
|
|
{
|
2019-01-23 17:07:40 +01:00
|
|
|
// Reached destination
|
2019-01-26 00:06:24 +01:00
|
|
|
m_naviLastPath.clear();
|
2019-01-17 23:54:47 +01:00
|
|
|
return true;
|
2019-01-26 00:06:24 +01:00
|
|
|
}
|
2019-01-23 17:07:40 +01:00
|
|
|
|
2019-01-27 23:26:34 +11:00
|
|
|
auto pNaviMgr = m_pFw->get< World::Manager::NaviMgr >();
|
|
|
|
auto pNaviProvider = pNaviMgr->getNaviProvider( m_pCurrentZone->getBgPath() );
|
|
|
|
|
|
|
|
if( !pNaviProvider )
|
2019-01-21 15:15:28 +01:00
|
|
|
{
|
2019-01-27 23:26:34 +11:00
|
|
|
Logger::error( "No NaviProvider for zone#{0} - {1}",
|
|
|
|
m_pCurrentZone->getGuId(),
|
|
|
|
m_pCurrentZone->getInternalName() );
|
|
|
|
return false;
|
|
|
|
}
|
2019-01-23 19:23:49 +01:00
|
|
|
|
2019-01-27 23:26:34 +11:00
|
|
|
auto path = pNaviProvider->findFollowPath( m_pos, pos );
|
2019-01-23 19:23:49 +01:00
|
|
|
|
2019-01-27 23:26:34 +11:00
|
|
|
if( !path.empty() )
|
|
|
|
{
|
|
|
|
m_naviLastPath = path;
|
|
|
|
m_naviTarget = pos;
|
|
|
|
m_naviPathStep = 0;
|
|
|
|
m_naviLastUpdate = Util::getTimeMs();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Logger::debug( "No path found from x{0} y{1} z{2} to x{3} y{4} z{5} in {6}",
|
|
|
|
getPos().x, getPos().y, getPos().z, pos.x, pos.y, pos.z, m_pCurrentZone->getInternalName() );
|
2019-01-21 15:15:28 +01:00
|
|
|
|
2019-01-28 15:35:44 +11:00
|
|
|
|
2019-01-27 23:26:34 +11:00
|
|
|
hateListClear();
|
2019-01-28 15:35:44 +11:00
|
|
|
|
|
|
|
if( m_state == BNpcState::Roaming )
|
|
|
|
{
|
|
|
|
Logger::warn( "BNpc Base#{0} Name#{1} unable to path from x{2} y{3} z{4} while roaming. "
|
|
|
|
"Possible pathing error in area. Returning BNpc to spawn position x{5} y{6} z{7}.",
|
|
|
|
m_bNpcBaseId, m_bNpcNameId,
|
|
|
|
getPos().x, getPos().y, getPos().z,
|
|
|
|
m_spawnPos.x, m_spawnPos.y, m_spawnPos.z );
|
|
|
|
|
|
|
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
|
|
|
m_state = BNpcState::Idle;
|
|
|
|
|
|
|
|
m_naviLastPath.clear();
|
|
|
|
|
|
|
|
setPos( m_spawnPos );
|
|
|
|
sendPositionUpdate();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2019-01-23 17:42:09 +01:00
|
|
|
}
|
2019-01-17 23:54:47 +01:00
|
|
|
|
|
|
|
|
2019-01-26 00:06:24 +01:00
|
|
|
step();
|
2019-01-30 22:37:58 +01:00
|
|
|
m_pCurrentZone->updateActorPosition( *this );
|
2019-01-17 23:54:47 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::sendPositionUpdate()
|
|
|
|
{
|
2019-01-26 11:39:25 +11:00
|
|
|
uint8_t unk1 = 0x3a;
|
|
|
|
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;
|
|
|
|
|
|
|
|
auto movePacket = std::make_shared< MoveActorPacket >( *getAsChara(), unk1, animationType, 0, 0x5A );
|
2019-01-17 23:54:47 +01:00
|
|
|
sendToInRangeSet( movePacket );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::hateListClear()
|
|
|
|
{
|
|
|
|
auto it = m_hateList.begin();
|
|
|
|
for( auto listEntry : m_hateList )
|
|
|
|
{
|
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;
|
|
|
|
}
|
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
void Sapphire::Entity::BNpc::hateListAdd( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
auto hateEntry = std::make_shared< HateListEntry >();
|
|
|
|
hateEntry->m_hateAmount = 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-01-19 01:15:17 +01:00
|
|
|
void Sapphire::Entity::BNpc::hateListUpdate( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
for( auto listEntry : m_hateList )
|
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
if( listEntry->m_pChara == pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
listEntry->m_hateAmount += hateAmount;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto hateEntry = std::make_shared< HateListEntry >();
|
|
|
|
hateEntry->m_hateAmount = 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-01-19 01:15:17 +01:00
|
|
|
void Sapphire::Entity::BNpc::hateListRemove( Sapphire::Entity::CharaPtr pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
for( auto listEntry : m_hateList )
|
|
|
|
{
|
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();
|
2019-01-19 22:56:07 +01:00
|
|
|
tmpPlayer->onMobDeaggro( getAsBNpc() );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
bool Sapphire::Entity::BNpc::hateListHasActor( Sapphire::Entity::CharaPtr pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
2019-01-29 08:02:39 +01:00
|
|
|
for( 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
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
void Sapphire::Entity::BNpc::aggro( Sapphire::Entity::CharaPtr pChara )
|
2019-01-17 23:54:47 +01:00
|
|
|
{
|
|
|
|
m_lastAttack = Util::getTimeMs();
|
2019-01-19 01:15:17 +01:00
|
|
|
hateListUpdate( pChara, 1 );
|
2019-01-17 23:54:47 +01:00
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
changeTarget( pChara->getId() );
|
2019-01-17 23:54:47 +01:00
|
|
|
setStance( Stance::Active );
|
|
|
|
m_state = BNpcState::Combat;
|
|
|
|
|
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-01-23 22:37:55 +01:00
|
|
|
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 1, 1, 1 ) );
|
2019-01-19 22:56:07 +01:00
|
|
|
tmpPlayer->onMobAggro( getAsBNpc() );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
void Sapphire::Entity::BNpc::deaggro( 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-01-23 22:37:55 +01:00
|
|
|
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) );
|
2019-01-19 22:56:07 +01:00
|
|
|
tmpPlayer->onMobDeaggro( getAsBNpc() );
|
2019-01-17 23:54:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-28 19:16:44 +11:00
|
|
|
void Sapphire::Entity::BNpc::onTick()
|
|
|
|
{
|
|
|
|
if( m_state == BNpcState::Retreat )
|
|
|
|
{
|
|
|
|
regainHp();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-17 23:54:47 +01:00
|
|
|
void Sapphire::Entity::BNpc::update( int64_t currTime )
|
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
const uint8_t minActorDistance = 4;
|
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
|
|
|
|
2019-01-27 01:12:31 +01:00
|
|
|
|
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-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:
|
|
|
|
{
|
|
|
|
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-01-26 00:06:24 +01:00
|
|
|
if( Util::getTimeSeconds() - m_lastRoamTargetReached > roamTick )
|
|
|
|
{
|
|
|
|
auto pNaviMgr = m_pFw->get< World::Manager::NaviMgr >();
|
|
|
|
auto pNaviProvider = pNaviMgr->getNaviProvider( m_pCurrentZone->getBgPath() );
|
2019-01-26 00:12:54 +01:00
|
|
|
|
|
|
|
if( !pNaviProvider )
|
|
|
|
{
|
|
|
|
m_lastRoamTargetReached = Util::getTimeSeconds();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-01-26 00:06:24 +01:00
|
|
|
m_roamPos = pNaviProvider->findRandomPositionInCircle( m_spawnPos, 5 );
|
|
|
|
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;
|
|
|
|
|
|
|
|
auto distanceOrig = Util::distance( getPos().x, getPos().y, getPos().z,
|
|
|
|
m_spawnPos.x,
|
|
|
|
m_spawnPos.y,
|
|
|
|
m_spawnPos.z );
|
|
|
|
|
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-01-19 01:15:17 +01:00
|
|
|
pHatedActor->getPos().x,
|
|
|
|
pHatedActor->getPos().y,
|
|
|
|
pHatedActor->getPos().z );
|
2019-01-17 23:54:47 +01:00
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
if( distanceOrig > maxDistanceToOrigin )
|
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;
|
|
|
|
}
|
|
|
|
|
2019-01-19 01:15:17 +01:00
|
|
|
if( distance > minActorDistance )
|
2019-02-06 18:51:51 +01:00
|
|
|
{
|
2019-02-06 23:29:51 +01:00
|
|
|
//auto pTeriMgr = m_pFw->get< World::Manager::TerritoryMgr >();
|
|
|
|
//if ( ( currTime - m_lastAttack ) > 600 && pTeriMgr->isDefaultTerritory( getCurrentZone()->getTerritoryTypeId() ) )
|
2019-02-06 18:51:51 +01:00
|
|
|
moveTo( pHatedActor->getPos() );
|
|
|
|
}
|
2019-01-17 23:54:47 +01:00
|
|
|
else
|
|
|
|
{
|
2019-01-19 01:15:17 +01:00
|
|
|
if( face( pHatedActor->getPos() ) )
|
2019-01-17 23:54:47 +01:00
|
|
|
sendPositionUpdate();
|
|
|
|
// 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-01-28 13:40:03 +11:00
|
|
|
|
2019-01-28 19:16:44 +11:00
|
|
|
|
|
|
|
Chara::update( currTime );
|
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() )
|
|
|
|
{
|
|
|
|
auto addHp = static_cast< uint32_t >( this->getMaxHp() * 0.1f + 1 );
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
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
|
|
|
|
|
|
|
for( auto& pHateEntry : m_hateList )
|
|
|
|
{
|
|
|
|
// TODO: handle drops
|
|
|
|
auto pPlayer = pHateEntry->m_pChara->getAsPlayer();
|
|
|
|
if( pPlayer )
|
|
|
|
pPlayer->onMobKill( m_bNpcNameId );
|
|
|
|
}
|
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();
|
|
|
|
|
2019-01-31 17:53:20 +11:00
|
|
|
if( pClosestChara && pClosestChara->isAlive() && pClosestChara->isPlayer() )
|
2019-01-27 01:12:31 +01:00
|
|
|
{
|
2019-01-31 23:19:25 +11:00
|
|
|
// will use this range if chara level is lower than bnpc, otherwise diminishing equation applies
|
2019-01-31 17:53:20 +11:00
|
|
|
float range = 13.f;
|
|
|
|
|
|
|
|
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
|
|
|
|
range = std::max< float >( 0.f, range - std::pow( 1.53f, levelDiff * 0.6f ) );
|
|
|
|
}
|
2019-01-31 17:53:20 +11:00
|
|
|
|
2019-01-27 01:12:31 +01:00
|
|
|
auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
|
|
|
|
pClosestChara->getPos().x,
|
|
|
|
pClosestChara->getPos().y,
|
|
|
|
pClosestChara->getPos().z );
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-01-31 23:55:55 +11:00
|
|
|
void Sapphire::Entity::BNpc::pushNearbyBNpcs()
|
2019-01-31 22:49:04 +11:00
|
|
|
{
|
|
|
|
for( auto& bNpc : m_inRangeBNpc )
|
|
|
|
{
|
|
|
|
auto pos = bNpc->getPos();
|
|
|
|
auto distance = Util::distance( m_pos, bNpc->getPos() );
|
|
|
|
|
2019-01-31 23:08:43 +01:00
|
|
|
|
2019-01-31 23:19:25 +11:00
|
|
|
// todo: not sure what's good here
|
|
|
|
auto factor = bNpc->getNaviTargetReachedDistance();
|
|
|
|
|
2019-01-31 23:44:53 +11:00
|
|
|
auto delta = static_cast< float >( Util::getTimeMs() - bNpc->getLastUpdateTime() ) / 1000.f;
|
|
|
|
delta = std::min< float >( factor, delta );
|
|
|
|
|
2019-01-31 22:49:04 +11:00
|
|
|
// too far away, ignore it
|
2019-01-31 23:19:25 +11:00
|
|
|
if( distance > factor )
|
2019-01-31 22:49:04 +11:00
|
|
|
continue;
|
|
|
|
|
|
|
|
auto angle = Util::calcAngFrom( m_pos.x, m_pos.y, pos.x, pos.y ) + PI;
|
|
|
|
|
|
|
|
auto x = ( cosf( angle ) );
|
|
|
|
auto z = ( sinf( angle ) );
|
|
|
|
|
2019-01-31 23:44:53 +11:00
|
|
|
bNpc->setPos( pos.x + ( x * factor * delta ),
|
2019-01-31 22:49:04 +11:00
|
|
|
pos.y,
|
2019-01-31 23:08:43 +01:00
|
|
|
pos.z + ( z * factor * delta ), true );
|
2019-01-31 22:49:04 +11:00
|
|
|
|
|
|
|
// setPos( m_pos.x + ( xBase * -pushDistance ),
|
|
|
|
// m_pos.y,
|
|
|
|
// m_pos.z + ( zBase * -pushDistance ) );
|
|
|
|
}
|
2019-03-28 23:57:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Sapphire::Entity::BNpc::setOwner( Sapphire::Entity::CharaPtr m_pChara )
|
|
|
|
{
|
|
|
|
m_pOwner = m_pChara;
|
|
|
|
if( m_pChara != nullptr )
|
|
|
|
{
|
|
|
|
auto setOwnerPacket = makeZonePacket< FFXIVIpcActorOwner >( m_pChara->getId() );
|
|
|
|
setOwnerPacket->data().type = 0x01;
|
|
|
|
setOwnerPacket->data().actorId2 = m_pChara->getId();
|
|
|
|
sendToInRangeSet( setOwnerPacket );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto setOwnerPacket = makeZonePacket< FFXIVIpcActorOwner >( m_pChara->getId() );
|
|
|
|
setOwnerPacket->data().type = 0x01;
|
|
|
|
setOwnerPacket->data().actorId2 = INVALID_GAME_OBJECT_ID;
|
|
|
|
sendToInRangeSet( setOwnerPacket );
|
|
|
|
}
|
|
|
|
}
|