diff --git a/src/scripts/instances/questbattles/ChasingShadows.cpp b/src/scripts/instances/questbattles/ChasingShadows.cpp index 8963ff11..8fb7740f 100644 --- a/src/scripts/instances/questbattles/ChasingShadows.cpp +++ b/src/scripts/instances/questbattles/ChasingShadows.cpp @@ -84,10 +84,10 @@ public: a2->setFlag( Entity::NoDeaggro ); a3->setFlag( Entity::NoDeaggro ); - a2->hateListAdd( ida, 10000 ); - a3->hateListAdd( ida, 10000 ); - a2->hateListAdd( papa, 10000 ); - a3->hateListAdd( papa, 10000 ); + a2->hateListAddOrUpdate( ida, 10000 ); + a3->hateListAddOrUpdate( ida, 10000 ); + a2->hateListAddOrUpdate( papa, 10000 ); + a3->hateListAddOrUpdate( papa, 10000 ); auto a4 = instance.createBNpcFromLevelEntry( INIT_POP_ENEMY_A_01, 5, 0, 300, 937, @@ -98,8 +98,8 @@ public: a5->setFlag( Entity::NoDeaggro ); auto pPlayer = instance.getPlayerPtr(); - a4->hateListAdd( pPlayer, 1 ); - a5->hateListAdd( pPlayer, 1 ); + a4->hateListAddOrUpdate( pPlayer, 1 ); + a5->hateListAddOrUpdate( pPlayer, 1 ); } if( pair2Spawnd == 0 && bossHpPercent <= 50 ) @@ -112,10 +112,10 @@ public: a2->setFlag( Entity::NoDeaggro ); a3->setFlag( Entity::NoDeaggro ); - a2->hateListAdd( ida, 10000 ); - a3->hateListAdd( ida, 10000 ); - a2->hateListAdd( papa, 10000 ); - a3->hateListAdd( papa, 10000 ); + a2->hateListAddOrUpdate( ida, 10000 ); + a3->hateListAddOrUpdate( ida, 10000 ); + a2->hateListAddOrUpdate( papa, 10000 ); + a3->hateListAddOrUpdate( papa, 10000 ); auto a4 = instance.createBNpcFromLevelEntry( INIT_POP_ENEMY_A_03, 5, 0, 300, 937, instance.getDirectorId(), Common::BNpcType::Enemy ); @@ -125,8 +125,8 @@ public: a5->setFlag( Entity::NoDeaggro ); auto pPlayer = instance.getPlayerPtr(); - a4->hateListAdd( pPlayer, 1 ); - a5->hateListAdd( pPlayer, 1 ); + a4->hateListAddOrUpdate( pPlayer, 1 ); + a5->hateListAddOrUpdate( pPlayer, 1 ); } @@ -142,8 +142,8 @@ public: a5->setFlag( Entity::NoDeaggro ); auto pPlayer = instance.getPlayerPtr(); - a4->hateListAdd( pPlayer, 1 ); - a5->hateListAdd( pPlayer, 1 ); + a4->hateListAddOrUpdate( pPlayer, 1 ); + a5->hateListAddOrUpdate( pPlayer, 1 ); } if( instance.getCountEnemyBNpc() == 0 && successCalled == 0 ) @@ -189,24 +189,24 @@ public: a3->setFlag( Entity::NoDeaggro ); a4->setFlag( Entity::NoDeaggro ); a5->setFlag( Entity::NoDeaggro ); - a1->hateListAdd( a4, 10000 ); - a1->hateListAdd( a5, 10000 ); + a1->hateListAddOrUpdate( a4, 10000 ); + a1->hateListAddOrUpdate( a5, 10000 ); - a2->hateListAdd( player.getAsPlayer(), 1 ); - a2->hateListAdd( a4, 10000 ); - a2->hateListAdd( a5, 10000 ); + a2->hateListAddOrUpdate( player.getAsPlayer(), 1 ); + a2->hateListAddOrUpdate( a4, 10000 ); + a2->hateListAddOrUpdate( a5, 10000 ); - a3->hateListAdd( player.getAsPlayer(), 1 ); - a3->hateListAdd( a4, 10000 ); - a3->hateListAdd( a5, 10000 ); + a3->hateListAddOrUpdate( player.getAsPlayer(), 1 ); + a3->hateListAddOrUpdate( a4, 10000 ); + a3->hateListAddOrUpdate( a5, 10000 ); - a4->hateListAdd( a1, 10000 ); - a4->hateListAdd( a2, 9999 ); - a4->hateListAdd( a3, 9999 ); + a4->hateListAddOrUpdate( a1, 10000 ); + a4->hateListAddOrUpdate( a2, 9999 ); + a4->hateListAddOrUpdate( a3, 9999 ); - a5->hateListAdd( a1, 10000 ); - a5->hateListAdd( a2, 9999 ); - a5->hateListAdd( a3, 9999 ); + a5->hateListAddOrUpdate( a1, 10000 ); + a5->hateListAddOrUpdate( a2, 9999 ); + a5->hateListAddOrUpdate( a3, 9999 ); } }; diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index 713c3472..a9602057 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -33,6 +33,7 @@ #include "Player.h" #include "BNpc.h" #include "BNpcTemplate.h" +#include "Script/ScriptMgr.h" #include "Common.h" @@ -120,6 +121,8 @@ Sapphire::Entity::BNpc::BNpc( uint32_t id, BNpcTemplatePtr pTemplate, float posX m_naviTargetReachedDistance = 4.f; calculateStats(); + auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); + scriptMgr.onBNpcInit( *this ); } Sapphire::Entity::BNpc::~BNpc() = default; @@ -255,6 +258,15 @@ bool Sapphire::Entity::BNpc::moveTo( const Entity::Chara& targetChara ) return false; } +void Sapphire::Entity::BNpc::stopMoving() +{ + auto pNaviProvider = m_pCurrentTerritory->getNaviProvider(); + if( !pNaviProvider ) + return; + sendPositionUpdate(); + pNaviProvider->updateAgentPosition( *this ); +} + void Sapphire::Entity::BNpc::sendPositionUpdate() { uint8_t unk1 = 0x3a; @@ -269,13 +281,14 @@ void Sapphire::Entity::BNpc::sendPositionUpdate() void Sapphire::Entity::BNpc::hateListClear() { - auto it = m_hateList.begin(); - for( auto& listEntry : m_hateList ) + if( m_hateList.empty() ) + return; + + auto hateListCopy = m_hateList; + for( auto& listEntry : hateListCopy ) { - if( isInRangeSet( listEntry->m_pChara ) ) - deaggro( listEntry->m_pChara ); + deaggro( listEntry->m_pChara ); } - m_hateList.clear(); } Sapphire::Entity::CharaPtr Sapphire::Entity::BNpc::hateListGetHighest() @@ -298,25 +311,11 @@ Sapphire::Entity::CharaPtr Sapphire::Entity::BNpc::hateListGetHighest() return nullptr; } -void Sapphire::Entity::BNpc::hateListAdd( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount ) -{ - auto hateEntry = std::make_shared< HateListEntry >(); - hateEntry->m_hateAmount = static_cast< uint32_t >( hateAmount ); - hateEntry->m_pChara = pChara; - - m_hateList.insert( hateEntry ); - if( pChara->isPlayer() ) - { - auto pPlayer = pChara->getAsPlayer(); - pPlayer->hateListAdd( getAsBNpc() ); - } -} - -void Sapphire::Entity::BNpc::hateListUpdate( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount ) +void Sapphire::Entity::BNpc::hateListAddOrUpdate( Sapphire::Entity::CharaPtr pChara, int32_t hateAmount ) { for( auto listEntry : m_hateList ) { - if( listEntry->m_pChara == pChara ) + if( listEntry->m_pChara->getId() == pChara->getId() ) { listEntry->m_hateAmount += static_cast< uint32_t >( hateAmount ); return; @@ -327,21 +326,29 @@ void Sapphire::Entity::BNpc::hateListUpdate( Sapphire::Entity::CharaPtr pChara, hateEntry->m_hateAmount = static_cast< uint32_t >( hateAmount ); hateEntry->m_pChara = pChara; m_hateList.insert( hateEntry ); + if( pChara->isPlayer() ) + { + auto pPlayer = pChara->getAsPlayer(); + pPlayer->hateListAdd( getAsBNpc() ); + } + auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); + scriptMgr.onBNpcHateListAdd( *this, *pChara ); } void Sapphire::Entity::BNpc::hateListRemove( Sapphire::Entity::CharaPtr pChara ) { for( auto listEntry : m_hateList ) { - if( listEntry->m_pChara == pChara ) + if( listEntry->m_pChara->getId() == pChara->getId() ) { - m_hateList.erase( listEntry ); if( pChara->isPlayer() ) { PlayerPtr tmpPlayer = pChara->getAsPlayer(); tmpPlayer->onMobDeaggro( getAsBNpc() ); } + auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); + scriptMgr.onBNpcHateListRemove( *this, *pChara ); return; } } @@ -363,34 +370,28 @@ void Sapphire::Entity::BNpc::aggro( Sapphire::Entity::CharaPtr pChara ) auto variation = static_cast< uint32_t >( pRNGMgr.getRandGenerator< float >( 500, 1000 ).next() ); m_lastAttack = Util::getTimeMs() + variation; - hateListUpdate( pChara, 1 ); changeTarget( pChara->getId() ); setStance( Stance::Active ); m_state = BNpcState::Combat; - sendToInRangeSet( makeActorControl( getId(), ActorControlType::ToggleWeapon, 1, 1, 0 ) ); - sendToInRangeSet( makeActorControl( getId(), ActorControlType::ToggleAggro, 1, 0, 0 ) ); - - if( pChara->isPlayer() ) + if( m_hateList.empty() ) { - PlayerPtr tmpPlayer = pChara->getAsPlayer(); - tmpPlayer->onMobAggro( getAsBNpc() ); + sendToInRangeSet( makeActorControl( getId(), ActorControlType::ToggleWeapon, 1, 1, 0 ) ); + sendToInRangeSet( makeActorControl( getId(), ActorControlType::ToggleAggro, 1, 0, 0 ) ); } + hateListAddOrUpdate( pChara, 1 ); } void Sapphire::Entity::BNpc::deaggro( Sapphire::Entity::CharaPtr pChara ) { - if( !hateListHasActor( pChara ) ) - hateListRemove( pChara ); + hateListRemove( pChara ); - if( pChara->isPlayer() ) + if( m_hateList.empty() ) { - PlayerPtr tmpPlayer = pChara->getAsPlayer(); sendToInRangeSet( makeActorControl( getId(), ActorControlType::ToggleWeapon, 0, 1, 1 ) ); sendToInRangeSet( makeActorControl( getId(), ActorControlType::ToggleAggro, 0, 0, 0 ) ); - tmpPlayer->onMobDeaggro( getAsBNpc() ); } } @@ -405,9 +406,6 @@ void Sapphire::Entity::BNpc::onTick() void Sapphire::Entity::BNpc::update( uint64_t tickCount ) { - const uint8_t maxDistanceToOrigin = 40; - const uint32_t roamTick = 20; - if( getCurrentAction() && getCurrentAction()->hasCastTime() ) { if( m_pCurrentAction->update() ) @@ -415,6 +413,18 @@ void Sapphire::Entity::BNpc::update( uint64_t tickCount ) return; } + auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); + if( !scriptMgr.onBNpcUpdate( *this, tickCount ) ) + doDefaultBNpcUpdate( tickCount ); + + Chara::update( tickCount ); +} + +void Sapphire::Entity::BNpc::doDefaultBNpcUpdate( uint64_t tickCount ) +{ + const uint8_t maxDistanceToOrigin = 40; + const uint32_t roamTick = 20; + auto pNaviProvider = m_pCurrentTerritory->getNaviProvider(); if( !pNaviProvider ) @@ -490,13 +500,14 @@ void Sapphire::Entity::BNpc::update( uint64_t tickCount ) checkAggro(); } + break; case BNpcState::Combat: { pNaviProvider->updateAgentParameters( *this ); auto distanceOrig = Util::distance( getPos().x, getPos().y, getPos().z, - m_spawnPos.x, m_spawnPos.y, m_spawnPos.z ); + m_spawnPos.x, m_spawnPos.y, m_spawnPos.z ); auto pHatedActor = hateListGetHighest(); @@ -553,9 +564,6 @@ void Sapphire::Entity::BNpc::update( uint64_t tickCount ) } } } - - - Chara::update( tickCount ); } void Sapphire::Entity::BNpc::regainHp() @@ -590,6 +598,9 @@ void Sapphire::Entity::BNpc::onDeath() m_timeOfDeath = Util::getTimeSeconds(); setOwner( nullptr ); + auto& scriptMgr = Common::Service< Sapphire::Scripting::ScriptMgr >::ref(); + scriptMgr.onBNpcDeath( *this ); + for( auto& pHateEntry : m_hateList ) { // TODO: handle drops @@ -647,6 +658,8 @@ void Sapphire::Entity::BNpc::checkAggro() void Sapphire::Entity::BNpc::setOwner( Sapphire::Entity::CharaPtr m_pChara ) { + if( ( !m_pOwner && !m_pChara ) || ( m_pOwner && m_pChara && m_pOwner->getId() == m_pChara->getId() ) ) + return; m_pOwner = m_pChara; if( m_pChara != nullptr ) { @@ -754,4 +767,17 @@ void Sapphire::Entity::BNpc::calculateStats() m_baseStats.attack = m_baseStats.str; m_baseStats.attackPotMagic = m_baseStats.inte; m_baseStats.healingPotMagic = m_baseStats.mnd; -} \ No newline at end of file +} + +void Sapphire::Entity::BNpc::setCustomVar( uint32_t varId, uint64_t value ) +{ + m_customVarMap[ varId ] = value; +} + +uint64_t Sapphire::Entity::BNpc::getCustomVar( uint32_t varId ) +{ + auto it = m_customVarMap.find( varId ); + if( it != m_customVarMap.end() ) + return it->second; + return 0; +} diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index 12e084a9..7e693a1c 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -79,6 +79,8 @@ namespace Sapphire::Entity bool moveTo( const Entity::Chara& targetChara ); + void stopMoving(); + void sendPositionUpdate(); BNpcState getState() const; @@ -86,8 +88,7 @@ namespace Sapphire::Entity void hateListClear(); CharaPtr hateListGetHighest(); - void hateListAdd( CharaPtr pChara, int32_t hateAmount ); - void hateListUpdate( CharaPtr pChara, int32_t hateAmount ); + void hateListAddOrUpdate( CharaPtr pChara, int32_t hateAmount ); void hateListRemove( CharaPtr pChara ); bool hateListHasActor( CharaPtr pChara ); @@ -95,6 +96,7 @@ namespace Sapphire::Entity void deaggro( CharaPtr pChara ); void update( uint64_t tickCount ) override; + void doDefaultBNpcUpdate( uint64_t tickCount ); void onTick() override; void onActionHostile( CharaPtr pSource ) override; @@ -121,6 +123,9 @@ namespace Sapphire::Entity void calculateStats() override; + void setCustomVar( uint32_t varId, uint64_t value ); + uint64_t getCustomVar( uint32_t varId ); + private: uint32_t m_bNpcBaseId; uint32_t m_bNpcNameId; @@ -155,6 +160,7 @@ namespace Sapphire::Entity CharaPtr m_pOwner; + std::unordered_map< uint32_t, uint64_t > m_customVarMap; }; } diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index cdc7c414..61dcac63 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -374,6 +374,8 @@ Change the current target and propagate to in range players */ void Sapphire::Entity::Chara::changeTarget( uint64_t targetId ) { + if( m_targetId == targetId ) + return; setTargetId( targetId ); FFXIVPacketBasePtr packet = makeActorControlTarget( m_id, SetTarget, 0, 0, 0, 0, targetId ); sendToInRangeSet( packet ); diff --git a/src/world/Script/NativeScriptApi.cpp b/src/world/Script/NativeScriptApi.cpp index 0e5624a4..5fdb304c 100644 --- a/src/world/Script/NativeScriptApi.cpp +++ b/src/world/Script/NativeScriptApi.cpp @@ -172,6 +172,26 @@ namespace Sapphire::ScriptAPI { } + void BattleNpcScript::onInit( Entity::BNpc& bnpc ) + { + } + + void BattleNpcScript::onUpdate( Entity::BNpc& bnpc, uint64_t tickCount ) + { + } + + void BattleNpcScript::onHateListAdd( Entity::BNpc& bnpc, Entity::Chara& target ) + { + } + + void BattleNpcScript::onHateListRemove( Entity::BNpc& bnpc, Entity::Chara& target ) + { + } + + void BattleNpcScript::onDeath( Entity::BNpc& bnpc ) + { + } + /////////////////////////////////////////////////////////////////// ZoneScript::ZoneScript( uint32_t zoneId ) : diff --git a/src/world/Script/NativeScriptApi.h b/src/world/Script/NativeScriptApi.h index fa8f46fe..98be4c5f 100644 --- a/src/world/Script/NativeScriptApi.h +++ b/src/world/Script/NativeScriptApi.h @@ -201,6 +201,16 @@ namespace Sapphire::ScriptAPI { public: explicit BattleNpcScript( uint32_t npcId ); + + virtual void onInit( Entity::BNpc& bnpc ); + + virtual void onUpdate( Entity::BNpc& bnpc, uint64_t tickCount ); + + virtual void onHateListAdd( Entity::BNpc& bnpc, Entity::Chara& target ); + + virtual void onHateListRemove( Entity::BNpc& bnpc, Entity::Chara& target ); + + virtual void onDeath( Entity::BNpc& bnpc ); }; /*! diff --git a/src/world/Script/ScriptMgr.cpp b/src/world/Script/ScriptMgr.cpp index e1256fb8..e2399fb2 100644 --- a/src/world/Script/ScriptMgr.cpp +++ b/src/world/Script/ScriptMgr.cpp @@ -9,6 +9,7 @@ #include "Territory/QuestBattle.h" #include "Territory/PublicContent.h" #include "Actor/Player.h" +#include "Actor/BNpc.h" #include "Actor/EventObject.h" #include "ServerMgr.h" #include "Event/EventHandler.h" @@ -651,4 +652,64 @@ bool Sapphire::Scripting::ScriptMgr::onPublicContentEnterTerritory( PublicConten } return false; -} \ No newline at end of file +} + +bool Sapphire::Scripting::ScriptMgr::onBNpcInit( Entity::BNpc& bnpc ) +{ + auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::BattleNpcScript >( bnpc.getBNpcBaseId() ); + if( script ) + { + script->onInit( bnpc ); + return true; + } + + return false; +} + +bool Sapphire::Scripting::ScriptMgr::onBNpcUpdate( Entity::BNpc& bnpc, uint64_t tickCount ) +{ + auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::BattleNpcScript >( bnpc.getBNpcBaseId() ); + if( script ) + { + script->onUpdate( bnpc, tickCount ); + return true; + } + + return false; +} + +bool Sapphire::Scripting::ScriptMgr::onBNpcHateListAdd( Entity::BNpc& bnpc, Entity::Chara& target ) +{ + auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::BattleNpcScript >( bnpc.getBNpcBaseId() ); + if( script ) + { + script->onHateListAdd( bnpc, target ); + return true; + } + + return false; +} + +bool Sapphire::Scripting::ScriptMgr::onBNpcHateListRemove( Entity::BNpc& bnpc, Entity::Chara& target ) +{ + auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::BattleNpcScript >( bnpc.getBNpcBaseId() ); + if( script ) + { + script->onHateListRemove( bnpc, target ); + return true; + } + + return false; +} + +bool Sapphire::Scripting::ScriptMgr::onBNpcDeath( Entity::BNpc& bnpc ) +{ + auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::BattleNpcScript >( bnpc.getBNpcBaseId() ); + if( script ) + { + script->onDeath( bnpc ); + return true; + } + + return false; +} diff --git a/src/world/Script/ScriptMgr.h b/src/world/Script/ScriptMgr.h index 61793bd4..35411392 100644 --- a/src/world/Script/ScriptMgr.h +++ b/src/world/Script/ScriptMgr.h @@ -130,6 +130,16 @@ namespace Sapphire::Scripting bool onPublicContentEnterTerritory( PublicContentPtr instance, Entity::Player& player, uint32_t eventId, uint16_t param1, uint16_t param2 ); + bool onBNpcInit( Entity::BNpc& bnpc ); + + bool onBNpcUpdate( Entity::BNpc& bnpc, uint64_t tickCount ); + + bool onBNpcHateListAdd( Entity::BNpc& bnpc, Entity::Chara& target ); + + bool onBNpcHateListRemove( Entity::BNpc& bnpc, Entity::Chara& target ); + + bool onBNpcDeath( Entity::BNpc& bnpc ); + bool loadDir( const std::string& dirname, std::set< std::string >& files, const std::string& ext ); NativeScriptMgr& getNativeScriptHandler();