diff --git a/src/scripts/CMakeLists.txt b/src/scripts/CMakeLists.txt index 31cc1334..adc06e5b 100644 --- a/src/scripts/CMakeLists.txt +++ b/src/scripts/CMakeLists.txt @@ -46,7 +46,7 @@ foreach(_scriptDir ${children}) MODULE ${SCRIPT_BUILD_FILES} "${SCRIPT_INCLUDE_FILES}" - "${_scriptDir}/ScriptLoader.cpp" ) + "${_scriptDir}/ScriptLoader.cpp" ) target_link_libraries( "script_${_name}" world ) diff --git a/src/world/CMakeLists.txt b/src/world/CMakeLists.txt index 632151e2..76deabf9 100644 --- a/src/world/CMakeLists.txt +++ b/src/world/CMakeLists.txt @@ -9,24 +9,26 @@ file( GLOB SERVER_SOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} Action/*.cpp ContentFinder/*.cpp DebugCommand/*.cpp + Encounter/*.cpp + Encounter/InstanceContent/*.cpp Event/*.cpp FreeCompany/*.cpp Inventory/*.cpp Linkshell/*.cpp Manager/*.cpp Math/*.cpp + Navi/*.cpp Network/*.cpp Network/Util/*.cpp Network/Handlers/*.cpp Network/PacketWrappers/*.cpp + Quest/*.cpp Script/*.cpp StatusEffect/*.cpp + Task/*.cpp Territory/*.cpp Territory/Housing/*.cpp - Util/*.cpp - Navi/*.cpp - Task/*.cpp - Quest/*.cpp ) + Util/*.cpp ) add_executable( world ${SERVER_SOURCE_FILES} ) diff --git a/src/world/Encounter/EncounterFight.h b/src/world/Encounter/EncounterFight.h new file mode 100644 index 00000000..d8bd421c --- /dev/null +++ b/src/world/Encounter/EncounterFight.h @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include + +namespace Sapphire +{ + class EncounterState + { + public: + using EncounterStatePtr = std::shared_ptr< EncounterState >; + using StateStack = std::stack< EncounterStatePtr >; + using StateStackPtr = std::shared_ptr< StateStack >; + + protected: + bool m_bShouldFinish{ false }; + StateStackPtr m_stateStack; + std::shared_ptr< EncounterFight > m_pEncounter; + uint64_t m_startTime{ 0 }; + + public: + EncounterState( std::shared_ptr< EncounterFight > pEncounter ) : + m_pEncounter( pEncounter ) + { + }; + + virtual ~EncounterState() = default; + bool shouldFinish() { return m_bShouldFinish; }; + + virtual void init() = 0; + virtual void update( uint64_t deltaTime ) = 0; + + virtual void finish() = 0; + }; + + enum class EncounterFightStatus + { + IDLE, + ACTIVE, + FAIL, + SUCCESS + }; + + class EncounterFight : public std::enable_shared_from_this< EncounterFight > + { + public: + EncounterFight( InstanceContentPtr pInstance ) : + m_pInstance( pInstance ) + { + }; + virtual ~EncounterFight() = default; + + virtual void init() = 0; + virtual void start() = 0; + virtual void update( uint64_t deltaTime ) = 0; + virtual void reset() = 0; + + virtual void addState( EncounterState::EncounterStatePtr pState, bool initState = true ) = 0; + virtual void addBNpc( Entity::BNpcPtr pBNpc ) = 0; + virtual void removeBNpc( uint32_t layoutId ) = 0; + virtual Entity::BNpcPtr getBNpc( uint32_t layoutId ) = 0; + + virtual EncounterFightStatus getEncounterFightStatus() const = 0; + + protected: + uint64_t m_startTime{ 0 }; + EncounterState::StateStackPtr m_stateStack; + std::set< Entity::PlayerPtr > m_playerList; + std::unordered_map< uint32_t, Entity::BNpcPtr > m_bnpcs; + InstanceContentPtr m_pInstance; + EncounterFightStatus m_status{ EncounterFightStatus::IDLE }; + }; +} \ No newline at end of file diff --git a/src/world/Encounter/InstanceContent/IfritNormal.h b/src/world/Encounter/InstanceContent/IfritNormal.h new file mode 100644 index 00000000..8b2760be --- /dev/null +++ b/src/world/Encounter/InstanceContent/IfritNormal.h @@ -0,0 +1,198 @@ +#include + +namespace Sapphire +{ + class IfritNormalData + { + public: + static constexpr int IFRIT = 4126276; + static constexpr int HELLFIRE = 0; + }; + + class IfritStateTwo : public EncounterState + { + public: + IfritStateTwo( EncounterFightPtr pEncounter ) : EncounterState( pEncounter ) + { + } + + void init() override + { + Logger::info( "stage 2 init" ); + } + + void update( uint64_t deltaTime ) override + { + if( m_startTime == 0 ) + m_startTime = deltaTime; + + auto timeElapsedMs = deltaTime - m_startTime; + + auto pIfrit = m_pEncounter->getBNpc( IfritNormalData::IFRIT ); + + pIfrit->setRot( pIfrit->getRot() - .2f ); + pIfrit->sendPositionUpdate(); + + if( timeElapsedMs > 5000 ) + { + m_bShouldFinish = true; + } + } + + void finish() override + { + Logger::info( "stage 2 done, going back to stage 1" ); + } + }; + + + class IfritStateOne : public EncounterState + { + public: + IfritStateOne( EncounterFightPtr pEncounter ) : EncounterState( pEncounter ) + { + } + + void init() override + { + Logger::info( "stage 1 init" ); + } + + void update( uint64_t deltaTime ) override + { + if( m_startTime == 0 ) + m_startTime = deltaTime; + + auto timeElapsedMs = deltaTime - m_startTime; + + auto pIfrit = m_pEncounter->getBNpc( IfritNormalData::IFRIT ); + + pIfrit->setRot( pIfrit->getRot() + .2f ); + pIfrit->sendPositionUpdate(); + + if( timeElapsedMs > 5000 ) + { + auto ifritTwoState = std::make_shared< IfritStateTwo >( m_pEncounter ); + m_pEncounter->addState( ifritTwoState ); + } + + if( timeElapsedMs > 12000 ) + { + pIfrit->hateListGetHighest()->die(); + } + } + + void finish() override + { + + } + }; + + class IfritEncounterFight : public EncounterFight + { + public: + IfritEncounterFight( InstanceContentPtr pInstance ) : EncounterFight( pInstance ) + { + + }; + + void init() override + { + m_status = EncounterFightStatus::IDLE; + m_startTime = 0; + + m_stateStack = std::make_shared< EncounterState::StateStack >(); + + // todo: i don't like this + auto boss = m_pInstance->createBNpcFromLayoutId( IfritNormalData::IFRIT, 13884, Common::BNpcType::Enemy ); + addBNpc( boss ); + + //instance.sendForward(); + /* + auto ifritStateTwo = std::make_shared< IfritStateTwo >( m_stateStack ); + m_stateStack->push( ifritStateTwo );*/ + } + + void addState( EncounterState::EncounterStatePtr pState, bool initState = true ) override + { + m_stateStack->push( pState ); + if( initState ) + pState->init(); + } + + void start() override + { + auto ifritInitState = std::make_shared< IfritStateOne >( shared_from_this() ); + addState( ifritInitState ); + + m_status = EncounterFightStatus::ACTIVE; + } + + void update( uint64_t deltaTime ) override + { + // todo: better way to start fights here.. + + auto ifrit = getBNpc( IfritNormalData::IFRIT ); + + if( ifrit; ifrit->hateListGetHighestValue() != 0 && m_status == EncounterFightStatus::IDLE ) + { + m_startTime = deltaTime; + start(); + } + + if( m_status == EncounterFightStatus::ACTIVE && ifrit && !ifrit->hateListGetHighest()->isAlive() ) + { + m_status = EncounterFightStatus::FAIL; + } + + if( m_stateStack; !m_stateStack->empty() ) + { + if( m_stateStack->top()->shouldFinish() ) + { + m_stateStack->top()->finish(); + m_stateStack->pop(); + } + + m_stateStack->top()->update( deltaTime ); + } + } + + void reset() override + { + auto boss = m_pInstance->getActiveBNpcByLayoutId( IfritNormalData::IFRIT ); + if( boss ) + { + removeBNpc( IfritNormalData::IFRIT ); + m_pInstance->removeActor( boss ); + + } + + + init(); + } + + EncounterFightStatus getEncounterFightStatus() const override + { + return m_status; + } + + void addBNpc( Entity::BNpcPtr pBNpc ) override + { + m_bnpcs[ pBNpc->getLayoutId() ] = pBNpc; + } + + Entity::BNpcPtr getBNpc( uint32_t layoutId ) override + { + auto bnpc = m_bnpcs.find( layoutId ); + if( bnpc != std::end( m_bnpcs ) ) + return bnpc->second; + + return nullptr; + } + + void removeBNpc( uint32_t layoutId ) override + { + m_bnpcs.erase( layoutId ); + } + }; +} diff --git a/src/world/ForwardsZone.h b/src/world/ForwardsZone.h index adedecbe..ce8a4d2e 100644 --- a/src/world/ForwardsZone.h +++ b/src/world/ForwardsZone.h @@ -29,6 +29,7 @@ TYPE_FORWARD( ItemContainer ); TYPE_FORWARD( Land ); TYPE_FORWARD( Linkshell ); TYPE_FORWARD( FreeCompany ); +TYPE_FORWARD( EncounterFight ); namespace World { diff --git a/src/world/Network/PacketWrappers/MoveActorPacket.h b/src/world/Network/PacketWrappers/MoveActorPacket.h index d11f8e9e..fd65249d 100644 --- a/src/world/Network/PacketWrappers/MoveActorPacket.h +++ b/src/world/Network/PacketWrappers/MoveActorPacket.h @@ -10,8 +10,7 @@ namespace Sapphire::Network::Packets::WorldPackets::Server { /** - * @brief The Client UI Initialization packet. This must be sent to the client - * once upon connection to configure the UI. + * @brief The MoveActor packet for updating an actor's position. */ class MoveActorPacket : public ZoneChannelPacket< FFXIVIpcActorMove > { diff --git a/src/world/Script/NativeScriptApi.cpp b/src/world/Script/NativeScriptApi.cpp index 87997387..01587b8d 100644 --- a/src/world/Script/NativeScriptApi.cpp +++ b/src/world/Script/NativeScriptApi.cpp @@ -290,6 +290,5 @@ namespace Sapphire::ScriptAPI uint16_t param1, uint16_t param2 ) { } - } diff --git a/src/world/Script/NativeScriptApi.h b/src/world/Script/NativeScriptApi.h index 531a9b40..f43e4e4f 100644 --- a/src/world/Script/NativeScriptApi.h +++ b/src/world/Script/NativeScriptApi.h @@ -375,7 +375,7 @@ namespace Sapphire::ScriptAPI }; /*! - * @brief The base class for any scripts that implement behaviour related to instance content zones + * @brief The base class for any scripts that implement behaviour related to quest battles */ class QuestBattleScript : public ScriptObject { @@ -405,7 +405,6 @@ namespace Sapphire::ScriptAPI return Common::Service< World::Manager::PlayerMgr >::ref(); } }; - } #endif diff --git a/src/world/Script/ScriptLoader.cpp b/src/world/Script/ScriptLoader.cpp index 164520f3..be462921 100644 --- a/src/world/Script/ScriptLoader.cpp +++ b/src/world/Script/ScriptLoader.cpp @@ -241,7 +241,7 @@ Sapphire::ScriptAPI::ScriptObject** Sapphire::Scripting::ScriptLoader::getScript } else { - Logger::warn( "did not find a win32initLinkshell export on a windows script target - the server will likely crash!" ); + Logger::warn( "did not find a win32initWarp export on a windows script target - the server will likely crash!" ); } #else auto func = reinterpret_cast< getScripts >( dlsym( handle, "getScripts" ) ); diff --git a/src/world/Script/ScriptMgr.cpp b/src/world/Script/ScriptMgr.cpp index d56a0626..c000d028 100644 --- a/src/world/Script/ScriptMgr.cpp +++ b/src/world/Script/ScriptMgr.cpp @@ -683,10 +683,13 @@ bool Sapphire::Scripting::ScriptMgr::onZoneInit( const Territory& zone ) bool Sapphire::Scripting::ScriptMgr::onInstanceInit( InstanceContent& instance ) { + auto instId = instance.getDirectorId(); auto script = m_nativeScriptMgr->getScript< Sapphire::ScriptAPI::InstanceContentScript >( instance.getDirectorId() ); + if( script ) { script->onInit( instance ); + return true; } @@ -700,6 +703,7 @@ bool Sapphire::Scripting::ScriptMgr::onInstanceUpdate( InstanceContent& instance if( script ) { script->onUpdate( instance, tickCount ); + return true; } diff --git a/src/world/Territory/InstanceContent.cpp b/src/world/Territory/InstanceContent.cpp index d5b1b6dc..fee9886a 100644 --- a/src/world/Territory/InstanceContent.cpp +++ b/src/world/Territory/InstanceContent.cpp @@ -28,6 +28,8 @@ #include "InstanceContent.h" #include "InstanceObjectCache.h" +#include + using namespace Sapphire::Common; using namespace Sapphire::Network::Packets; @@ -54,6 +56,8 @@ Sapphire::InstanceContent::InstanceContent( std::shared_ptr< Excel::ExcelStruct< m_currentBgm( pInstanceConfiguration->data().Music ), m_instanceExpireTime( Util::getTimeSeconds() + 300 ), m_instanceTerminateTime( 0 ), + m_instanceResetTime( 0 ), + m_instanceResetFinishTime( 0 ), m_instanceTerminate( false ) { @@ -67,6 +71,11 @@ bool Sapphire::InstanceContent::init() auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); scriptMgr.onInstanceInit( *this ); + // todo: every fight is now ifrit + m_pEncounter = std::make_shared< IfritEncounterFight >( std::dynamic_pointer_cast< InstanceContent, Territory >( shared_from_this() ) ); + + m_pEncounter->init(); + return true; } @@ -137,10 +146,10 @@ void Sapphire::InstanceContent::onUpdate( uint64_t tickCount ) if( it == m_playerMap.end() ) return; - auto player = it->second; - if( !player->isLoadingComplete() || - !player->isDirectorInitialized() || - player->hasCondition( PlayerCondition::WatchingCutscene ) ) + auto pPlayer = it->second; + if( !pPlayer->isLoadingComplete() || + !pPlayer->isDirectorInitialized() || + pPlayer->hasCondition( PlayerCondition::WatchingCutscene ) ) return; } @@ -158,12 +167,62 @@ void Sapphire::InstanceContent::onUpdate( uint64_t tickCount ) if( m_pEntranceEObj ) m_pEntranceEObj->setPermissionInvisibility( 1 ); m_state = DutyInProgress; + break; } case DutyReset: + { + // todo: revive players if trial/enclosed raid arena, add reset timer + if( m_instanceResetTime == 0 ) + { + sendDutyReset(); + m_instanceResetTime = tickCount + 3000; + + return; + } + else if( tickCount < m_instanceResetTime ) + return; + + if( m_instanceResetFinishTime == 0 ) + { + m_instanceResetFinishTime = tickCount + 5000; + m_pEncounter->reset(); + + auto& server = Common::Service< World::WorldServer >::ref(); + for( const auto& playerIt : m_playerMap ) + { + auto pPlayer = playerIt.second; + + pPlayer->resetHp(); + pPlayer->resetMp(); + pPlayer->setStatus( Common::ActorStatus::Idle ); + + movePlayerToEntrance( *pPlayer ); + auto zoneInPacket = makeActorControlSelf( pPlayer->getId(), Appear, 0x3, 0, 0, 0 ); + auto setStatusPacket = makeActorControl( pPlayer->getId(), SetStatus, static_cast< uint8_t >( Common::ActorStatus::Idle ) ); + + + server.queueForPlayer( pPlayer->getCharacterId(), zoneInPacket ); + server.queueForPlayers( pPlayer->getInRangePlayerIds( true ), setStatusPacket ); + } + + if( m_pEntranceEObj ) + m_pEntranceEObj->setPermissionInvisibility( 0 ); + + return; + } + else if( tickCount < m_instanceResetFinishTime ) + return; + + + m_pEntranceEObj->setPermissionInvisibility( 1 ); + sendForward(); + + m_state = DutyInProgress; break; + } case DutyInProgress: { @@ -178,6 +237,9 @@ void Sapphire::InstanceContent::onUpdate( uint64_t tickCount ) m_instanceTerminate = true; updateBNpcs( tickCount ); + + if( m_pEncounter->getEncounterFightStatus() == EncounterFightStatus::FAIL ) + m_state = DutyReset; break; } @@ -215,6 +277,8 @@ void Sapphire::InstanceContent::onUpdate( uint64_t tickCount ) auto& scriptMgr = Common::Service< Scripting::ScriptMgr >::ref(); scriptMgr.onInstanceUpdate( *this, tickCount ); + m_pEncounter->update( tickCount ); + m_lastUpdate = tickCount; } @@ -421,6 +485,17 @@ void Sapphire::InstanceContent::sendForward() } } +void Sapphire::InstanceContent::sendDutyReset() +{ + auto& server = Common::Service< World::WorldServer >::ref(); + for( const auto& playerIt : m_playerMap ) + { + auto player = playerIt.second; + server.queueForPlayer( player->getCharacterId(), makeActorControlSelf( player->getId(), DirectorUpdate, getDirectorId(), + DirectorEventId::LoadingScreen, 3000 ) ); + } +} + void Sapphire::InstanceContent::sendVoteState() { auto& server = Common::Service< World::WorldServer >::ref(); @@ -454,6 +529,34 @@ Sapphire::InstanceContent::InstanceContentState Sapphire::InstanceContent::getSt return m_state; } +void Sapphire::InstanceContent::movePlayerToEntrance( Sapphire::Entity::Player& player ) +{ + auto& exdData = Common::Service< Data::ExdData >::ref(); + auto& instanceObjectCache = Common::Service< InstanceObjectCache >::ref(); + auto contentInfo = exdData.getRow< Excel::InstanceContent >( m_instanceContentId ); + + auto rect = instanceObjectCache.getEventRange( contentInfo->data().EntranceRect ); + + if( m_pEntranceEObj != nullptr ) + { + if( rect ) + player.setRot( Util::eulerToDirection( { rect->header.transform.rotation.x, rect->header.transform.rotation.y, rect->header.transform.rotation.z } ) ); + else + player.setRot( PI ); + player.setPos( m_pEntranceEObj->getPos() ); + } + else if( rect ) + { + player.setRot( Util::eulerToDirection( { rect->header.transform.rotation.x, rect->header.transform.rotation.y, rect->header.transform.rotation.z } ) ); + player.setPos( { rect->header.transform.translation.x, rect->header.transform.translation.y, rect->header.transform.translation.z } ); + } + else + { + player.setRot( PI ); + player.setPos( { 0.f, 0.f, 0.f } ); + } +} + void Sapphire::InstanceContent::onBeforePlayerZoneIn( Sapphire::Entity::Player& player ) { // remove any players from the instance who aren't bound on zone in @@ -463,30 +566,7 @@ void Sapphire::InstanceContent::onBeforePlayerZoneIn( Sapphire::Entity::Player& // if a player has already spawned once inside this instance, don't move them if they happen to zone in again if( !hasPlayerPreviouslySpawned( player ) ) { - auto& exdData = Common::Service< Data::ExdData >::ref(); - auto& instanceObjectCache = Common::Service< InstanceObjectCache >::ref(); - auto contentInfo = exdData.getRow< Excel::InstanceContent >( m_instanceContentId ); - - auto rect = instanceObjectCache.getEventRange( contentInfo->data().EntranceRect ); - - if( m_pEntranceEObj != nullptr ) - { - if( rect ) - player.setRot( Util::eulerToDirection( { rect->header.transform.rotation.x, rect->header.transform.rotation.y, rect->header.transform.rotation.z } ) ); - else - player.setRot( PI ); - player.setPos( m_pEntranceEObj->getPos() ); - } - else if( rect ) - { - player.setRot( Util::eulerToDirection( { rect->header.transform.rotation.x, rect->header.transform.rotation.y, rect->header.transform.rotation.z } ) ); - player.setPos( { rect->header.transform.translation.x, rect->header.transform.translation.y, rect->header.transform.translation.z } ); - } - else - { - player.setRot( PI ); - player.setPos( { 0.f, 0.f, 0.f } ); - } + movePlayerToEntrance( player ); } player.resetObjSpawnIndex(); diff --git a/src/world/Territory/InstanceContent.h b/src/world/Territory/InstanceContent.h index 214bd5da..c502f0f6 100644 --- a/src/world/Territory/InstanceContent.h +++ b/src/world/Territory/InstanceContent.h @@ -5,6 +5,7 @@ #include "Event/Director.h" #include "Forwards.h" #include +#include namespace Sapphire { @@ -121,6 +122,8 @@ namespace Sapphire void sendDutyCommence(); + void sendDutyReset(); + void sendForward(); void sendDutyFailed( bool force ); @@ -191,6 +194,8 @@ namespace Sapphire bool isTerminationReady() const; size_t getInstancePlayerCount() const; + + void movePlayerToEntrance( Entity::Player& player ); private: std::shared_ptr< Excel::ExcelStruct< Excel::InstanceContent > > m_instanceConfiguration; std::shared_ptr< Excel::ExcelStruct< Excel::ContentFinderCondition > > m_contentFinderCondition; @@ -202,6 +207,8 @@ namespace Sapphire uint32_t m_instanceExpireTime; uint64_t m_instanceTerminateTime; uint64_t m_instanceCommenceTime; + uint64_t m_instanceResetTime; + uint64_t m_instanceResetFinishTime; bool m_voteState; bool m_instanceTerminate; @@ -214,6 +221,8 @@ namespace Sapphire // the players which are bound to the instance, regardless of inside or offline std::set< uint32_t > m_boundPlayerIds; + + EncounterFightPtr m_pEncounter; }; }