1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-27 14:57:44 +00:00
sapphire/src/servers/Server_Zone/Actor/BattleNpc.cpp

581 lines
16 KiB
C++
Raw Normal View History

2017-08-08 13:53:47 +02:00
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
2017-08-28 16:13:23 -03:00
#include <cmath>
2017-08-08 13:53:47 +02:00
2017-08-19 00:18:40 +02:00
#include <src/servers/Server_Common/Database/Database.h>
#include <src/servers/Server_Common/Logging/Logger.h>
#include <src/servers/Server_Common/Exd/ExdData.h>
#include <src/servers/Server_Common/Util/Util.h>
#include <src/servers/Server_Common/Util/UtilMath.h>
2017-08-08 13:53:47 +02:00
#include "Player.h"
#include "BattleNpc.h"
#include "src/servers/Server_Zone/Network/PacketWrappers/MoveActorPacket.h"
#include "src/servers/Server_Zone/Network/PacketWrappers/ActorControlPacket142.h"
#include "src/servers/Server_Zone/Network/PacketWrappers/ActorControlPacket143.h"
#include "src/servers/Server_Zone/StatusEffect/StatusEffectContainer.h"
2017-08-08 13:53:47 +02:00
using namespace Core::Common;
using namespace Core::Network::Packets;
using namespace Core::Network::Packets::Server;
extern Core::Logger g_log;
extern Core::Db::Database g_database;
extern Core::Data::ExdData g_exdData;
uint32_t Core::Entity::BattleNpc::m_nextID = 1149241694;
Core::Entity::BattleNpc::BattleNpc()
{
m_id = 0;
m_type = ActorType::BattleNpc;
m_status = ActorStatus::Idle;
}
Core::Entity::BattleNpc::~BattleNpc()
{
}
Core::Entity::BattleNpc::BattleNpc( uint32_t modelId, uint32_t nameid, const Common::FFXIVARR_POSITION3& spawnPos,
uint32_t sizeId, uint32_t type, uint32_t level, uint32_t behaviour,
uint32_t mobType )
{
BattleNpc::m_nextID++;
m_id = BattleNpc::m_nextID;
//strcpy( m_name, pBNpc->m_name.c_str() );
m_pos = spawnPos;
m_posOrigin = spawnPos;
m_type = ActorType::BattleNpc;
m_mode = MODE_IDLE;
m_maxHp = 150;
m_maxMp = 100;
m_baseStats.max_hp = m_maxHp;
m_baseStats.max_mp = m_maxMp;
m_hp = m_maxHp;
m_mp = m_maxMp;
m_currentStance = Stance::Passive;
m_class = ClassJob::CLASS_GLADIATOR;
m_level = level > 0 ? level : 60;
m_modelId = modelId;
m_nameId = nameid;
m_behavior = behaviour;
m_bnpcBaseId = sizeId;
m_status = ActorStatus::Idle;
m_pOwner = nullptr;
m_mobType = mobType;
//m_type = static_cast< Common::ActorType >( type );
}
void Core::Entity::BattleNpc::initStatusEffectContainer()
{
m_pStatusEffectContainer = StatusEffect::StatusEffectContainerPtr( new StatusEffect::StatusEffectContainer( shared_from_this() ) );
}
2017-08-08 13:53:47 +02:00
// spawn this player for pTarget
void Core::Entity::BattleNpc::spawn( Core::Entity::PlayerPtr pTarget )
{
//GamePacketNew< FFXIVIpcActorSpawn > spawnPacket( getId(), pTarget->getId() );
//spawnPacket.data().unknown_0 = 0;
//spawnPacket.data().ownerId = m_pOwner == nullptr ? INVALID_GAME_OBJECT_ID : m_pOwner->getId();
//spawnPacket.data().targetId = INVALID_GAME_OBJECT_ID & 0xFFFFFFFF;
//spawnPacket.data().hPCurr = m_hp;
//spawnPacket.data().hPMax = m_baseStats.max_hp;
//spawnPacket.data().level = m_level;
////spawnPacket.data().tPCurr = 1000;
//spawnPacket.data().model = m_modelId;
//spawnPacket.data().bnpcBaseId = m_bnpcBaseId;
//spawnPacket.data().nameId = m_nameId;
//spawnPacket.data().spawnIndex = pTarget->getSpawnIdForActorId( getId() );
//g_log.info(std::to_string(spawnPacket.data().spawnIndex) + " " + std::to_string(getId()));
//spawnPacket.data().status = static_cast< uint8_t >( m_status );
//spawnPacket.data().mobAgressive = m_behavior;
//spawnPacket.data().type = static_cast< uint8_t >( m_type );
//spawnPacket.data().mobTypeIcon = m_mobType;
//spawnPacket.data().unknown_33 = 5;
//spawnPacket.data().typeFlags = 4;
//spawnPacket.data().pos.x = m_pos.x;
//spawnPacket.data().pos.y = m_pos.y;
//spawnPacket.data().pos.z = m_pos.z;
//spawnPacket.data().rotation = Math::Util::floatToUInt16Rot( getRotation() );
////spawnPacket.data().unknown_B0[11] = 1;
////spawnPacket.data().unknown_B0[12] = 4;
////spawnPacket.data().unknown_B0[14] = 20;
//pTarget->queuePacket( spawnPacket );
GamePacketNew< FFXIVIpcNpcSpawn, ServerZoneIpcType > spawnPacket( getId(), pTarget->getId() );
2017-08-08 13:53:47 +02:00
spawnPacket.data().pos.x = m_pos.x;
spawnPacket.data().pos.y = m_pos.y;
spawnPacket.data().pos.z = m_pos.z;
spawnPacket.data().targetId = INVALID_GAME_OBJECT_ID & 0xFFFFFFFF;
spawnPacket.data().hPCurr = m_hp;
spawnPacket.data().hPMax = m_baseStats.max_hp;
spawnPacket.data().level = m_level;
spawnPacket.data().subtype = 5;
spawnPacket.data().enemyType = 4;
spawnPacket.data().modelChara = m_modelId;
spawnPacket.data().bNPCBase = m_bnpcBaseId;
spawnPacket.data().bNPCName = m_nameId;
spawnPacket.data().spawnIndex = pTarget->getSpawnIdForActorId( getId() );
spawnPacket.data().rotation = Math::Util::floatToUInt16Rot( getRotation() );
spawnPacket.data().type = static_cast< uint8_t >( m_type );
spawnPacket.data().state = static_cast< uint8_t >( m_status );
pTarget->queuePacket( spawnPacket );
}
// despawn
void Core::Entity::BattleNpc::despawn( Core::Entity::ActorPtr pTarget )
{
auto pPlayer = pTarget->getAsPlayer();
pPlayer->freePlayerSpawnId( getId() );
ActorControlPacket143 controlPacket( m_id, DespawnZoneScreenMsg, 0x04, getId(), 0x01 );
pPlayer->queuePacket( controlPacket );
}
uint8_t Core::Entity::BattleNpc::getLevel() const
{
return m_level;
}
Core::Entity::StateMode Core::Entity::BattleNpc::getMode() const
{
return m_mode;
}
void Core::Entity::BattleNpc::setMode( Core::Entity::StateMode mode )
{
m_mode = mode;
}
uint8_t Core::Entity::BattleNpc::getbehavior() const
{
return m_behavior;
}
void Core::Entity::BattleNpc::hateListAdd( Core::Entity::ActorPtr pActor, int32_t hateAmount )
{
HateListEntry* hateEntry = new HateListEntry();
hateEntry->m_hateAmount = hateAmount;
hateEntry->m_pActor = pActor;
m_hateList.insert( hateEntry );
}
Core::Entity::ActorPtr Core::Entity::BattleNpc::hateListGetHighest()
{
auto it = m_hateList.begin();
uint32_t maxHate = 0;
HateListEntry* entry = nullptr;
for( ; it != m_hateList.end(); ++it )
{
if( ( *it )->m_hateAmount > maxHate )
{
maxHate = ( *it )->m_hateAmount;
entry = *it;
}
}
if( entry && maxHate != 0 )
return entry->m_pActor;
return nullptr;
}
void Core::Entity::BattleNpc::setOwner( Core::Entity::PlayerPtr pPlayer )
{
m_pOwner = pPlayer;
if( pPlayer != nullptr )
{
GamePacketNew< FFXIVIpcActorOwner, ServerZoneIpcType > setOwnerPacket( getId(), pPlayer->getId() );
2017-08-08 13:53:47 +02:00
setOwnerPacket.data().type = 0x01;
setOwnerPacket.data().actorId = pPlayer->getId();
sendToInRangeSet( setOwnerPacket );
}
else
{
GamePacketNew< FFXIVIpcActorOwner, ServerZoneIpcType > setOwnerPacket(getId(), INVALID_GAME_OBJECT_ID);
2017-08-08 13:53:47 +02:00
setOwnerPacket.data().type = 0x01;
setOwnerPacket.data().actorId = INVALID_GAME_OBJECT_ID;
sendToInRangeSet( setOwnerPacket );
}
}
void Core::Entity::BattleNpc::sendPositionUpdate()
{
MoveActorPacket movePacket( shared_from_this(), 0x3A, 0x00, 0, 0x5A );
sendToInRangeSet( movePacket );
}
bool Core::Entity::BattleNpc::moveTo( Common::FFXIVARR_POSITION3& pos )
{
if( Math::Util::distance( getPos().x, getPos().y, getPos().z,
pos.x, pos.y, pos.z ) <= 4 )
// reached destination
return true;
float rot = Math::Util::calcAngFrom(getPos().x, getPos().z, pos.x, pos.z);
float newRot = PI - rot + (PI / 2);
face( pos );
float angle = Math::Util::calcAngFrom( getPos().x, getPos().z, pos.x, pos.z ) + PI;
float x = static_cast< float >( cosf(angle) * 1.1f );
float y = ( getPos().y + pos.y ) * 0.5f; // fake value while there is no collision
float z = static_cast< float >( sinf(angle) * 1.1f );
Common::FFXIVARR_POSITION3 newPos;
newPos.x = getPos().x + x;
newPos.y = y;
newPos.z = getPos().z + z;
setPosition( newPos );
Common::FFXIVARR_POSITION3 tmpPos;
tmpPos.x = getPos().x + x;
tmpPos.y = y;
tmpPos.z = getPos().z + z;
angle = angle * 2;
setPosition( tmpPos );
setRotation(newRot);
sendPositionUpdate();
return false;
}
void Core::Entity::BattleNpc::aggro( Core::Entity::ActorPtr pActor )
{
m_lastAttack = Util::getTimeMs();
hateListUpdate( pActor, 1 );
changeTarget( pActor->getId() );
setStance( Stance::Active );
m_mode = MODE_COMBAT;
if( pActor->isPlayer() )
{
PlayerPtr tmpPlayer = pActor->getAsPlayer();
tmpPlayer->queuePacket( ActorControlPacket142( getId(), 0, 1, 1 ) );
tmpPlayer->onMobAggro( getAsBattleNpc() );
}
}
void Core::Entity::BattleNpc::deaggro( Core::Entity::ActorPtr pActor )
{
if( !hateListHasActor( pActor ) )
hateListRemove( pActor );
if( pActor->isPlayer() )
{
PlayerPtr tmpPlayer = pActor->getAsPlayer();
tmpPlayer->onMobDeaggro( getAsBattleNpc() );
}
}
void Core::Entity::BattleNpc::hateListClear()
{
auto it = m_hateList.begin();
for( ; it != m_hateList.end(); ++it )
{
if( isInRangeSet( ( *it )->m_pActor ) )
deaggro( ( *it )->m_pActor );
HateListEntry* tmpListEntry = ( *it );
delete tmpListEntry;
}
m_hateList.clear();
}
void Core::Entity::BattleNpc::hateListRemove( Core::Entity::ActorPtr pActor )
{
auto it = m_hateList.begin();
for( ; it != m_hateList.end(); ++it )
{
if( ( *it )->m_pActor == pActor )
{
HateListEntry* pEntry = *it;
m_hateList.erase( it );
delete pEntry;
if( pActor->isPlayer() )
{
PlayerPtr tmpPlayer = pActor->getAsPlayer();
tmpPlayer->onMobDeaggro( getAsBattleNpc() );
}
return;
}
}
}
bool Core::Entity::BattleNpc::hateListHasActor( Core::Entity::ActorPtr pActor )
{
auto it = m_hateList.begin();
for( ; it != m_hateList.end(); ++it )
{
if( ( *it )->m_pActor == pActor )
return true;
}
return false;
}
void Core::Entity::BattleNpc::resetPos()
{
m_pos = m_posOrigin;
}
uint32_t Core::Entity::BattleNpc::getNameId() const
{
return m_nameId;
}
void Core::Entity::BattleNpc::hateListUpdate( Core::Entity::ActorPtr pActor, int32_t hateAmount )
{
auto it = m_hateList.begin();
for( ; it != m_hateList.end(); ++it )
{
if( ( *it )->m_pActor == pActor )
{
( *it )->m_hateAmount += hateAmount;
return;
}
}
HateListEntry* hateEntry = new HateListEntry();
hateEntry->m_hateAmount = hateAmount;
hateEntry->m_pActor = pActor;
m_hateList.insert( hateEntry );
}
void Core::Entity::BattleNpc::onDeath()
{
//LuaManager->onMobDeath( this );
setTimeOfDeath( static_cast< uint32_t >( time( nullptr ) ) );
setTargetId( INVALID_GAME_OBJECT_ID );
m_currentStance = Stance::Passive;
m_mode = MODE_IDLE;
m_hp = 0;
setOwner( nullptr );
// todo: fully ghetto retarded exp reward pls fix
{
uint32_t minHate = -1;
uint32_t maxHate = 0;
uint32_t totalHate = 0;
for( auto& pHateEntry : m_hateList )
{
if( pHateEntry->m_pActor->isPlayer() )
{
if( pHateEntry->m_hateAmount < minHate )
minHate = pHateEntry->m_hateAmount;
else if( pHateEntry->m_hateAmount > maxHate )
maxHate = pHateEntry->m_hateAmount;
}
totalHate += pHateEntry->m_hateAmount;
}
//uint32_t plsBeHatedThisMuchAtLeast = totalHate / ( maxHate + 2 ) / clamp( m_hateList.size(), 1.0f, 1.5f );
for( auto& pHateEntry : m_hateList )
{
// todo: this is pure retarded
// todo: check for companion
if( pHateEntry->m_pActor->isPlayer() ) // && pHateEntry->m_hateAmount >= plsBeHatedThisMuchAtLeast )
{
auto level = pHateEntry->m_pActor->getLevel();
auto levelDiff = (int)this->m_level - (int)level;
auto cappedLevelDiff = Math::Util::clamp( levelDiff, 1, 6 );
auto expNeeded = g_exdData.m_paramGrowthInfoMap[m_level + cappedLevelDiff - 1].needed_exp;
int32_t exp = 0;
// todo: arbitrary numbers pulled out of my ass
if( m_level <= 14 )
exp = ( expNeeded / ( 100 - levelDiff) ) + cappedLevelDiff + ( cappedLevelDiff * ( ( rand() % ( cappedLevelDiff * 9 ) ) + 1 ) );
else if( m_level <= 24 )
exp = ( expNeeded / ( 150 - levelDiff) ) + cappedLevelDiff + ( cappedLevelDiff * ( ( rand() % ( cappedLevelDiff * 8 ) ) + 1 ) );
else if( m_level <= 34 )
exp = ( expNeeded / ( 350 - levelDiff ) ) + cappedLevelDiff + ( cappedLevelDiff * ( ( rand() % ( cappedLevelDiff * 7 ) ) + 1 ) );
else if( m_level <= 44 )
exp = ( expNeeded / ( 550 - levelDiff ) ) + cappedLevelDiff + ( cappedLevelDiff * ( ( rand() % ( cappedLevelDiff * 6 ) ) + 1 ) );
else if( m_level <= 50 )
exp = ( expNeeded / ( 750 - levelDiff ) ) + cappedLevelDiff + ( cappedLevelDiff * ( ( rand() % ( cappedLevelDiff * 5 ) ) + 1 ) );
else
exp = ( expNeeded / ( 1200 - levelDiff ) ) + cappedLevelDiff + ( cappedLevelDiff * ( ( rand() % ( cappedLevelDiff * 4 ) ) + 1 ) );
// todo: this is actually retarded, we need real rand()
srand( time( NULL ) );
auto pPlayer = pHateEntry->m_pActor->getAsPlayer();
pPlayer->gainExp( exp );
pPlayer->onMobKill( m_nameId );
}
}
}
hateListClear();
}
void Core::Entity::BattleNpc::onActionHostile( Core::Entity::ActorPtr pSource )
{
if( hateListGetHighest() == nullptr )
aggro( pSource );
if( getClaimer() == nullptr )
setOwner( pSource->getAsPlayer() );
}
Core::Entity::ActorPtr Core::Entity::BattleNpc::getClaimer() const
{
return m_pOwner;
}
// HACK: this is highly experimental code, will have to be changed eventually
// since there are different types of mobs... (stationary, moving...) likely to be
// handled by scripts entirely.
void Core::Entity::BattleNpc::update( int64_t currTime )
{
if( !isAlive() )
{
m_status = ActorStatus::Idle;
m_mode = MODE_IDLE;
return;
}
m_pStatusEffectContainer->update();
2017-08-08 13:53:47 +02:00
float distance = Math::Util::distance( m_pos.x, m_pos.y, m_pos.z,
m_posOrigin.x, m_posOrigin.y, m_posOrigin.z );
if( ( distance > 70 ) && m_mode != MODE_RETREAT )
{
changeTarget( INVALID_GAME_OBJECT_ID );
m_mode = MODE_RETREAT;
hateListClear();
setOwner( nullptr );
}
switch( m_mode )
{
case MODE_RETREAT:
{
if( moveTo( m_posOrigin ) )
m_mode = MODE_IDLE;
}
break;
case MODE_IDLE:
{
ActorPtr pClosestActor = getClosestActor();
if( ( pClosestActor != nullptr ) && pClosestActor->isAlive() )
{
distance = Math::Util::distance( getPos().x, getPos().y, getPos().z,
pClosestActor->getPos().x,
pClosestActor->getPos().y,
pClosestActor->getPos().z );
//if( distance < 8 && getbehavior() == 2 )
// aggro( pClosestActor );
}
}
break;
case MODE_COMBAT:
{
ActorPtr pClosestActor = hateListGetHighest();
if( pClosestActor != nullptr && !pClosestActor->isAlive() )
{
hateListRemove( pClosestActor );
pClosestActor = hateListGetHighest();
}
if( pClosestActor != nullptr )
{
distance = Math::Util::distance( getPos().x, getPos().y, getPos().z,
pClosestActor->getPos().x,
pClosestActor->getPos().y,
pClosestActor->getPos().z );
if( distance > 4 )
moveTo( pClosestActor->getPos() );
else
{
if( face( pClosestActor->getPos() ) )
sendPositionUpdate();
// in combat range. ATTACK!
autoAttack( pClosestActor );
}
}
else
{
changeTarget( INVALID_GAME_OBJECT_ID );
setStance( Stance::Passive );
setOwner( nullptr );
m_mode = MODE_RETREAT;
}
}
break;
}
}
uint32_t Core::Entity::BattleNpc::getTimeOfDeath() const
{
return m_timeOfDeath;
}
void Core::Entity::BattleNpc::setTimeOfDeath( uint32_t tod )
{
m_timeOfDeath = tod;
}