mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-25 22:17:45 +00:00
387 lines
9.6 KiB
C++
387 lines
9.6 KiB
C++
#include <Util/Util.h>
|
|
#include <Util/UtilMath.h>
|
|
#include <Network/PacketContainer.h>
|
|
#include <Exd/ExdDataGenerated.h>
|
|
#include <utility>
|
|
#include <Network/CommonActorControl.h>
|
|
#include <Network/PacketWrappers/EffectPacket.h>
|
|
#include <Network/PacketDef/Zone/ClientZoneDef.h>
|
|
|
|
#include "Forwards.h"
|
|
#include "Action/Action.h"
|
|
|
|
#include "Territory/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 "Network/PacketWrappers/NpcSpawnPacket.h"
|
|
#include "Network/PacketWrappers/MoveActorPacket.h"
|
|
|
|
#include "StatusEffect/StatusEffect.h"
|
|
#include "Action/ActionCollision.h"
|
|
#include "ServerMgr.h"
|
|
#include "Session.h"
|
|
#include "Math/CalcBattle.h"
|
|
#include "Chara.h"
|
|
#include "Player.h"
|
|
#include "BNpc.h"
|
|
#include "BNpcTemplate.h"
|
|
#include "Manager/TerritoryMgr.h"
|
|
#include "Common.h"
|
|
|
|
using namespace Sapphire::Common;
|
|
using namespace Sapphire::Network::Packets;
|
|
using namespace Sapphire::Network::Packets::Server;
|
|
using namespace Sapphire::Network::ActorControl;
|
|
|
|
Sapphire::Entity::BNpc::BNpc( FrameworkPtr pFw ) :
|
|
Npc( ObjKind::BattleNpc, pFw )
|
|
{
|
|
}
|
|
|
|
Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX, float posY, float posZ, float rot,
|
|
uint8_t level, uint32_t maxHp, ZonePtr pZone, FrameworkPtr pFw ) :
|
|
Npc( ObjKind::BattleNpc, pFw )
|
|
{
|
|
m_id = id;
|
|
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();
|
|
m_enemyType = pTemplate->getEnemyType();
|
|
m_pos.x = posX;
|
|
m_pos.y = posY;
|
|
m_pos.z = posZ;
|
|
m_rot = rot;
|
|
m_level = level;
|
|
|
|
m_pCurrentZone = pZone;
|
|
|
|
m_spawnPos = m_pos;
|
|
|
|
m_maxHp = maxHp;
|
|
m_maxMp = 200;
|
|
m_hp = maxHp;
|
|
m_mp = 200;
|
|
|
|
m_state = BNpcState::Idle;
|
|
|
|
m_baseStats.max_hp = maxHp;
|
|
m_baseStats.max_mp = 200;
|
|
|
|
memcpy( m_customize, pTemplate->getCustomize(), sizeof( m_customize ) );
|
|
memcpy( m_modelEquip, pTemplate->getModelEquip(), sizeof( m_modelEquip ) );
|
|
|
|
}
|
|
|
|
Sapphire::Entity::BNpc::~BNpc()
|
|
{
|
|
}
|
|
|
|
uint8_t Sapphire::Entity::BNpc::getAggressionMode() const
|
|
{
|
|
return m_aggressionMode;
|
|
}
|
|
|
|
uint8_t Sapphire::Entity::BNpc::getEnemyType() const
|
|
{
|
|
return m_enemyType;
|
|
}
|
|
|
|
uint64_t Sapphire::Entity::BNpc::getWeaponMain() const
|
|
{
|
|
return m_weaponMain;
|
|
}
|
|
|
|
uint64_t Sapphire::Entity::BNpc::getWeaponSub() const
|
|
{
|
|
return m_weaponSub;
|
|
}
|
|
|
|
uint16_t Sapphire::Entity::BNpc::getModelChara() const
|
|
{
|
|
return m_modelChara;
|
|
}
|
|
|
|
uint8_t Sapphire::Entity::BNpc::getLevel() const
|
|
{
|
|
return m_level;
|
|
}
|
|
|
|
uint32_t Sapphire::Entity::BNpc::getBNpcBaseId() const
|
|
{
|
|
return m_bNpcBaseId;
|
|
}
|
|
|
|
uint32_t Sapphire::Entity::BNpc::getBNpcNameId() const
|
|
{
|
|
return m_bNpcNameId;
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::spawn( PlayerPtr pTarget )
|
|
{
|
|
pTarget->queuePacket( std::make_shared< NpcSpawnPacket >( *getAsBNpc(), *pTarget ) );
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::despawn( PlayerPtr pTarget )
|
|
{
|
|
pTarget->freePlayerSpawnId( getId() );
|
|
}
|
|
|
|
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 )
|
|
{
|
|
if( Util::distance( getPos().x, getPos().y, getPos().z, pos.x, pos.y, pos.z ) <= 4 )
|
|
// reached destination
|
|
return true;
|
|
|
|
float rot = Util::calcAngFrom( getPos().x, getPos().z, pos.x, pos.z );
|
|
float newRot = PI - rot + ( PI / 2 );
|
|
|
|
face( pos );
|
|
float angle = Util::calcAngFrom( getPos().x, getPos().z, pos.x, pos.z ) + PI;
|
|
|
|
auto x = ( cosf( angle ) * 1.1f );
|
|
auto y = ( getPos().y + pos.y ) * 0.5f; // fake value while there is no collision
|
|
auto z = ( sinf( angle ) * 1.1f );
|
|
|
|
Common::FFXIVARR_POSITION3 newPos{ getPos().x + x, y, getPos().z + z };
|
|
setPos( newPos );
|
|
|
|
Common::FFXIVARR_POSITION3 tmpPos{ getPos().x + x, y, getPos().z + z };
|
|
|
|
setPos( tmpPos );
|
|
setRot( newRot );
|
|
|
|
sendPositionUpdate();
|
|
|
|
return false;
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::sendPositionUpdate()
|
|
{
|
|
auto movePacket = std::make_shared< MoveActorPacket >( *getAsChara(), 0x3A, 0, 0, 0x5A );
|
|
sendToInRangeSet( movePacket );
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::hateListClear()
|
|
{
|
|
auto it = m_hateList.begin();
|
|
for( auto listEntry : m_hateList )
|
|
{
|
|
if( isInRangeSet( listEntry->m_pChara ) )
|
|
deaggro( listEntry->m_pChara );
|
|
}
|
|
m_hateList.clear();
|
|
}
|
|
|
|
Sapphire::Entity::CharaPtr Sapphire::Entity::BNpc::hateListGetHighest()
|
|
{
|
|
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 )
|
|
return entry->m_pChara;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::hateListAdd( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount )
|
|
{
|
|
auto hateEntry = std::make_shared< HateListEntry >();
|
|
hateEntry->m_hateAmount = hateAmount;
|
|
hateEntry->m_pChara = pChara;
|
|
|
|
m_hateList.insert( hateEntry );
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::hateListUpdate( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount )
|
|
{
|
|
for( auto listEntry : m_hateList )
|
|
{
|
|
if( listEntry->m_pChara == pChara )
|
|
{
|
|
listEntry->m_hateAmount += hateAmount;
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto hateEntry = std::make_shared< HateListEntry >();
|
|
hateEntry->m_hateAmount = hateAmount;
|
|
hateEntry->m_pChara = pChara;
|
|
m_hateList.insert( hateEntry );
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::hateListRemove( Sapphire::Entity::CharaPtr pChara )
|
|
{
|
|
for( auto listEntry : m_hateList )
|
|
{
|
|
if( listEntry->m_pChara == pChara )
|
|
{
|
|
|
|
m_hateList.erase( listEntry );
|
|
if( pChara->isPlayer() )
|
|
{
|
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
|
tmpPlayer->onMobDeaggro( getAsBNpc() );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Sapphire::Entity::BNpc::hateListHasActor( Sapphire::Entity::CharaPtr pChara )
|
|
{
|
|
for( auto listEntry : m_hateList )
|
|
{
|
|
if( listEntry->m_pChara == pChara )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::aggro( Sapphire::Entity::CharaPtr pChara )
|
|
{
|
|
m_lastAttack = Util::getTimeMs();
|
|
hateListUpdate( pChara, 1 );
|
|
|
|
changeTarget( pChara->getId() );
|
|
setStance( Stance::Active );
|
|
m_state = BNpcState::Combat;
|
|
|
|
if( pChara->isPlayer() )
|
|
{
|
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
|
tmpPlayer->queuePacket( makeActorControl142( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) );
|
|
tmpPlayer->onMobAggro( getAsBNpc() );
|
|
}
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::deaggro( Sapphire::Entity::CharaPtr pChara )
|
|
{
|
|
if( !hateListHasActor( pChara ) )
|
|
hateListRemove( pChara );
|
|
|
|
if( pChara->isPlayer() )
|
|
{
|
|
PlayerPtr tmpPlayer = pChara->getAsPlayer();
|
|
tmpPlayer->onMobDeaggro( getAsBNpc() );
|
|
}
|
|
}
|
|
|
|
void Sapphire::Entity::BNpc::update( int64_t currTime )
|
|
{
|
|
const uint8_t minActorDistance = 4;
|
|
const uint8_t aggroRange = 8;
|
|
const uint8_t maxDistanceToOrigin = 30;
|
|
|
|
switch( m_state )
|
|
{
|
|
case BNpcState::Retreat:
|
|
{
|
|
if( moveTo( m_spawnPos ) )
|
|
m_state = BNpcState::Idle;
|
|
}
|
|
break;
|
|
|
|
case BNpcState::Idle:
|
|
{
|
|
// passive mobs should ignore players unless aggro'd
|
|
if( m_aggressionMode == 1 )
|
|
return;
|
|
|
|
CharaPtr pClosestChara = getClosestChara();
|
|
|
|
if( pClosestChara && pClosestChara->isAlive() )
|
|
{
|
|
auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
|
|
pClosestChara->getPos().x,
|
|
pClosestChara->getPos().y,
|
|
pClosestChara->getPos().z );
|
|
|
|
if( distance < aggroRange && pClosestChara->isPlayer() )
|
|
aggro( pClosestChara );
|
|
//if( distance < aggroRange && getbehavior() == 2 )
|
|
// aggro( pClosestActor );
|
|
}
|
|
}
|
|
|
|
case BNpcState::Combat:
|
|
{
|
|
auto pHatedActor = hateListGetHighest();
|
|
if( !pHatedActor )
|
|
return;
|
|
|
|
auto distanceOrig = Util::distance( getPos().x, getPos().y, getPos().z,
|
|
m_spawnPos.x,
|
|
m_spawnPos.y,
|
|
m_spawnPos.z );
|
|
|
|
if( pHatedActor && !pHatedActor->isAlive() )
|
|
{
|
|
hateListRemove( pHatedActor );
|
|
pHatedActor = hateListGetHighest();
|
|
}
|
|
|
|
if( pHatedActor )
|
|
{
|
|
auto distance = Util::distance( getPos().x, getPos().y, getPos().z,
|
|
pHatedActor->getPos().x,
|
|
pHatedActor->getPos().y,
|
|
pHatedActor->getPos().z );
|
|
|
|
if( distanceOrig > maxDistanceToOrigin )
|
|
{
|
|
hateListClear();
|
|
changeTarget( INVALID_GAME_OBJECT_ID );
|
|
setStance( Stance::Passive );
|
|
//setOwner( nullptr );
|
|
m_state = BNpcState::Retreat;
|
|
break;
|
|
}
|
|
|
|
if( distance > minActorDistance )
|
|
moveTo( pHatedActor->getPos() );
|
|
else
|
|
{
|
|
if( face( pHatedActor->getPos() ) )
|
|
sendPositionUpdate();
|
|
// in combat range. ATTACK!
|
|
autoAttack( pHatedActor );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
changeTarget( INVALID_GAME_OBJECT_ID );
|
|
setStance( Stance::Passive );
|
|
//setOwner( nullptr );
|
|
m_state = BNpcState::Retreat;
|
|
}
|
|
}
|
|
}
|
|
}
|