diff --git a/src/common/Common.h b/src/common/Common.h index 1cf24ea3..2c2a4f89 100644 --- a/src/common/Common.h +++ b/src/common/Common.h @@ -1885,6 +1885,7 @@ namespace Sapphire::Common using PlayerStateFlagList = std::vector< PlayerCondition >; + // todo: load BNpcBase and other exd data into this struct struct BNPCInstanceObject { uint16_t territoryType; diff --git a/src/common/Util/UtilMath.cpp b/src/common/Util/UtilMath.cpp index 9a5d1991..8bbb1c95 100644 --- a/src/common/Util/UtilMath.cpp +++ b/src/common/Util/UtilMath.cpp @@ -99,6 +99,20 @@ FFXIVARR_POSITION3 Util::getOffsettedPosition( const FFXIVARR_POSITION3& pos, fl return ret; } +FFXIVARR_POSITION3 Util::getKnockbackPosition( const FFXIVARR_POSITION3& origin, const FFXIVARR_POSITION3& pos, float distance ) +{ + FFXIVARR_POSITION3 ret{ pos }; + + float from = Common::Util::calcAngFrom( origin.x, origin.z, pos.x, pos.z ); + float angle = PI - from + ( PI / 2 ); + + angle = angle + ( PI / 2 ); + ret.x -= distance * cos( angle ); + ret.z += distance * sin( angle ); + + return ret; +} + FFXIVARR_POSITION3 Util::transform( const FFXIVARR_POSITION3& vector, const Matrix33& matrix ) { FFXIVARR_POSITION3 dst{}; diff --git a/src/common/Util/UtilMath.h b/src/common/Util/UtilMath.h index 21be831a..9f956494 100644 --- a/src/common/Util/UtilMath.h +++ b/src/common/Util/UtilMath.h @@ -31,6 +31,8 @@ namespace Sapphire::Common::Util FFXIVARR_POSITION3 getOffsettedPosition( const FFXIVARR_POSITION3& pos, float rotation, float right, float up, float forward ); + FFXIVARR_POSITION3 getKnockbackPosition( const FFXIVARR_POSITION3& origin, const FFXIVARR_POSITION3& pos, float distance ); + template < typename T > T clamp( T val, T minimum, T maximum ) { diff --git a/src/world/AI/Fsm/StateCombat.cpp b/src/world/AI/Fsm/StateCombat.cpp index 5bcefd58..612e9288 100644 --- a/src/world/AI/Fsm/StateCombat.cpp +++ b/src/world/AI/Fsm/StateCombat.cpp @@ -48,19 +48,19 @@ void AI::Fsm::StateCombat::onUpdate( Entity::BNpc& bnpc, uint64_t tickCount ) bnpc.moveTo( *pHatedActor ); } - if( pNaviProvider->syncPosToChara( bnpc ) ) - bnpc.sendPositionUpdate(); + pNaviProvider->syncPosToChara( bnpc ); - if( distance < ( bnpc.getNaviTargetReachedDistance() + pHatedActor->getRadius() ) ) + if( distance < ( bnpc.getNaviTargetReachedDistance() + bnpc.getRadius() + pHatedActor->getRadius() ) ) { - if( !bnpc.hasFlag( Entity::TurningDisabled ) && bnpc.face( pHatedActor->getPos() ) ) - bnpc.sendPositionUpdate(); + if( !bnpc.hasFlag( Entity::TurningDisabled ) ) + bnpc.face( pHatedActor->getPos() ); if( !bnpc.checkAction() ) bnpc.processGambits( tickCount ); // in combat range. ATTACK! - bnpc.autoAttack( pHatedActor ); + if( !bnpc.hasFlag( Entity::BNpcFlag::AutoAttackDisabled ) ) + bnpc.autoAttack( pHatedActor ); } } diff --git a/src/world/Action/Action.cpp b/src/world/Action/Action.cpp index 912a73f0..544d6af4 100644 --- a/src/world/Action/Action.cpp +++ b/src/world/Action/Action.cpp @@ -181,6 +181,16 @@ const Common::FFXIVARR_POSITION3& Action::Action::getPos() const return m_pos; } +void Action::Action::setRot( float rot ) +{ + m_rot = rot; +} + +float Action::Action::getRot() const +{ + return m_rot; +} + void Action::Action::setTargetId( uint64_t targetId ) { m_targetId = targetId; @@ -327,10 +337,10 @@ void Action::Action::start() data.CastTime = static_cast< float >( m_castTimeMs ) / 1000.f; data.Target = static_cast< uint32_t >( m_targetId ); - data.TargetPos[ 0 ] = Common::Util::floatToUInt16( m_pSource->getPos().x ); - data.TargetPos[ 1 ] = Common::Util::floatToUInt16( m_pSource->getPos().y ); - data.TargetPos[ 2 ] = Common::Util::floatToUInt16( m_pSource->getPos().z ); - data.Dir = m_pSource->getRot(); + data.TargetPos[ 0 ] = Common::Util::floatToUInt16( m_pos.x ); + data.TargetPos[ 1 ] = Common::Util::floatToUInt16( m_pos.y ); + data.TargetPos[ 2 ] = Common::Util::floatToUInt16( m_pos.z ); + data.Dir = m_rot; server().queueForPlayers( m_pSource->getInRangePlayerIds( m_pSource->isPlayer() ), castPacket ); diff --git a/src/world/Action/Action.h b/src/world/Action/Action.h index e6e71424..ea55ab1f 100644 --- a/src/world/Action/Action.h +++ b/src/world/Action/Action.h @@ -30,6 +30,9 @@ namespace Sapphire::World::Action void setPos( const Common::FFXIVARR_POSITION3& pos ); const Common::FFXIVARR_POSITION3& getPos() const; + void setRot( float rot ); + float getRot() const; + void setTargetId( uint64_t targetId ); uint64_t getTargetId() const; Entity::CharaPtr getSourceChara() const; @@ -220,6 +223,7 @@ namespace Sapphire::World::Action std::shared_ptr< Excel::ExcelStruct< Excel::Action > > m_actionData; Common::FFXIVARR_POSITION3 m_pos{}; + float m_rot{}; ActionResultBuilderPtr m_actionResultBuilder; diff --git a/src/world/Actor/BNpc.cpp b/src/world/Actor/BNpc.cpp index 51b7494f..198815e2 100644 --- a/src/world/Actor/BNpc.cpp +++ b/src/world/Actor/BNpc.cpp @@ -377,7 +377,6 @@ bool BNpc::moveTo( const FFXIVARR_POSITION3& pos ) // Reached destination face( pos ); setPos( pos1 ); - sendPositionUpdate(); pNaviProvider->updateAgentPosition( *this ); return true; } @@ -389,7 +388,6 @@ bool BNpc::moveTo( const FFXIVARR_POSITION3& pos ) else face( pos ); setPos( pos1 ); - sendPositionUpdate(); return false; } @@ -415,7 +413,6 @@ bool BNpc::moveTo( const Chara& targetChara ) // Reached destination face( targetChara.getPos() ); setPos( pos1 ); - sendPositionUpdate(); pNaviProvider->resetMoveTarget( *this ); pNaviProvider->updateAgentPosition( *this ); return true; @@ -427,7 +424,6 @@ bool BNpc::moveTo( const Chara& targetChara ) else face( targetChara.getPos() ); setPos( pos1 ); - sendPositionUpdate(); return false; } @@ -659,6 +655,10 @@ void BNpc::onTick() void BNpc::update( uint64_t tickCount ) { Chara::update( tickCount ); + + if( m_dirtyFlag & DirtyFlag::Position ) + sendPositionUpdate(); + m_fsm->update( *this, tickCount ); } diff --git a/src/world/Actor/BNpc.h b/src/world/Actor/BNpc.h index ca860d1b..540a624f 100644 --- a/src/world/Actor/BNpc.h +++ b/src/world/Actor/BNpc.h @@ -32,7 +32,6 @@ namespace Sapphire::Entity enum BNpcFlag { - None = 0x00, Immobile = 0x01, TurningDisabled = 0x02, Invincible = 0x04, diff --git a/src/world/Actor/Chara.cpp b/src/world/Actor/Chara.cpp index 9956880d..302c4818 100644 --- a/src/world/Actor/Chara.cpp +++ b/src/world/Actor/Chara.cpp @@ -14,6 +14,8 @@ #include "Network/PacketWrappers/ActorControlTargetPacket.h" #include "Network/PacketWrappers/EffectPacket1.h" #include "Network/PacketWrappers/HudParamPacket.h" +#include "Network/PacketWrappers/MoveActorPacket.h" + #include "Network/Util/PacketUtil.h" #include "Action/Action.h" @@ -25,6 +27,7 @@ #include "Manager/TerritoryMgr.h" #include "Manager/MgrUtil.h" #include "Manager/PlayerMgr.h" +#include "Navi/NaviProvider.h" #include "Common.h" using namespace Sapphire; @@ -199,6 +202,24 @@ bool Chara::isAlive() const return ( m_hp > 0 ); } +void Chara::setPos( const Common::FFXIVARR_POSITION3& pos, bool broadcastUpdate ) +{ + GameObject::setPos( pos, broadcastUpdate ); + m_dirtyFlag |= DirtyFlag::Position; +} + +void Chara::setPos( float x, float y, float z, bool broadcastUpdate ) +{ + GameObject::setPos( x, y, z, broadcastUpdate ); + m_dirtyFlag |= DirtyFlag::Position; +} + +void Sapphire::Entity::Chara::setRot( float rot ) +{ + GameObject::setRot( rot ); + m_dirtyFlag |= DirtyFlag::Position; +} + /*! \return max hp for the actor */ uint32_t Chara::getMaxHp() const { @@ -856,4 +877,39 @@ void Chara::onTick() Network::Util::Packet::sendHudParam( *this ); } +} + +void Chara::knockback( const FFXIVARR_POSITION3& origin, float distance, bool ignoreNav ) +{ + auto kbPos = Common::Util::getKnockbackPosition( origin, m_pos, distance ); + auto& teriMgr = Common::Service< Manager::TerritoryMgr >::ref(); + auto pTeri = teriMgr.getTerritoryByGuId( getTerritoryId() ); + + if( !ignoreNav ) + { + // todo: use agent + auto pNav = pTeri->getNaviProvider(); + auto path = pNav->findFollowPath( m_pos, kbPos ); + + FFXIVARR_POSITION3 navPos{origin}; + float prevDistance{1000.f}; + for( const auto& point : path ) + { + auto navDist = Common::Util::distance( kbPos, point ); + if( navDist < prevDistance ) + { + navPos = point; + prevDistance = navDist; + } + } + setPos( navPos ); + pNav->updateAgentPosition( *this ); + } + else + { + setPos( kbPos ); + } + pTeri->updateActorPosition( *this ); + + server().queueForPlayers( getInRangePlayerIds(), std::make_shared< MoveActorPacket >( *this, getRot(), 2, 0, 0, 0x5A / 4 ) ); } \ No newline at end of file diff --git a/src/world/Actor/Chara.h b/src/world/Actor/Chara.h index 8ee157de..23183b15 100644 --- a/src/world/Actor/Chara.h +++ b/src/world/Actor/Chara.h @@ -13,6 +13,14 @@ namespace Sapphire::Entity { + // todo: probably macro/template operators for enums + enum DirtyFlag : uint32_t + { + Position = 0x01, + HpMpTp = 0x02, + Status = 0x04, + Appearance = 0x08 + }; /*! \class Chara @@ -96,6 +104,8 @@ namespace Sapphire::Entity /*! Detour Crowd actor scale */ float m_radius; + uint32_t m_dirtyFlag{}; + public: Chara( Common::ObjKind type ); @@ -195,6 +205,12 @@ namespace Sapphire::Entity bool isAlive() const; + virtual void setPos( const Common::FFXIVARR_POSITION3& pos, bool broadcastUpdate = true ) override; + + virtual void setPos( float x, float y, float z, bool broadcastUpdate = true ) override; + + virtual void setRot( float rot ) override; + virtual uint32_t getMaxHp() const; virtual uint32_t getMaxMp() const; @@ -266,6 +282,8 @@ namespace Sapphire::Entity Common::BaseParam getPrimaryStat() const; + void knockback( const Common::FFXIVARR_POSITION3& origin, float distance, bool ignoreNav = false ); + }; } \ No newline at end of file diff --git a/src/world/Actor/GameObject.h b/src/world/Actor/GameObject.h index db4bb3af..2d5bd6b3 100644 --- a/src/world/Actor/GameObject.h +++ b/src/world/Actor/GameObject.h @@ -10,7 +10,6 @@ namespace Sapphire::Entity { - /*! \class GameObject \brief Base class for all actor/objects @@ -59,13 +58,13 @@ namespace Sapphire::Entity Common::FFXIVARR_POSITION3& getPos(); const Common::FFXIVARR_POSITION3& getPos() const; - void setPos( const Common::FFXIVARR_POSITION3& pos, bool broadcastUpdate = true ); + virtual void setPos( const Common::FFXIVARR_POSITION3& pos, bool broadcastUpdate = true ); - void setPos( float x, float y, float z, bool broadcastUpdate = true ); + virtual void setPos( float x, float y, float z, bool broadcastUpdate = true ); float getRot() const; - void setRot( float rot ); + virtual void setRot( float rot ); bool isChara() const; diff --git a/src/world/Encounter/InstanceContent/IfritNormal.h b/src/world/Encounter/InstanceContent/IfritNormal.h index 74e81e64..4f93a560 100644 --- a/src/world/Encounter/InstanceContent/IfritNormal.h +++ b/src/world/Encounter/InstanceContent/IfritNormal.h @@ -61,7 +61,6 @@ namespace Sapphire auto pIfrit = pEncounter->getBNpc( NPC_IFRIT ); pIfrit->setRot( pIfrit->getRot() + .2f ); - pIfrit->sendPositionUpdate(); // todo: use gambits+timelines for this if( timeElapsedMs > 10000 ) @@ -100,7 +99,6 @@ namespace Sapphire auto pIfrit = pEncounter->getBNpc( NPC_IFRIT ); pIfrit->setRot( pIfrit->getRot() - .2f ); - pIfrit->sendPositionUpdate(); // todo: use gambits+timelines for this if( timeElapsedMs > 5000 ) diff --git a/src/world/Encounter/Timepoint.cpp b/src/world/Encounter/Timepoint.cpp index bc954d44..3cacd774 100644 --- a/src/world/Encounter/Timepoint.cpp +++ b/src/world/Encounter/Timepoint.cpp @@ -423,6 +423,7 @@ namespace Sapphire::Encounter { pAction->setInterrupted( Common::ActionInterruptType::RegularInterrupt ); pAction->interrupt(); + pBNpc->setCurrentAction( nullptr ); return false; } else @@ -441,7 +442,6 @@ namespace Sapphire::Encounter { pBNpc->setRot( pSetPosData->m_rot ); pBNpc->setPos( pSetPosData->m_x, pSetPosData->m_y, pSetPosData->m_z, true ); - pBNpc->sendPositionUpdate(); } } break; diff --git a/src/world/Manager/DebugCommandMgr.cpp b/src/world/Manager/DebugCommandMgr.cpp index 824c9065..20b21dee 100644 --- a/src/world/Manager/DebugCommandMgr.cpp +++ b/src/world/Manager/DebugCommandMgr.cpp @@ -560,7 +560,7 @@ void DebugCommandMgr::add( char* data, Entity::Player& player, std::shared_ptr< Entity::GameObjectPtr pTarget = nullptr; auto inRange = player.getInRangeActors(); - for( auto pChara : inRange ) + for( auto& pChara : inRange ) { if( pChara->getId() == targetId ) { @@ -583,6 +583,17 @@ void DebugCommandMgr::add( char* data, Entity::Player& player, std::shared_ptr< server().queueForPlayer( player.getCharacterId(), actorMovePacket ); } } + else if( subCommand == "knockback" ) + { + float distance{ 0.f }; + sscanf( params.c_str(), "%f", &distance ); + + for( auto& pActor : player.getInRangeActors() ) + { + if( auto pBNpc = pActor->getAsBNpc(); pBNpc && Common::Util::distance( pBNpc->getPos(), player.getPos() ) <= distance ) + pBNpc->knockback( player.getPos(), distance ); + } + } else if( subCommand == "achvGeneral" ) { uint32_t achvSubtype; diff --git a/src/world/Navi/NaviProvider.cpp b/src/world/Navi/NaviProvider.cpp index b510a6bb..0a4ae78c 100644 --- a/src/world/Navi/NaviProvider.cpp +++ b/src/world/Navi/NaviProvider.cpp @@ -355,6 +355,7 @@ std::vector< Sapphire::Common::FFXIVARR_POSITION3 > // iterPos[ 0 ], iterPos[ 1 ], iterPos[ 2 ], // targetPos[ 0 ], targetPos[ 1 ], targetPos[ 2 ] ); + // todo: adjust these for the actor radius const float STEP_SIZE = 0.5f; const float SLOP = 0.15f; diff --git a/src/world/Network/Handlers/PacketHandlers.cpp b/src/world/Network/Handlers/PacketHandlers.cpp index c66b80d0..f5df6c9b 100644 --- a/src/world/Network/Handlers/PacketHandlers.cpp +++ b/src/world/Network/Handlers/PacketHandlers.cpp @@ -243,6 +243,7 @@ void Sapphire::Network::GameConnection::moveHandler( const Packets::FFXIVARR_PAC if( !player.hasInRangeActor() ) return; + // todo: probably move this into a builder and send the packet on Player::update if( m_dirtyFlags & DirtyFlag::Position ) //auto movePacket = std::make_shared< MoveActorPacket >( player, headRotation, animationType, animationState, animationSpeed, unknownRotation ); auto movePacket = std::make_shared< MoveActorPacket >( player, headRotation, data.flag, data.flag2, animationSpeed, unknownRotation ); server().queueForPlayers( player.getInRangePlayerIds(), movePacket ); diff --git a/src/world/Territory/Territory.cpp b/src/world/Territory/Territory.cpp index 4ea7edfb..c717a8ef 100644 --- a/src/world/Territory/Territory.cpp +++ b/src/world/Territory/Territory.cpp @@ -895,6 +895,8 @@ bool Territory::loadBNpcs() stmt->setUInt( 1, getTerritoryTypeId() ); auto res = db.query( stmt ); + // todo: load any exd links, cache them, build more info and setup bnpcs properly + while( res->next() ) { auto bnpc = std::make_shared< Common::BNPCInstanceObject >();