1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-26 06:27:45 +00:00

Implementation of the state machine for bnpcs, missing hp recovering after lost hate.

This commit is contained in:
Mordred 2023-03-20 10:29:56 +01:00
parent c7b50ca1e9
commit c97a67ffe6
24 changed files with 660 additions and 309 deletions

View file

@ -1,38 +0,0 @@
#include <cstdint>
#include <ForwardsZone.h>
#include <Actor/BNpc.h>
#include "Fsm.h"
#include "FsmState.h"
#pragma once
using namespace Sapphire;
using namespace Sapphire::World;
AI::FsmStatePtr AI::Fsm::addState( FsmStatePtr state )
{
m_states.push_back( state );
return state;
}
void AI::Fsm::setCurrentState( FsmStatePtr state )
{
m_pCurrentState = state;
}
void AI::Fsm::update( Entity::BNpc& bnpc, float deltaTime )
{
if( !m_pCurrentState )
return;
FsmTransitionPtr transition = m_pCurrentState->getTriggeredTransition( bnpc );
if( transition )
{
m_pCurrentState->onExit( bnpc );
m_pCurrentState = transition->getTargetState();
m_pCurrentState->onEnter( bnpc );
}
m_pCurrentState->onUpdate( bnpc, deltaTime );
}

View file

@ -1,23 +0,0 @@
#include <cstdint>
#include <ForwardsZone.h>
#include <Actor/BNpc.h>
#pragma once
namespace Sapphire::World::AI
{
class Fsm
{
public:
Fsm() = default;
~Fsm() = default;
FsmStatePtr addState( FsmStatePtr state );
void setCurrentState( FsmStatePtr state );
virtual void update( Entity::BNpc& bnpc, float deltaTime );
protected:
std::vector< FsmStatePtr > m_states;
FsmStatePtr m_pCurrentState;
};
}

View file

@ -0,0 +1,95 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include <Util/Util.h>
#include <Util/UtilMath.h>
#pragma once
namespace Sapphire::World::AI::Fsm
{
class Condition
{
public:
Condition() = default;
virtual ~Condition() = default;
virtual bool isConditionMet( Sapphire::Entity::BNpc& src ) const = 0;
virtual bool update( Sapphire::Entity::BNpc& src, float time )
{
if( isConditionMet( src ) )
return true;
return false;
};
};
class RoamNextTimeReachedCondition : public Condition
{
public:
bool isConditionMet( Sapphire::Entity::BNpc& src ) const override
{
if( ( Common::Util::getTimeSeconds() - src.getLastRoamTargetReachedTime() ) > 20 )
return true;
return false;
}
};
class RoamTargetReachedCondition : public Condition
{
public:
bool isConditionMet( Sapphire::Entity::BNpc& src ) const override
{
if( src.isRoamTargetReached() )
return true;
return false;
}
};
class HateListEmptyCondition : public Condition
{
public:
bool isConditionMet( Sapphire::Entity::BNpc& src ) const override
{
if( src.hateListGetHighest() )
return false;
return true;
}
};
class HateListHasEntriesCondition : public Condition
{
public:
bool isConditionMet( Sapphire::Entity::BNpc& src ) const override
{
if( src.hateListGetHighest() )
return true;
return false;
}
};
class SpawnPointDistanceGtMaxDistanceCondition : public Condition
{
public:
bool isConditionMet( Sapphire::Entity::BNpc& src ) const override
{
auto distanceOrig = Common::Util::distance( src.getPos(), src.getSpawnPos() );
if( distanceOrig > 40 )
return true;
return false;
}
};
class IsDeadCondition : public Condition
{
public:
bool isConditionMet( Sapphire::Entity::BNpc& src ) const override
{
if( !src.isAlive() )
return true;
return false;
}
};
}

43
src/world/AI/Fsm/State.h Normal file
View file

@ -0,0 +1,43 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include "Transition.h"
#pragma once
namespace Sapphire::World::AI::Fsm
{
class State
{
public:
virtual ~State() = default;
virtual void onUpdate( Entity::BNpc& bnpc, uint64_t tickCount ) = 0;
virtual void onEnter( Entity::BNpc& bnpc ) { }
virtual void onExit( Entity::BNpc& bnpc ) { }
void addTransition( TransitionPtr transition )
{
m_transitions.push_back( transition );
}
void addTransition( StatePtr targetState, ConditionPtr condition )
{
m_transitions.push_back( make_Transition( targetState, condition ) );
}
TransitionPtr getTriggeredTransition( Entity::BNpc& bnpc )
{
for( auto transition : m_transitions )
{
if( transition->hasTriggered( bnpc ) )
return transition;
}
return nullptr;
}
private:
std::vector< TransitionPtr > m_transitions;
};
}

View file

@ -0,0 +1,79 @@
#include "StateCombat.h"
#include "Actor/BNpc.h"
#include "Logging/Logger.h"
#include <Service.h>
#include <Manager/TerritoryMgr.h>
#include <Territory/Territory.h>
#include <Navi/NaviProvider.h>
using namespace Sapphire::World;
void AI::Fsm::StateCombat::onUpdate( Entity::BNpc& bnpc, uint64_t tickCount )
{
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
auto pZone = teriMgr.getTerritoryByGuId( bnpc.getTerritoryId() );
auto pNaviProvider = pZone->getNaviProvider();
auto pHatedActor = bnpc.hateListGetHighest();
if( !pHatedActor )
return;
pNaviProvider->updateAgentParameters( bnpc );
auto distanceOrig = Common::Util::distance( bnpc.getPos(), bnpc.getSpawnPos() );
if( !pHatedActor->isAlive() || bnpc.getTerritoryId() != pHatedActor->getTerritoryId() )
{
bnpc.hateListRemove( pHatedActor );
pHatedActor = bnpc.hateListGetHighest();
}
if( !pHatedActor )
return;
auto distance = Common::Util::distance( bnpc.getPos(), pHatedActor->getPos() );
if( !bnpc.hasFlag( Entity::NoDeaggro ) )
{
}
if( !bnpc.hasFlag( Entity::Immobile ) && distance > ( bnpc.getNaviTargetReachedDistance() + pHatedActor->getRadius() ) )
{
if( pNaviProvider )
pNaviProvider->setMoveTarget( bnpc, pHatedActor->getPos() );
bnpc.moveTo( *pHatedActor );
}
if( pNaviProvider->syncPosToChara( bnpc ) )
bnpc.sendPositionUpdate();
if( distance < ( bnpc.getNaviTargetReachedDistance() + pHatedActor->getRadius() ) )
{
if( !bnpc.hasFlag( Entity::TurningDisabled ) && bnpc.face( pHatedActor->getPos() ) )
bnpc.sendPositionUpdate();
if( !bnpc.checkAction() )
bnpc.processGambits( tickCount );
// in combat range. ATTACK!
bnpc.autoAttack( pHatedActor );
}
}
void AI::Fsm::StateCombat::onEnter( Entity::BNpc& bnpc )
{
}
void AI::Fsm::StateCombat::onExit( Entity::BNpc& bnpc )
{
bnpc.hateListClear();
bnpc.changeTarget( Common::INVALID_GAME_OBJECT_ID64 );
bnpc.setStance( Common::Stance::Passive );
bnpc.setOwner( nullptr );
}

View file

@ -0,0 +1,20 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include "State.h"
#pragma once
namespace Sapphire::World::AI::Fsm
{
class StateCombat : public State
{
public:
virtual ~StateCombat() = default;
void onUpdate( Entity::BNpc& bnpc, uint64_t tickCount );
void onEnter( Entity::BNpc& bnpc );
void onExit( Entity::BNpc& bnpc );
};
}

View file

@ -0,0 +1,26 @@
#include "StateDead.h"
#include "Actor/BNpc.h"
#include "Logging/Logger.h"
#include <Service.h>
#include <Manager/TerritoryMgr.h>
#include <Territory/Territory.h>
#include <Navi/NaviProvider.h>
using namespace Sapphire::World;
void AI::Fsm::StateDead::onUpdate( Entity::BNpc& bnpc, uint64_t tickCount )
{
}
void AI::Fsm::StateDead::onEnter( Entity::BNpc& bnpc )
{
}
void AI::Fsm::StateDead::onExit( Entity::BNpc& bnpc )
{
}

View file

@ -0,0 +1,20 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include "State.h"
#pragma once
namespace Sapphire::World::AI::Fsm
{
class StateDead : public State
{
public:
virtual ~StateDead() = default;
void onUpdate( Entity::BNpc& bnpc, uint64_t tickCount );
void onEnter( Entity::BNpc& bnpc );
void onExit( Entity::BNpc& bnpc );
};
}

View file

@ -0,0 +1,20 @@
#include "StateIdle.h"
#include "Actor/BNpc.h"
#include "Logging/Logger.h"
using namespace Sapphire::World;
void AI::Fsm::StateIdle::onUpdate( Entity::BNpc& bnpc, uint64_t tickCount )
{
}
void AI::Fsm::StateIdle::onEnter( Entity::BNpc& bnpc )
{
bnpc.setLastRoamTargetReachedTime( Common::Util::getTimeSeconds() );
}
void AI::Fsm::StateIdle::onExit( Entity::BNpc& bnpc )
{
}

View file

@ -0,0 +1,20 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include "State.h"
#pragma once
namespace Sapphire::World::AI::Fsm
{
class StateIdle : public State
{
public:
virtual ~StateIdle() = default;
void onUpdate( Entity::BNpc& bnpc, uint64_t tickCount );
void onEnter( Entity::BNpc& bnpc );
void onExit( Entity::BNpc& bnpc );
};
}

View file

@ -0,0 +1,38 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include "StateMachine.h"
#include "State.h"
#pragma once
using namespace Sapphire;
using namespace Sapphire::World;
AI::Fsm::StatePtr AI::Fsm::StateMachine::addState( Fsm::StatePtr state )
{
m_states.push_back( state );
return state;
}
void AI::Fsm::StateMachine::setCurrentState( Fsm::StatePtr state )
{
m_pCurrentState = state;
}
void AI::Fsm::StateMachine::update( Entity::BNpc& bnpc, uint64_t tickCount )
{
if( !m_pCurrentState )
return;
TransitionPtr transition = m_pCurrentState->getTriggeredTransition( bnpc );
if( transition )
{
m_pCurrentState->onExit( bnpc );
m_pCurrentState = transition->getTargetState();
m_pCurrentState->onEnter( bnpc );
}
m_pCurrentState->onUpdate( bnpc, tickCount );
}

View file

@ -0,0 +1,23 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#pragma once
namespace Sapphire::World::AI::Fsm
{
class StateMachine
{
public:
StateMachine() = default;
~StateMachine() = default;
StatePtr addState( StatePtr state );
void setCurrentState( StatePtr state );
virtual void update( Entity::BNpc& bnpc, uint64_t tickCount );
protected:
std::vector< StatePtr > m_states;
StatePtr m_pCurrentState;
};
}

View file

@ -0,0 +1,41 @@
#include "StateRetreat.h"
#include "Actor/BNpc.h"
#include "Logging/Logger.h"
#include <Service.h>
#include <Manager/TerritoryMgr.h>
#include <Territory/Territory.h>
#include <Navi/NaviProvider.h>
using namespace Sapphire::World;
void AI::Fsm::StateRetreat::onUpdate( Entity::BNpc& bnpc, uint64_t tickCount )
{
if( bnpc.moveTo( bnpc.getSpawnPos() ) )
{
bnpc.setRoamTargetReached( true );
bnpc.setLastRoamTargetReachedTime( Common::Util::getTimeSeconds() );
}
}
void AI::Fsm::StateRetreat::onEnter( Entity::BNpc& bnpc )
{
bnpc.setRoamTargetReached( false );
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
auto pZone = teriMgr.getTerritoryByGuId( bnpc.getTerritoryId() );
auto pNaviProvider = pZone->getNaviProvider();
bnpc.setInvincibilityType( Common::InvincibilityType::InvincibilityIgnoreDamage );
if( pNaviProvider )
pNaviProvider->setMoveTarget( bnpc, bnpc.getSpawnPos() );
}
void AI::Fsm::StateRetreat::onExit( Entity::BNpc& bnpc )
{
bnpc.setOwner( nullptr );
bnpc.setRoamTargetReached( false );
bnpc.setInvincibilityType( Common::InvincibilityType::InvincibilityNone );
}

View file

@ -0,0 +1,20 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include "State.h"
#pragma once
namespace Sapphire::World::AI::Fsm
{
class StateRetreat : public State
{
public:
virtual ~StateRetreat() = default;
void onUpdate( Entity::BNpc& bnpc, uint64_t tickCount );
void onEnter( Entity::BNpc& bnpc );
void onExit( Entity::BNpc& bnpc );
};
}

View file

@ -0,0 +1,49 @@
#include "StateRoam.h"
#include "Actor/BNpc.h"
#include "Logging/Logger.h"
#include <Service.h>
#include <Manager/TerritoryMgr.h>
#include <Territory/Territory.h>
#include <Navi/NaviProvider.h>
using namespace Sapphire::World;
void AI::Fsm::StateRoam::onUpdate( Entity::BNpc& bnpc, uint64_t tickCount )
{
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
auto pZone = teriMgr.getTerritoryByGuId( bnpc.getTerritoryId() );
auto pNaviProvider = pZone->getNaviProvider();
if( pNaviProvider )
pNaviProvider->setMoveTarget( bnpc, bnpc.getRoamTargetPos() );
if( bnpc.moveTo( bnpc.getRoamTargetPos() ) )
{
bnpc.setRoamTargetReached( true );
bnpc.setLastRoamTargetReachedTime( Common::Util::getTimeSeconds() );
}
}
void AI::Fsm::StateRoam::onEnter( Entity::BNpc& bnpc )
{
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
auto pZone = teriMgr.getTerritoryByGuId( bnpc.getTerritoryId() );
auto pNaviProvider = pZone->getNaviProvider();
if( !pNaviProvider )
{
bnpc.setRoamTargetReached( true );
return;
}
auto pos = pNaviProvider->findRandomPositionInCircle( bnpc.getSpawnPos(), bnpc.getInstanceObjectInfo()->WanderingRange );
bnpc.setRoamTargetPos( pos );
}
void AI::Fsm::StateRoam::onExit( Entity::BNpc& bnpc )
{
bnpc.setRoamTargetReached( false );
}

View file

@ -0,0 +1,20 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include "State.h"
#pragma once
namespace Sapphire::World::AI::Fsm
{
class StateRoam : public State
{
public:
virtual ~StateRoam() = default;
void onUpdate( Entity::BNpc& bnpc, uint64_t tickCount );
void onEnter( Entity::BNpc& bnpc );
void onExit( Entity::BNpc& bnpc );
};
}

View file

@ -0,0 +1,22 @@
#include <cstdint>
#include "ForwardsZone.h"
#include "Actor/BNpc.h"
#include "AI/Fsm/Condition.h"
#pragma once
namespace Sapphire::World::AI::Fsm
{
class Transition
{
public:
Transition( StatePtr targetState, ConditionPtr condition ) : m_pTargetState( targetState ), m_pCondition( condition ) { }
virtual ~Transition() = default;
StatePtr getTargetState() { return m_pTargetState; }
bool hasTriggered( Entity::BNpc& bnpc ) { return m_pCondition->isConditionMet( bnpc ); }
private:
StatePtr m_pTargetState;
ConditionPtr m_pCondition;
};
}

View file

@ -1,23 +0,0 @@
#include <cstdint>
#include <ForwardsZone.h>
#include <Actor/BNpc.h>
#pragma once
namespace Sapphire::World::AI
{
class FsmCondition
{
public:
FsmCondition() = default;
virtual ~FsmCondition() = default;
virtual bool isConditionMet( Sapphire::Entity::BNpc& src ) const = 0;
virtual bool update( Sapphire::Entity::BNpc& src, float time )
{
if( isConditionMet( src ) )
return true;
return false;
};
};
}

View file

@ -1,37 +0,0 @@
#include <cstdint>
#include <ForwardsZone.h>
#include <Actor/BNpc.h>
#include "FsmTransition.h"
#pragma once
namespace Sapphire::World::AI
{
class FsmState
{
public:
virtual ~FsmState() = default;
virtual void onUpdate( Entity::BNpc& bnpc, float deltaTime ) = 0;
virtual void onEnter( Entity::BNpc& bnpc ) { }
virtual void onExit( Entity::BNpc& bnpc ) { }
void addTransition( FsmTransitionPtr transition )
{
m_transitions.push_back( transition );
}
FsmTransitionPtr getTriggeredTransition( Entity::BNpc& bnpc )
{
for( auto transition : m_transitions )
{
if( transition->hasTriggered( bnpc ) )
return transition;
}
return nullptr;
}
private:
std::vector< FsmTransitionPtr > m_transitions;
};
}

View file

@ -1,22 +0,0 @@
#include <cstdint>
#include <ForwardsZone.h>
#include <Actor/BNpc.h>
#include "FsmCondition.h"
#pragma once
namespace Sapphire::World::AI
{
class FsmTransition
{
public:
FsmTransition( FsmStatePtr targetState, FsmConditionPtr condition ) : m_pTargetState( targetState ), m_pCondition( condition ) { }
virtual ~FsmTransition() = default;
FsmStatePtr getTargetState() { return m_pTargetState; }
bool hasTriggered( Entity::BNpc& bnpc ) { return m_pCondition->isConditionMet( bnpc ); }
private:
FsmStatePtr m_pTargetState;
FsmConditionPtr m_pCondition;
};
}

View file

@ -47,6 +47,13 @@
#include <Action/Action.h>
#include <AI/GambitRule.h>
#include <AI/GambitTargetCondition.h>
#include <AI/Fsm/StateMachine.h>
#include <AI/Fsm/Condition.h>
#include <AI/Fsm/StateIdle.h>
#include <AI/Fsm/StateRoam.h>
#include <AI/Fsm/StateCombat.h>
#include <AI/Fsm/StateRetreat.h>
#include <AI/Fsm/StateDead.h>
using namespace Sapphire;
using namespace Sapphire::World;
@ -88,8 +95,7 @@ BNpc::BNpc( uint32_t id, std::shared_ptr< Common::BNPCInstanceObject > pInfo, co
m_flags = 0;
m_rank = pInfo->BNPCRankId;
if( pInfo->WanderingRange == 0 || pInfo->BoundInstanceID != 0 )
setFlag( Immobile );
// Striking Dummy
if( pInfo->NameId == 541 )
@ -107,6 +113,9 @@ BNpc::BNpc( uint32_t id, std::shared_ptr< Common::BNPCInstanceObject > pInfo, co
m_modelChara = bNpcBaseData->data().Model;
m_enemyType = bNpcBaseData->data().Battalion;
if( pInfo->WanderingRange == 0 || pInfo->BoundInstanceID != 0 || m_enemyType == 0 )
setFlag( Immobile );
m_class = ClassJob::Gladiator;
m_territoryTypeId = zone.getTerritoryTypeId();
@ -324,7 +333,7 @@ uint32_t BNpc::getBNpcNameId() const
void BNpc::spawn( PlayerPtr pTarget )
{
m_lastRoamTargetReached = Common::Util::getTimeSeconds();
m_lastRoamTargetReachedTime = Common::Util::getTimeSeconds();
auto& server = Common::Service< World::WorldServer >::ref();
server.queueForPlayer( pTarget->getCharacterId(), std::make_shared< NpcSpawnPacket >( *this, *pTarget ) );
@ -638,165 +647,8 @@ void BNpc::onTick()
void BNpc::update( uint64_t tickCount )
{
auto& teriMgr = Common::Service< World::Manager::TerritoryMgr >::ref();
auto pZone = teriMgr.getTerritoryByGuId( getTerritoryId() );
const uint8_t maxDistanceToOrigin = 40;
const uint32_t roamTick = 20;
auto pNaviProvider = pZone->getNaviProvider();
if( !pNaviProvider )
return;
Chara::update( tickCount );
if( !checkAction() )
processGambits( tickCount );
switch( m_state )
{
case BNpcState::Dead:
case BNpcState::JustDied:
return;
case BNpcState::Retreat:
{
setInvincibilityType( InvincibilityType::InvincibilityIgnoreDamage );
if( pNaviProvider )
pNaviProvider->setMoveTarget( *this, m_spawnPos );
if( moveTo( m_spawnPos ) )
{
setInvincibilityType( InvincibilityType::InvincibilityNone );
// retail doesn't seem to roam straight after retreating
// todo: perhaps requires more investigation?
m_lastRoamTargetReached = Common::Util::getTimeSeconds();
// resetHp
setHp( getMaxHp() );
m_state = BNpcState::Idle;
setOwner( nullptr );
}
}
break;
case BNpcState::Roaming:
{
if( pNaviProvider )
pNaviProvider->setMoveTarget( *this, m_roamPos );
if( moveTo( m_roamPos ) )
{
m_lastRoamTargetReached = Common::Util::getTimeSeconds();
m_state = BNpcState::Idle;
}
checkAggro();
}
break;
case BNpcState::Idle:
{
auto pHatedActor = hateListGetHighest();
if( pHatedActor )
aggro( pHatedActor );
if( pNaviProvider->syncPosToChara( *this ) )
sendPositionUpdate();
if( !hasFlag( Immobile ) && ( Common::Util::getTimeSeconds() - m_lastRoamTargetReached > roamTick ) )
{
if( !pNaviProvider )
{
m_lastRoamTargetReached = Common::Util::getTimeSeconds();
break;
}
if( m_pInfo->WanderingRange != 0 && getEnemyType() != 0 )
{
m_roamPos = pNaviProvider->findRandomPositionInCircle( m_spawnPos, m_pInfo->WanderingRange );
}
else
{
m_roamPos = m_spawnPos;
}
m_state = BNpcState::Roaming;
}
checkAggro();
break;
}
case BNpcState::Combat:
{
auto pHatedActor = hateListGetHighest();
if( !pHatedActor )
return;
pNaviProvider->updateAgentParameters( *this );
auto distanceOrig = Common::Util::distance( getPos(), m_spawnPos );
if( !pHatedActor->isAlive() || getTerritoryId() != pHatedActor->getTerritoryId() )
{
hateListRemove( pHatedActor );
pHatedActor = hateListGetHighest();
}
if( !pHatedActor )
{
changeTarget( INVALID_GAME_OBJECT_ID64 );
setStance( Stance::Passive );
//setOwner( nullptr );
m_state = BNpcState::Retreat;
pNaviProvider->updateAgentParameters( *this );
break;
}
auto distance = Common::Util::distance( getPos(), pHatedActor->getPos() );
if( !hasFlag( NoDeaggro ) && ( ( distanceOrig > maxDistanceToOrigin ) || distance > 30.0f ) )
{
hateListClear();
changeTarget( INVALID_GAME_OBJECT_ID64 );
setStance( Stance::Passive );
setOwner( nullptr );
m_state = BNpcState::Retreat;
break;
}
if( distance > ( getNaviTargetReachedDistance() + pHatedActor->getRadius() ) )
{
if( hasFlag( Immobile ) )
break;
if( pNaviProvider )
pNaviProvider->setMoveTarget( *this, pHatedActor->getPos() );
moveTo( *pHatedActor );
}
if( pNaviProvider->syncPosToChara( *this ) )
sendPositionUpdate();
if( distance < ( getNaviTargetReachedDistance() + pHatedActor->getRadius() ) )
{
if( !hasFlag( TurningDisabled ) && face( pHatedActor->getPos() ) )
sendPositionUpdate();
// in combat range. ATTACK!
autoAttack( pHatedActor );
}
}
break;
}
m_fsm->update( *this, tickCount );
}
void BNpc::restHp()
@ -1054,12 +906,41 @@ void BNpc::init()
m_maxHp = Math::CalcStats::calculateMaxHp( *getAsChara() );
m_hp = m_maxHp;
m_lastRoamTargetReachedTime = Common::Util::getTimeSeconds();
//setup a test gambit
auto testGambitRule = AI::make_GambitRule( AI::make_TopHateTargetCondition(), Action::make_Action( getAsChara(), 88, 0 ), 5000 );
auto testGambitRule1 = AI::make_GambitRule( AI::make_HPSelfPctLessThanTargetCondition( 50 ), Action::make_Action( getAsChara(), 120, 0 ), 5000 );
m_gambits.push_back( testGambitRule );
m_gambits.push_back( testGambitRule1 );
using namespace AI::Fsm;
m_fsm = make_StateMachine();
auto stateIdle = make_StateIdle();
auto stateCombat = make_StateCombat();
auto stateDead = make_StateDead();
if( !hasFlag( Immobile ) )
{
auto stateRoam = make_StateRoam();
stateIdle->addTransition( stateRoam, make_RoamNextTimeReachedCondition() );
stateRoam->addTransition( stateIdle, make_RoamTargetReachedCondition() );
stateRoam->addTransition( stateCombat, make_HateListHasEntriesCondition() );
stateRoam->addTransition( stateDead, make_IsDeadCondition() );
m_fsm->addState( stateRoam );
}
stateIdle->addTransition( stateCombat, make_HateListHasEntriesCondition() );
stateCombat->addTransition( stateIdle, make_HateListEmptyCondition() );
stateIdle->addTransition( stateDead, make_IsDeadCondition() );
stateCombat->addTransition( stateDead, make_IsDeadCondition() );
m_fsm->addState( stateIdle );
if( !hasFlag( NoDeaggro ) )
{
auto stateRetreat = make_StateRetreat();
stateCombat->addTransition( stateRetreat, make_SpawnPointDistanceGtMaxDistanceCondition() );
stateRetreat->addTransition( stateIdle, make_RoamTargetReachedCondition() );
}
m_fsm->setCurrentState( stateIdle );
}
void BNpc::processGambits( uint64_t tickCount )
@ -1082,3 +963,43 @@ void BNpc::processGambits( uint64_t tickCount )
}
}
uint32_t BNpc::getLastRoamTargetReachedTime() const
{
return m_lastRoamTargetReachedTime;
}
void BNpc::setLastRoamTargetReachedTime( uint32_t time )
{
m_lastRoamTargetReachedTime = time;
}
std::shared_ptr< Common::BNPCInstanceObject > BNpc::getInstanceObjectInfo() const
{
return m_pInfo;
}
void BNpc::setRoamTargetReached( bool reached )
{
m_roamTargetReached = reached;
}
bool BNpc::isRoamTargetReached() const
{
return m_roamTargetReached;
}
void BNpc::setRoamTargetPos( const FFXIVARR_POSITION3& targetPos )
{
m_roamPos = targetPos;
}
const Common::FFXIVARR_POSITION3& BNpc::getRoamTargetPos() const
{
return m_roamPos;
}
const Common::FFXIVARR_POSITION3& BNpc::getSpawnPos() const
{
return m_spawnPos;
}

View file

@ -151,6 +151,19 @@ namespace Sapphire::Entity
void processGambits( uint64_t tickCount );
uint32_t getLastRoamTargetReachedTime() const;
void setLastRoamTargetReachedTime( uint32_t time );
std::shared_ptr< Common::BNPCInstanceObject > getInstanceObjectInfo() const;
void setRoamTargetReached( bool reached );
bool isRoamTargetReached() const;
void setRoamTargetPos( const Common::FFXIVARR_POSITION3& targetPos );
const Common::FFXIVARR_POSITION3& getRoamTargetPos() const;
const Common::FFXIVARR_POSITION3& getSpawnPos() const;
private:
uint32_t m_bNpcBaseId;
uint32_t m_bNpcNameId;
@ -178,7 +191,8 @@ namespace Sapphire::Entity
std::shared_ptr< Common::BNPCInstanceObject > m_pInfo;
uint32_t m_timeOfDeath;
uint32_t m_lastRoamTargetReached;
uint32_t m_lastRoamTargetReachedTime;
bool m_roamTargetReached{ false };
Common::FFXIVARR_POSITION3 m_spawnPos;
Common::FFXIVARR_POSITION3 m_roamPos;
@ -194,6 +208,8 @@ namespace Sapphire::Entity
CharaPtr m_pOwner;
std::vector< World::AI::GambitRulePtr > m_gambits;
std::shared_ptr< World::AI::Fsm::StateMachine > m_fsm;
};
}

View file

@ -8,6 +8,7 @@ file( GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
Actor/*.cpp
Action/*.cpp
AI/*.cpp
AI/Fsm/*.cpp
ContentFinder/*.cpp
DebugCommand/*.cpp
Event/*.cpp

View file

@ -54,11 +54,31 @@ namespace World::AI
TYPE_FORWARD( HPSelfPctLessThanTargetCondition );
TYPE_FORWARD( GambitRule );
}
namespace World::AI::Fsm
{
TYPE_FORWARD( Condition );
TYPE_FORWARD( State );
TYPE_FORWARD( Transition );
TYPE_FORWARD( StateMachine );
TYPE_FORWARD( StateIdle );
TYPE_FORWARD( StateRoam );
TYPE_FORWARD( StateCombat );
TYPE_FORWARD( StateRetreat );
TYPE_FORWARD( StateDead );
TYPE_FORWARD( RoamNextTimeReachedCondition );
TYPE_FORWARD( RoamTargetReachedCondition );
TYPE_FORWARD( HateListEmptyCondition );
TYPE_FORWARD( HateListHasEntriesCondition );
TYPE_FORWARD( SpawnPointDistanceGtMaxDistanceCondition );
TYPE_FORWARD( IsDeadCondition );
TYPE_FORWARD( FsmCondition );
TYPE_FORWARD( FsmState );
TYPE_FORWARD( FsmTransition );
TYPE_FORWARD( Fsm );
}
namespace Inventory