#include #include #include #include #include #include #include #include #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; } } } }