mirror of
https://github.com/SapphireServer/Sapphire.git
synced 2025-04-26 14:37:44 +00:00
Implementation of the state machine for bnpcs, missing hp recovering after lost hate.
This commit is contained in:
parent
c7b50ca1e9
commit
c97a67ffe6
24 changed files with 660 additions and 309 deletions
|
@ -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 );
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
95
src/world/AI/Fsm/Condition.h
Normal file
95
src/world/AI/Fsm/Condition.h
Normal 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
43
src/world/AI/Fsm/State.h
Normal 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;
|
||||
};
|
||||
}
|
79
src/world/AI/Fsm/StateCombat.cpp
Normal file
79
src/world/AI/Fsm/StateCombat.cpp
Normal 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 );
|
||||
}
|
||||
|
20
src/world/AI/Fsm/StateCombat.h
Normal file
20
src/world/AI/Fsm/StateCombat.h
Normal 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 );
|
||||
|
||||
};
|
||||
}
|
26
src/world/AI/Fsm/StateDead.cpp
Normal file
26
src/world/AI/Fsm/StateDead.cpp
Normal 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 )
|
||||
{
|
||||
|
||||
}
|
||||
|
20
src/world/AI/Fsm/StateDead.h
Normal file
20
src/world/AI/Fsm/StateDead.h
Normal 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 );
|
||||
|
||||
};
|
||||
}
|
20
src/world/AI/Fsm/StateIdle.cpp
Normal file
20
src/world/AI/Fsm/StateIdle.cpp
Normal 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 )
|
||||
{
|
||||
}
|
||||
|
20
src/world/AI/Fsm/StateIdle.h
Normal file
20
src/world/AI/Fsm/StateIdle.h
Normal 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 );
|
||||
|
||||
};
|
||||
}
|
38
src/world/AI/Fsm/StateMachine.cpp
Normal file
38
src/world/AI/Fsm/StateMachine.cpp
Normal 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 );
|
||||
}
|
23
src/world/AI/Fsm/StateMachine.h
Normal file
23
src/world/AI/Fsm/StateMachine.h
Normal 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;
|
||||
};
|
||||
}
|
41
src/world/AI/Fsm/StateRetreat.cpp
Normal file
41
src/world/AI/Fsm/StateRetreat.cpp
Normal 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 );
|
||||
}
|
||||
|
20
src/world/AI/Fsm/StateRetreat.h
Normal file
20
src/world/AI/Fsm/StateRetreat.h
Normal 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 );
|
||||
|
||||
};
|
||||
}
|
49
src/world/AI/Fsm/StateRoam.cpp
Normal file
49
src/world/AI/Fsm/StateRoam.cpp
Normal 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 );
|
||||
}
|
||||
|
20
src/world/AI/Fsm/StateRoam.h
Normal file
20
src/world/AI/Fsm/StateRoam.h
Normal 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 );
|
||||
|
||||
};
|
||||
}
|
22
src/world/AI/Fsm/Transition.h
Normal file
22
src/world/AI/Fsm/Transition.h
Normal 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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue