diff --git a/data/actions/player.json b/data/actions/player.json index 149d05aa..e6c9cb3e 100644 --- a/data/actions/player.json +++ b/data/actions/player.json @@ -1,7 +1,7 @@ { "7": { "name": "Attack", - "potency": 0, + "potency": 110, "comboPotency": 0, "flankPotency": 0, "frontPotency": 0, @@ -16,7 +16,7 @@ }, "8": { "name": "Shot", - "potency": 0, + "potency": 100, "comboPotency": 0, "flankPotency": 0, "frontPotency": 0, diff --git a/src/world/AI/GambitRule.cpp b/src/world/AI/GambitRule.cpp new file mode 100644 index 00000000..9fb40e63 --- /dev/null +++ b/src/world/AI/GambitRule.cpp @@ -0,0 +1,52 @@ +#include +#include +#include "GambitTargetCondition.h" +#include "GambitRule.h" + +using namespace Sapphire; +using namespace Sapphire::World; + +AI::GambitRule::GambitRule( const GambitTargetConditionPtr targetCondition, Action::ActionPtr action, uint32_t coolDown ) : + m_targetCondition( targetCondition ), + m_pAction( std::move( action ) ), + m_lastExecutionMs( 0 ), + m_coolDownMs( coolDown ), + m_isEnabled( true ) +{ + +} + +void AI::GambitRule::toggleEnabled() +{ + m_isEnabled = !m_isEnabled; +} + +bool AI::GambitRule::isEnabled() const +{ + return m_isEnabled; +} + +uint64_t AI::GambitRule::getLastExecutionMs() const +{ + return m_lastExecutionMs; +} + +void AI::GambitRule::setLastExecutionMs( uint64_t lastExecution ) +{ + m_lastExecutionMs = lastExecution; +} + +uint32_t AI::GambitRule::getCoolDown() const +{ + return m_coolDownMs; +} + +AI::GambitTargetConditionPtr AI::GambitRule::getGambitTargetCondition() +{ + return m_targetCondition; +} + +Action::ActionPtr AI::GambitRule::getActionPtr() +{ + return m_pAction; +} \ No newline at end of file diff --git a/src/world/AI/GambitRule.h b/src/world/AI/GambitRule.h new file mode 100644 index 00000000..c8a43648 --- /dev/null +++ b/src/world/AI/GambitRule.h @@ -0,0 +1,33 @@ +#include +#include +#include "GambitTargetCondition.h" + +#pragma once + +namespace Sapphire::World::AI +{ + class GambitRule + { + public: + GambitRule( GambitTargetConditionPtr targetCondition, Action::ActionPtr action, uint32_t coolDown ); + ~GambitRule() = default; + + bool isEnabled() const; + void toggleEnabled(); + + uint64_t getLastExecutionMs() const; + void setLastExecutionMs( uint64_t lastExecution ); + + uint32_t getCoolDown() const; + + GambitTargetConditionPtr getGambitTargetCondition(); + + Action::ActionPtr getActionPtr(); + private: + GambitTargetConditionPtr m_targetCondition; + Action::ActionPtr m_pAction; + uint32_t m_coolDownMs; + uint64_t m_lastExecutionMs; + bool m_isEnabled; + }; +} \ No newline at end of file diff --git a/src/world/AI/GambitTargetCondition.h b/src/world/AI/GambitTargetCondition.h new file mode 100644 index 00000000..ec0cd7ae --- /dev/null +++ b/src/world/AI/GambitTargetCondition.h @@ -0,0 +1,66 @@ +#include +#include +#include + +#pragma once + +namespace Sapphire::World::AI +{ + enum GambitTargetType : uint8_t + { + Self, + Player, + PlayerAndAlly, + Ally, + BNpc + }; + + class GambitTargetCondition + { + public: + GambitTargetCondition( GambitTargetType targetType ) : m_targetType( targetType ) {}; + virtual ~GambitTargetCondition() = default; + + virtual bool isConditionMet( Sapphire::Entity::BNpc& src ) { return false; }; + Sapphire::Entity::CharaPtr getTarget() const { return m_pTarget; }; + protected: + GambitTargetType m_targetType; + Sapphire::Entity::CharaPtr m_pTarget; + }; + + class TopHateTargetCondition : public GambitTargetCondition + { + public: + TopHateTargetCondition() : GambitTargetCondition( PlayerAndAlly ) {}; + + bool isConditionMet( Sapphire::Entity::BNpc& src ) override + { + auto foundChara = src.hateListGetHighest(); + if( foundChara ) + { + m_pTarget = foundChara; + return true; + } + return false; + }; + }; + + class HPSelfPctLessThan : public GambitTargetCondition + { + public: + HPSelfPctLessThan( uint8_t pct ) : GambitTargetCondition( Self ), m_HpPct( pct ) {}; + + virtual bool isConditionMet( Sapphire::Entity::BNpc& src ) + { + if( src.getHpPercent() < m_HpPct ) + { + m_pTarget = src.getAsBNpc(); + return true; + } + return false; + }; + private: + uint8_t m_HpPct; + }; + +} \ No newline at end of file diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index f353a88b..2224e089 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -44,7 +44,12 @@ #include #include +#include +#include +#include + using namespace Sapphire; +using namespace Sapphire::World; using namespace Sapphire::Common; using namespace Sapphire::Entity; using namespace Sapphire::Network::Packets; @@ -319,7 +324,7 @@ uint32_t BNpc::getBNpcNameId() const void BNpc::spawn( PlayerPtr pTarget ) { - m_lastRoamTargetReached = Util::getTimeSeconds(); + m_lastRoamTargetReached = Common::Util::getTimeSeconds(); auto& server = Common::Service< World::WorldServer >::ref(); server.queueForPlayer( pTarget->getCharacterId(), std::make_shared< NpcSpawnPacket >( *this, *pTarget ) ); @@ -355,7 +360,7 @@ bool BNpc::moveTo( const FFXIVARR_POSITION3& pos ) } auto pos1 = pNaviProvider->getMovePos( *this ); - auto distance = Util::distance( pos1, pos ); + auto distance = Common::Util::distance( pos1, pos ); if( distance < getNaviTargetReachedDistance() ) { @@ -393,7 +398,7 @@ bool BNpc::moveTo( const Chara& targetChara ) } auto pos1 = pNaviProvider->getMovePos( *this ); - auto distance = Util::distance( pos1, targetChara.getPos() ); + auto distance = Common::Util::distance( pos1, targetChara.getPos() ); if( distance <= ( getNaviTargetReachedDistance() + targetChara.getRadius() ) ) { @@ -584,7 +589,7 @@ void BNpc::aggro( const Sapphire::Entity::CharaPtr& pChara ) auto& pRNGMgr = Common::Service< World::Manager::RNGMgr >::ref(); auto variation = static_cast< uint32_t >( pRNGMgr.getRandGenerator< float >( 500, 1000 ).next() ); - m_lastAttack = Util::getTimeMs() + variation; + m_lastAttack = Common::Util::getTimeMs() + variation; setStance( Stance::Active ); m_state = BNpcState::Combat; @@ -644,7 +649,8 @@ void BNpc::update( uint64_t tickCount ) if( !pNaviProvider ) return; - checkAction(); + if( !checkAction() ) + processGambits( tickCount ); switch( m_state ) { @@ -665,7 +671,7 @@ void BNpc::update( uint64_t tickCount ) // retail doesn't seem to roam straight after retreating // todo: perhaps requires more investigation? - m_lastRoamTargetReached = Util::getTimeSeconds(); + m_lastRoamTargetReached = Common::Util::getTimeSeconds(); // resetHp setHp( getMaxHp() ); @@ -684,7 +690,7 @@ void BNpc::update( uint64_t tickCount ) if( moveTo( m_roamPos ) ) { - m_lastRoamTargetReached = Util::getTimeSeconds(); + m_lastRoamTargetReached = Common::Util::getTimeSeconds(); m_state = BNpcState::Idle; } @@ -701,12 +707,12 @@ void BNpc::update( uint64_t tickCount ) if( pNaviProvider->syncPosToChara( *this ) ) sendPositionUpdate(); - if( !hasFlag( Immobile ) && ( Util::getTimeSeconds() - m_lastRoamTargetReached > roamTick ) ) + if( !hasFlag( Immobile ) && ( Common::Util::getTimeSeconds() - m_lastRoamTargetReached > roamTick ) ) { if( !pNaviProvider ) { - m_lastRoamTargetReached = Util::getTimeSeconds(); + m_lastRoamTargetReached = Common::Util::getTimeSeconds(); break; } if( m_pInfo->WanderingRange != 0 && getEnemyType() != 0 ) @@ -732,7 +738,7 @@ void BNpc::update( uint64_t tickCount ) pNaviProvider->updateAgentParameters( *this ); - auto distanceOrig = Util::distance( getPos().x, getPos().y, getPos().z, m_spawnPos.x, m_spawnPos.y, m_spawnPos.z ); + auto distanceOrig = Common::Util::distance( getPos(), m_spawnPos ); if( pHatedActor && !pHatedActor->isAlive() ) { @@ -742,8 +748,7 @@ void BNpc::update( uint64_t tickCount ) if( pHatedActor ) { - auto distance = Util::distance( getPos().x, getPos().y, getPos().z, - pHatedActor->getPos().x, pHatedActor->getPos().y, pHatedActor->getPos().z ); + auto distance = Common::Util::distance( getPos(), pHatedActor->getPos() ); if( !hasFlag( NoDeaggro ) && ( ( distanceOrig > maxDistanceToOrigin ) || distance > 30.0f ) ) { @@ -828,7 +833,7 @@ void BNpc::onDeath() setTargetId( INVALID_GAME_OBJECT_ID64 ); m_currentStance = Stance::Passive; m_state = BNpcState::Dead; - m_timeOfDeath = Util::getTimeSeconds(); + m_timeOfDeath = Common::Util::getTimeSeconds(); setOwner( nullptr ); taskMgr.queueTask( World::makeFadeBNpcTask( 10000, getAsBNpc() ) ); @@ -885,7 +890,7 @@ void BNpc::checkAggro() range = std::max< float >( 0.f, range - std::pow( 1.53f, static_cast< float >( levelDiff ) * 0.6f ) ); } - auto distance = Util::distance( getPos(), pClosestChara->getPos() ); + auto distance = Common::Util::distance( getPos(), pClosestChara->getPos() ); if( distance < range ) { @@ -916,7 +921,7 @@ void BNpc::checkAggro() range = std::max< float >( 0.f, range - std::pow( 1.53f, static_cast< float >( levelDiff ) * 0.6f ) ); } - auto distance = Util::distance( getPos(), pClosestChara->getPos() ); + auto distance = Common::Util::distance( getPos(), pClosestChara->getPos() ); if( distance < range ) { @@ -969,7 +974,7 @@ void BNpc::autoAttack( CharaPtr pTarget ) auto& actionMgr = Common::Service< World::Manager::ActionMgr >::ref(); auto& exdData = Common::Service< Data::ExdData >::ref(); - uint64_t tick = Util::getTimeMs(); + uint64_t tick = Common::Util::getTimeMs(); // todo: this needs to use the auto attack delay for the equipped weapon if( ( tick - m_lastAttack ) > 2500 ) @@ -1046,6 +1051,39 @@ uint32_t BNpc::getLayoutId() const void BNpc::init() { + auto& exdData = Common::Service< Data::ExdData >::ref(); m_maxHp = Math::CalcStats::calculateMaxHp( *getAsChara() ); m_hp = m_maxHp; + + //setup a test gambit + auto testGambitRule = std::make_shared< AI::GambitRule >( std::make_shared< AI::TopHateTargetCondition >(), + Action::make_Action( getAsChara(), 88, 0, exdData.getRow< Excel::Action >( 88 ) ), 5000 ); + + auto testGambitRule1 = std::make_shared< AI::GambitRule >( std::make_shared< AI::HPSelfPctLessThan >( 50 ), + Action::make_Action( getAsChara(), 120, 0, exdData.getRow< Excel::Action >( 120 ) ), 5000 ); + + m_gambits.push_back( testGambitRule ); + m_gambits.push_back( testGambitRule1 ); +} + +void BNpc::processGambits( uint64_t tickCount ) +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + auto& actionMgr = Common::Service< World::Manager::ActionMgr >::ref(); + for( auto& gambitRule : m_gambits ) + { + if( !gambitRule->isEnabled() ) + continue; + + if( ( tickCount - gambitRule->getLastExecutionMs() ) > gambitRule->getCoolDown() ) + { + if( !gambitRule->getGambitTargetCondition()->isConditionMet( *this ) ) + continue; + + gambitRule->setLastExecutionMs( tickCount ); + actionMgr.handleTargetedAction( *this, gambitRule->getActionPtr()->getId(), exdData.getRow< Excel::Action >( gambitRule->getActionPtr()->getId() ), + gambitRule->getGambitTargetCondition()->getTarget()->getId(), 0 ); + } + + } } diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index 867f18f3..095a7ce4 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -3,6 +3,7 @@ #include #include "Forwards.h" +#include "ForwardsZone.h" #include "Chara.h" #include "Npc.h" #include @@ -148,6 +149,8 @@ namespace Sapphire::Entity uint32_t getLayoutId() const; + void processGambits( uint64_t tickCount ); + private: uint32_t m_bNpcBaseId; uint32_t m_bNpcNameId; @@ -189,6 +192,7 @@ namespace Sapphire::Entity Common::FFXIVARR_POSITION3 m_naviTarget; CharaPtr m_pOwner; + std::vector< World::AI::GambitRulePtr > m_gambits; }; diff --git a/src/world/CMakeLists.txt b/src/world/CMakeLists.txt index 632151e2..85006991 100644 --- a/src/world/CMakeLists.txt +++ b/src/world/CMakeLists.txt @@ -7,6 +7,7 @@ file( GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp Actor/*.cpp Action/*.cpp + AI/*.cpp ContentFinder/*.cpp DebugCommand/*.cpp Event/*.cpp diff --git a/src/world/ForwardsZone.h b/src/world/ForwardsZone.h index 0f6338f2..a6040794 100644 --- a/src/world/ForwardsZone.h +++ b/src/world/ForwardsZone.h @@ -47,6 +47,12 @@ namespace World::Territory::Housing TYPE_FORWARD( HousingInteriorTerritory ); } +namespace World::AI +{ + TYPE_FORWARD( GambitTargetCondition ); + TYPE_FORWARD( GambitRule ); +} + namespace Inventory { using InventoryContainerPair = std::pair< Common::InventoryType, uint8_t >;