diff --git a/src/world/AI/Fsm.cpp b/src/world/AI/Fsm.cpp deleted file mode 100644 index ce78c6d5..00000000 --- a/src/world/AI/Fsm.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include -#include -#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 ); -} diff --git a/src/world/AI/Fsm.h b/src/world/AI/Fsm.h deleted file mode 100644 index 4477f8de..00000000 --- a/src/world/AI/Fsm.h +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include -#include - -#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; - }; -} \ No newline at end of file diff --git a/src/world/AI/Fsm/Condition.h b/src/world/AI/Fsm/Condition.h new file mode 100644 index 00000000..11fb4804 --- /dev/null +++ b/src/world/AI/Fsm/Condition.h @@ -0,0 +1,95 @@ +#include +#include "ForwardsZone.h" +#include "Actor/BNpc.h" +#include +#include + +#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; + } + }; + +} \ No newline at end of file diff --git a/src/world/AI/Fsm/State.h b/src/world/AI/Fsm/State.h new file mode 100644 index 00000000..d5739e03 --- /dev/null +++ b/src/world/AI/Fsm/State.h @@ -0,0 +1,43 @@ +#include +#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; + }; +} \ No newline at end of file diff --git a/src/world/AI/Fsm/StateCombat.cpp b/src/world/AI/Fsm/StateCombat.cpp new file mode 100644 index 00000000..5bcefd58 --- /dev/null +++ b/src/world/AI/Fsm/StateCombat.cpp @@ -0,0 +1,79 @@ +#include "StateCombat.h" +#include "Actor/BNpc.h" +#include "Logging/Logger.h" +#include + +#include +#include +#include + +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 ); +} + diff --git a/src/world/AI/Fsm/StateCombat.h b/src/world/AI/Fsm/StateCombat.h new file mode 100644 index 00000000..9030331c --- /dev/null +++ b/src/world/AI/Fsm/StateCombat.h @@ -0,0 +1,20 @@ +#include +#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 ); + + }; +} \ No newline at end of file diff --git a/src/world/AI/Fsm/StateDead.cpp b/src/world/AI/Fsm/StateDead.cpp new file mode 100644 index 00000000..15b3a665 --- /dev/null +++ b/src/world/AI/Fsm/StateDead.cpp @@ -0,0 +1,26 @@ +#include "StateDead.h" +#include "Actor/BNpc.h" +#include "Logging/Logger.h" +#include +#include + +#include +#include + +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 ) +{ + +} + diff --git a/src/world/AI/Fsm/StateDead.h b/src/world/AI/Fsm/StateDead.h new file mode 100644 index 00000000..9051288a --- /dev/null +++ b/src/world/AI/Fsm/StateDead.h @@ -0,0 +1,20 @@ +#include +#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 ); + + }; +} \ No newline at end of file diff --git a/src/world/AI/Fsm/StateIdle.cpp b/src/world/AI/Fsm/StateIdle.cpp new file mode 100644 index 00000000..957e4c88 --- /dev/null +++ b/src/world/AI/Fsm/StateIdle.cpp @@ -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 ) +{ +} + diff --git a/src/world/AI/Fsm/StateIdle.h b/src/world/AI/Fsm/StateIdle.h new file mode 100644 index 00000000..fe145bfe --- /dev/null +++ b/src/world/AI/Fsm/StateIdle.h @@ -0,0 +1,20 @@ +#include +#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 ); + + }; +} \ No newline at end of file diff --git a/src/world/AI/Fsm/StateMachine.cpp b/src/world/AI/Fsm/StateMachine.cpp new file mode 100644 index 00000000..ac67ab86 --- /dev/null +++ b/src/world/AI/Fsm/StateMachine.cpp @@ -0,0 +1,38 @@ +#include +#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 ); +} diff --git a/src/world/AI/Fsm/StateMachine.h b/src/world/AI/Fsm/StateMachine.h new file mode 100644 index 00000000..3156b846 --- /dev/null +++ b/src/world/AI/Fsm/StateMachine.h @@ -0,0 +1,23 @@ +#include +#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; + }; +} \ No newline at end of file diff --git a/src/world/AI/Fsm/StateRetreat.cpp b/src/world/AI/Fsm/StateRetreat.cpp new file mode 100644 index 00000000..1f1e937d --- /dev/null +++ b/src/world/AI/Fsm/StateRetreat.cpp @@ -0,0 +1,41 @@ +#include "StateRetreat.h" +#include "Actor/BNpc.h" +#include "Logging/Logger.h" +#include +#include + +#include +#include + +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 ); +} + diff --git a/src/world/AI/Fsm/StateRetreat.h b/src/world/AI/Fsm/StateRetreat.h new file mode 100644 index 00000000..00cfe427 --- /dev/null +++ b/src/world/AI/Fsm/StateRetreat.h @@ -0,0 +1,20 @@ +#include +#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 ); + + }; +} \ No newline at end of file diff --git a/src/world/AI/Fsm/StateRoam.cpp b/src/world/AI/Fsm/StateRoam.cpp new file mode 100644 index 00000000..1b684817 --- /dev/null +++ b/src/world/AI/Fsm/StateRoam.cpp @@ -0,0 +1,49 @@ +#include "StateRoam.h" +#include "Actor/BNpc.h" +#include "Logging/Logger.h" +#include +#include + +#include +#include + +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 ); +} + diff --git a/src/world/AI/Fsm/StateRoam.h b/src/world/AI/Fsm/StateRoam.h new file mode 100644 index 00000000..6f9ba3ca --- /dev/null +++ b/src/world/AI/Fsm/StateRoam.h @@ -0,0 +1,20 @@ +#include +#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 ); + + }; +} \ No newline at end of file diff --git a/src/world/AI/Fsm/Transition.h b/src/world/AI/Fsm/Transition.h new file mode 100644 index 00000000..f5bd3b2f --- /dev/null +++ b/src/world/AI/Fsm/Transition.h @@ -0,0 +1,22 @@ +#include +#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; + }; +} \ No newline at end of file diff --git a/src/world/AI/FsmCondition.h b/src/world/AI/FsmCondition.h deleted file mode 100644 index 41619ac0..00000000 --- a/src/world/AI/FsmCondition.h +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include -#include - -#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; - }; - }; -} \ No newline at end of file diff --git a/src/world/AI/FsmState.h b/src/world/AI/FsmState.h deleted file mode 100644 index cb1b7466..00000000 --- a/src/world/AI/FsmState.h +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include -#include -#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; - }; -} \ No newline at end of file diff --git a/src/world/AI/FsmTransition.h b/src/world/AI/FsmTransition.h deleted file mode 100644 index 982dd874..00000000 --- a/src/world/AI/FsmTransition.h +++ /dev/null @@ -1,22 +0,0 @@ -#include -#include -#include -#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; - }; -} \ No newline at end of file diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index dd38c784..04107a5a 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -47,6 +47,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include 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; +} \ No newline at end of file diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index 095a7ce4..a8804f72 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -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; + }; } \ No newline at end of file diff --git a/src/world/CMakeLists.txt b/src/world/CMakeLists.txt index 85006991..da82a244 100644 --- a/src/world/CMakeLists.txt +++ b/src/world/CMakeLists.txt @@ -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 diff --git a/src/world/ForwardsZone.h b/src/world/ForwardsZone.h index e17eb916..de26643f 100644 --- a/src/world/ForwardsZone.h +++ b/src/world/ForwardsZone.h @@ -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